'Where should I catch the InterruptedException thrown by FirestorePagingAdapter - FirebaseUI-Android

A handful of my users are experiencing a crash according to Firebase Crashlytics:

Fatal Exception: io.reactivex.rxjava3.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.InterruptedException
   at io.reactivex.rxjava3.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:2)
   at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:54)
   at io.reactivex.rxjava3.core.Single.subscribe(Single.java:8)
   at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:2)
   at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:9)
   at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:13)
   at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java)
   at java.util.concurrent.FutureTask.run(FutureTask.java:266)
   at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:923)

Caused by java.lang.InterruptedException
   at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1024)
   at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1334)
   at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:232)
   at com.google.android.gms.tasks.zzaa.zza(zzaa.java:2)
   at com.google.android.gms.tasks.Tasks.await(Tasks.java:28)
   at com.firebase.ui.firestore.paging.FirestorePagingSource.lambda$loadSingle$0(FirestorePagingSource.java)
   at com.firebase.ui.firestore.paging.FirestorePagingSource.$r8$lambda$aRsAFsiduxRWbX-Rs8tsd39JsV0(FirestorePagingSource.java)
   at com.firebase.ui.firestore.paging.FirestorePagingSource$$InternalSyntheticLambda$0$301762c7c2c4bb9cc6676b1600ffb92473af44448cde26854aae0b3e15f6f4f6$0.call$bridge(FirestorePagingSource.java:92)
   at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:16)
   at io.reactivex.rxjava3.core.Single.subscribe(Single.java:8)
   at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:2)
   at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:9)
   at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:13)
   at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java)
   at java.util.concurrent.FutureTask.run(FutureTask.java:266)
   at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:923)

I have trouble reproducing the error on my end. However, in the Further reading linked in the error message I can read the following, which seems to be relatable to my problem:

In addition, some 3rd party libraries/code throw when they get interrupted by a cancel/dispose call which leads to an undeliverable exception most of the time. Internal changes in 2.0.6 now consistently cancel or dispose a Subscription/Disposable before cancelling/disposing a task or worker (which causes the interrupt on the target thread).

// in some library
try {
   doSomethingBlockingly()
} catch (InterruptedException ex) {
   // check if the interrupt is due to cancellation
   // if so, no need to signal the InterruptedException
   if (!disposable.isDisposed()) {
      observer.onError(ex);
   }
}

If the library/code already did this, the undeliverable InterruptedExceptions should stop now. If this pattern was not employed before, we encourage updating the code/library in question.

Below is the code where I set up the FirestorePagingAdapter, using FirebaseUI-Android. As stated it works perfect for all but a handful of users.

com.google.firebase.firestore.Query feedBaseQuery = firebaseHelper.getFeedBaseQuery();
    PagingConfig config = new PagingConfig(10, 5, false);
    FirestorePagingOptions<FeedObject> options = new FirestorePagingOptions.Builder<FeedObject>()
            .setLifecycleOwner(mainActivity)
            .setQuery(feedBaseQuery, config, FeedObject.class)
            .build();
    this.feedAdapter = new FirestoreFeedAdapter(mainActivity, options);
    recyclerView.setHasFixedSize(false);
    recyclerView.setLayoutManager(new LinearLayoutManager(mainActivity));
    recyclerView.setAdapter(this.feedAdapter);

So, my best guess right now is that the app gets interrupted somehow when retrieving data from Firestore which trows an InterruptedException which is never caught. I guess my question is if and where I should catch the InterruptedException?



Solution 1:[1]

I had same error when I change fragment quickly with really bad network or in offline mode, not really nice but I added that on my application class. And it worked fine...

RxJavaPlugins.setErrorHandler { e ->
    if (e is UndeliverableException) {
        Log.d(TAG, e.toString())
    } else {
        Thread.currentThread().also { thread ->
            thread.uncaughtExceptionHandler.uncaughtException(thread, e)
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 gizmoog