Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heap Dump Expedited Work not starting in Android < API31 with LeakCanary 2.8 #2268

Closed
dicosta opened this issue Jan 5, 2022 · 6 comments · Fixed by #2275
Closed

Heap Dump Expedited Work not starting in Android < API31 with LeakCanary 2.8 #2268

dicosta opened this issue Jan 5, 2022 · 6 comments · Fixed by #2275
Milestone

Comments

@dicosta
Copy link

dicosta commented Jan 5, 2022

Description

When trying to do a 'Dump Heap Now' on an emulator with API30/29, the Work does not start, and and an exception is logged (see the trace in additional information section)

If the emulator runs API31, it works fine.
If I don't include the work-runtime, running an emulator with API30/29, it runs ok (I understand that in this case, the job is being done in a simple Thread)

Steps to Reproduce

  1. Open Leaks Screen on an Emulator running Android API29 or API30
  2. Go to 'Heap Dumps'
  3. Tap on 'Dump Heap Now'

Expected behavior:
The 'Dump Heap' work to run

Version Information

  • LeakCanary version: 2.8
  • Android OS version: 11, 10,
  • Gradle version: 7.0.2

Additional Information

I think this is related to that Expedited work, when run in an Android version prior to API31, runs in a foreground service, and because of that, the Work should provide foreground info (like the icon and the title) for the notification the system displays when a foreground service is run

Trace:

2022-01-05 12:38:07.901 4829-4896/com.XXX.XXX.XXX D/LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.XXX.XXX.XXX/2022-01-05_12-38-04_891.hprof on WorkManager remote worker
2022-01-05 12:38:07.984 4829-4895/com.XXX.XXX.XXX E/WM-WorkerWrapper: Work [ id=xxx, tags={ leakcanary.internal.HeapAnalyzerWorker } ] failed because it threw an exception/error
    java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:311)
        at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
        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.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$1.run(WorkerWrapper.java:291)
        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(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.utils.WorkForegroundRunnable$2.run(WorkForegroundRunnable.java:93)
        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(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
        at androidx.work.ListenableWorker.getForegroundInfoAsync(ListenableWorker.java:260)
        at androidx.work.impl.utils.WorkForegroundRunnable$1.run(WorkForegroundRunnable.java:85)
        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(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
2022-01-05 12:38:07.985 4829-4895/com.xxxx.xxx.xxx I/WM-WorkerWrapper: Worker result FAILURE for Work [ id=xxx, tags={ leakcanary.internal.HeapAnalyzerWorker } ]
2022-01-05 12:38:07.991 4829-4829/com.xxx.xxx.xxx D/LeakCanary: Watching instance of androidx.work.impl.background.systemjob.SystemJobService (androidx.work.impl.background.systemjob.SystemJobService received Service#onDestroy() callback) with key xxx
@pyricau
Copy link
Member

pyricau commented Jan 6, 2022

Thanks for the detailed report and suggested solution!

pyricau added a commit that referenced this issue Jan 6, 2022
setExpedited() isn't enough, we also needed to provide ForegroundInfo. The documentation doesn't make this very obvious, and there's no API enforcement.

Fixes #2268
@pyricau pyricau added this to the 2.8.1 milestone Jan 6, 2022
@arochevdev
Copy link

I still have this error with 2.8.1 on Android 11 on Realme 8:

02-16 20:15:03.859 29792 29983 D LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-redacted/2022-02-16_20-15-01_253.hprof on WorkManager remote worker
02-16 20:15:03.872 29792 30039 I OpenGLRenderer: Davey! duration=2460ms; Flags=2, IntendedVsync=109347825965374, Vsync=109347825965374, OldestInputEvent=0, NewestInputEvent=0, HandleInputStart=109347825965374, AnimationStart=109347825965374, PerformTraversalsStart=109347825965374, DrawStart=109347825965374, SyncQueued=109347830611902, SyncStart=109347830613440, IssueDrawCommandsStart=109350270499363, SwapBuffers=109350278614440, FrameCompleted=109350286449440, DequeueBufferDuration=643384, QueueBufferDuration=1354154, GpuCompleted=108649226147552, 
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper: Work [ id=3e77621d-a199-46c9-b975-e8c074fc4c3e, tags={ leakcanary.internal.HeapAnalyzerWorker } ] failed because it threw an exception/error
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:311)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at java.lang.Thread.run(Thread.java:923)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper: Caused by: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.WorkerWrapper$1.run(WorkerWrapper.java:291)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at android.os.Handler.handleCallback(Handler.java:938)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at android.os.Handler.dispatchMessage(Handler.java:99)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at android.os.Looper.loop(Looper.java:255)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at android.app.ActivityThread.main(ActivityThread.java:8232)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at java.lang.reflect.Method.invoke(Native Method)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper: Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.WorkForegroundRunnable$2.run(WorkForegroundRunnable.java:93)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      ... 7 more
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper: Caused by: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.ListenableWorker.getForegroundInfoAsync(ListenableWorker.java:260)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      at androidx.work.impl.utils.WorkForegroundRunnable$1.run(WorkForegroundRunnable.java:85)
02-16 20:15:03.970 29792 30427 E WM-WorkerWrapper:      ... 7 more
02-16 20:15:03.974 29792 30427 I WM-WorkerWrapper: Worker result FAILURE for Work [ id=3e77621d-a199-46c9-b975-e8c074fc4c3e, tags={ leakcanary.internal.HeapAnalyzerWorker } ]
02-16 20:15:03.989 29792 29792 D LeakCanary: Watching instance of androidx.work.impl.background.systemjob.SystemJobService (androidx.work.impl.background.systemjob.SystemJobService received Service#onDestroy() callback) with key d46c5113-6176-4e7c-af06-200f2e6fb3d2

@arochevdev
Copy link

Sorry, it seems that this was due to the issue with WorkManager setup in the app (WorkerFactory always returned non-null worker from app's code even if workerClassName did not belong to the app's code).

@pyricau
Copy link
Member

pyricau commented Apr 3, 2022

@arochevdev How did this WorkerFactory behavior trigger the crash? I worry that others might run into this.

@arochevdev
Copy link

Application had its own WorkerFactory implementation, which always returned non-null ListenableWorker instance (app's own class). This prevented WorkManager from using its own built-in factory which creates workers from workerClassName using standard constructor parameters (custom WorkerFactory implementations are supposed to return null if requested to create worker with workerClassName that they know nothing about). I.e. every time any worker was created (including leakcanary.internal.HeapAnalyzerWorker), app's custom WorkerFactory created specific ListenableWorker implementation that had nothing to do with what being requested. And since WorkManager doesn't check that created worker's class matches workerClassName (it blindly trusts that if WorkerFactory returned non-null object then it did correct thing) it happily used it instead of requested worker.

I don't think that there is anything that can be done on LeakCanary side, application just used WorkManager incorrectly.

@pyricau
Copy link
Member

pyricau commented Apr 5, 2022

Thx!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants