subsampling-scale-image-view icon indicating copy to clipboard operation
subsampling-scale-image-view copied to clipboard

Crash with RejectedExecutionException when used in RecyclerView

Open Bhullnatik opened this issue 5 years ago • 3 comments

Hello, I'm getting a lot of reports in production from crashes happening with this error:

Fatal Exception: java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1ec5e0b rejected from java.util.concurrent.ThreadPoolExecutor@dcd94e8[Running, pool size = 17, active threads = 17, queued tasks = 128, completed tasks = 1856]

Complete stacktrace here
Fatal Exception: java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1ec5e0b rejected from java.util.concurrent.ThreadPoolExecutor@dcd94e8[Running, pool size = 17, active threads = 17, queued tasks = 128, completed tasks = 1856]
     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2085)
     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:848)
     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1394)
     at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:651)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.execute(SubsamplingScaleImageView.java:1881)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.refreshRequiredTiles(SubsamplingScaleImageView.java:1312)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.preDraw(SubsamplingScaleImageView.java:1359)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.checkReady(SubsamplingScaleImageView.java:1196)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.onTileLoaded(SubsamplingScaleImageView.java:1698)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.access$5900(SubsamplingScaleImageView.java:72)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView$TileLoadTask.onPostExecute(SubsamplingScaleImageView.java:1685)
     at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView$TileLoadTask.onPostExecute(SubsamplingScaleImageView.java:1628)
     at android.os.AsyncTask.finish(AsyncTask.java:695)
     at android.os.AsyncTask.access$600(AsyncTask.java:180)
     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
     at android.os.Handler.dispatchMessage(Handler.java:106)
     at android.os.Looper.loop(Looper.java:201)
     at android.app.ActivityThread.main(ActivityThread.java:6826)
     at java.lang.reflect.Method.invoke(Method.java)
     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

Note that the details of the ThreadPoolExecutor vary depending on the source, although it is always Running and has a pool size equal to the active threads.

Expected behaviour

It raises a catchable error, or queue the job for another time.

Actual behaviour

It crashes without any way to prevent it ourselves.

Steps to reproduce

I'm using this library to display selected photos from user input. The only tricky part is that I use it heavily in a RecyclerView. So at a time, there can be dozens of SubsamplingImageView where I would first use recycle() to make sure the list would be updated, then setImage() with local images.

The SubsamplingImageView itself has isPanEnabled, isQuickScaleEnabled & isZoomEnabled set to false, with setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP).

Affected devices

It happens across all devices consistently with our users' mobile park, although it seems it doesn't happen on Android 10 nor 11 (as a result of an internal change to the framework I suppose).

Devices Android versions
Screenshot 2020-08-24 at 17 11 47 Screenshot 2020-08-24 at 17 11 29

Affected images

N/A

I'm happy to provide more information or to help if needed, although I couldn't reproduce on my side I have enough reports to have some more information.

Seems related to #501 (now closed) & #519 (although for another issue).

Bhullnatik avatar Aug 24 '20 09:08 Bhullnatik

I suggest providing your own executor with setExecutor if your app is doing so much work on the default executor that the queue is exhausted.

davemorrissey avatar Aug 24 '20 14:08 davemorrissey

@davemorrissey Thanks for your answer. I'll look into as I'm not very familiar with AsyncTask's API, but isn't that just going to hide the problem if I provide a bigger Executor? Instead of failing gracefully and reporting the error in onImageLoadError() for example.

Bhullnatik avatar Aug 25 '20 04:08 Bhullnatik

If you provide an executor with an unbounded queue you wouldn't get this error. I'm assuming your app does a lot of other work with AsyncTasks or on the same default executor it uses, because it's very hard to imagine SSIV by itself could queue 128 tasks and not cause an out of memory error. It's designed for no more than three instances at once.

You can make your own Executor instance that catches and reports rejected execution, or possibly provides a different implementation of RejectedExecutionHandler.

davemorrissey avatar Aug 25 '20 07:08 davemorrissey