MobileNativeFoundation / MobileNativeFoundation/Store

[BUG] ANR due to ConcurrentHashMap lock in `RealCache`

Open
#182 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug documentation
Dominant language
Kotlin
Stars
3.4k
Forks
217
Avg merge
18m
Merged PRs (30d)
4

Description

See the following thread stack. I'm using a pretty vanilla setup for Room + Store and streaming in ViewModels (i.e. collecting on the main thread):

viewModelScope.launch {
    store.stream(StoreRequest.cached(showId, refresh = false))
        .collect { ... }
}

This might be a guidance issue, where we tell people to stream(...).flowOn(Dispatcher.Default)

Log:

"main" prio=5 tid=1 Runnable
  | group="main" sCount=1 dsCount=0 flags=9 obj=0x717a02f8 self=0xb4000070b9eec380
  | sysTid=27737 nice=0 cgrp=default sched=0/0 handle=0x71e08094f8
  | state=R schedstat=( 45250255527 4245629451 61014 ) utm=4253 stm=271 core=4 HZ=100
  | stack=0x7ff1d62000-0x7ff1d64000 stackSize=8192KB
  | held mutexes= "mutator lock"(shared held)
  at java.util.concurrent.ConcurrentHashMap.replaceNode (ConcurrentHashMap.java:1116)
  at java.util.concurrent.ConcurrentHashMap.remove (ConcurrentHashMap.java:1107)
  at com.dropbox.android.external.cache4.RealCache.evictEntries (RealCache.java:228)
  at com.dropbox.android.external.cache4.RealCache.put (RealCache.java:151)
  at com.dropbox.android.external.store4.impl.RealStore$stream$2.invokeSuspend (RealStore.java:116)
  at com.dropbox.android.external.store4.impl.RealStore$stream$2.invoke (RealStore.java)
  at kotlinx.coroutines.flow.FlowKt__TransformKt$onEach$$inlined$unsafeTransform$1$2.emit (FlowKt__TransformKt.java:134)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at com.dropbox.android.external.store4.impl.RealStore$diskNetworkCombined$$inlined$transform$1$1.emit (RealStore.java:152)
  at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt (FlowKt__ChannelsKt.java:58)
  at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend (FlowKt__ChannelsKt.java)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (BaseContinuationImpl.java:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.java:56)
  at kotlinx.coroutines.EventLoop.processUnconfinedEvent (EventLoop.java:69)
  at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core (CancellableContinuationImpl.java:184)
  at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined (DispatchedTaskKt.java:184)
  at kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTaskKt.java:108)
  at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.java:308)
  at kotlinx.coroutines.CancellableContinuationImpl.completeResume (CancellableContinuationImpl.java:395)
  at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.completeResumeReceive (AbstractChannel.java:872)
  at kotlinx.coroutines.channels.ArrayChannel.offerInternal (ArrayChannel.java:83)
  at kotlinx.coroutines.channels.AbstractSendChannel.send (AbstractSendChannel.java:133)
  at kotlinx.coroutines.channels.ChannelCoroutine.send$suspendImpl (ChannelCoroutine.java:1)
  at kotlinx.coroutines.channels.ChannelCoroutine.send (ChannelCoroutine.java:1)
  at kotlinx.coroutines.flow.internal.SendingCollector.emit (SendingCollector.java:19)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at com.dropbox.android.external.store4.impl.SourceOfTruthWithBarrier$reader$1$invokeSuspend$$inlined$flatMapLatest$1$lambda$1$1.emit (SourceOfTruthWithBarrier.java:153)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:15)
  at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollectorKt.java:1)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:73)
  at kotlinx.coroutines.flow.internal.SafeCollector.emit (SafeCollector.java:55)
  at androidx.room.CoroutinesRoom$Companion$createFlow$1$1$1.invokeSuspend (CoroutinesRoom.java:82)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (BaseContinuationImpl.java:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.java:56)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:223)
  at android.app.ActivityThread.main (ActivityThread.java:7656)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:947)

Smartphone:

  • Device: various
  • OS: various
  • Store Version: 4.0.0-alpha05 (trying alpha06 now)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the stack entries for RealCache.evictEntries and RealCache.put, then trace RealStore.stream around the reported calls. Reproduce the shown viewModelScope and stream collection pattern, including the possible flowOn(Dispatcher.Default) guidance. Done means the ANR cause and an actionable code or guidance resolution are established.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin
Domain
mobile-dev, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.