Skip to content

fix(process): Dispatch heap analysis events to main process listeners (#1789)#2812

Open
faraz152 wants to merge 2 commits intosquare:mainfrom
faraz152:fix/1789-separate-process-callback
Open

fix(process): Dispatch heap analysis events to main process listeners (#1789)#2812
faraz152 wants to merge 2 commits intosquare:mainfrom
faraz152:fix/1789-separate-process-callback

Conversation

@faraz152
Copy link
Copy Markdown

Summary

When heap analysis runs in a separate :leakcanary process via RemoteHeapAnalyzerWorker, the HeapAnalysisDone event is dispatched only to listeners in the background process. Custom EventListener callbacks configured in the main process never receive the analysis result.

This PR bridges the gap by serializing the done event in the background process and making it available to the main process through WorkManager's output data mechanism.

Related Issue

Fixes #1789

Changes

RemoteHeapAnalyzerWorker (background process)

  • After analysis completes, serializes the HeapAnalysisDone event to a file in the app's filesDir (shared across processes)
  • Passes the file path as WorkManager output data via Result.success(outputData)
  • Still calls InternalLeakCanary.sendEvent() in the background process for backward compatibility

RemoteWorkManagerHeapAnalyzer (main process)

  • After enqueuing the remote work, observes WorkInfo via LiveData
  • When the worker succeeds, reads the serialized event from the file path in output data
  • Deserializes and re-dispatches the HeapAnalysisDone event to main process listeners
  • Cleans up the temporary event file after reading

Design Decisions

  • File-based IPC over WorkManager Data: WorkManager's output Data has a 10 KB limit. Heap analysis events can exceed this due to the serialized HeapAnalysis object. Writing to a file in filesDir (accessible by both processes of the same app) avoids this limit.
  • Backward compatible: The background process still dispatches events locally via sendEvent(), so existing setups that configure listeners in the background process continue to work.
  • Self-cleaning: The event file is deleted after the main process reads it.

Testing

  • Module compiles successfully: ./gradlew :leakcanary:leakcanary-android-core:compileDebugKotlin
  • Verified the serialization mechanism matches the existing Serializables pattern used for WorkManager input data
  • Events are already Serializable (by design, per the EventListener.Event class doc)

Checklist

  • Code follows project style guidelines
  • No unrelated changes included
  • Commit messages follow conventional format
  • Build compiles successfully

When heap analysis runs in a separate :leakcanary process via
RemoteHeapAnalyzerWorker, the HeapAnalysisDone event is only dispatched
to listeners in the background process. Custom OnHeapAnalyzedListener
callbacks registered in the main process never receive the result.

This fix serializes the analysis result to a shared file in the worker,
then passes the file path as WorkManager output data. The main process
observes work completion via WorkInfo LiveData, reads the event back,
and re-dispatches it to all configured event listeners.

Fixes square#1789
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 30, 2026

CLA assistant check
All committers have signed the CLA.

@faraz152 faraz152 marked this pull request as ready for review March 30, 2026 10:51
Copy link
Copy Markdown
Member

@pyricau pyricau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! The issue makes sense, and I like the approach. Left some feedback and a few open questions.

try {
val doneEvent = Serializables.fromByteArray<HeapAnalysisDone<*>>(eventFile.readBytes())
if (doneEvent != null) {
SharkLog.d { "Dispatching remote heap analysis result to main process listeners" }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't need this log in the success case, we'll see an event anyway

  • remove?

} catch (e: Throwable) {
SharkLog.d(e) { "Error reading remote worker event file" }
} finally {
eventFile.delete()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the main process dies after scheduling the RemoteHeapAnalyzerWorker, then it'll never listen to the result (=> no update) and also the result file will never be deleted. Any way we could avoid that? Maybe re observing on startup? Mayble cleanup on startup? idk

// Observe the remote worker's completion from the main process so we can
// re-dispatch the HeapAnalysisDone event to listeners running here.
val workInfoLiveData = workManager.getWorkInfoByIdLiveData(heapAnalysisRequest.id)
Handler(Looper.getMainLooper()).post {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use mainHandler from Handlers.kt instead

- Remove success log in dispatchEventFromRemoteWorker (redundant,
  the event dispatch itself is visible)
- Use mainHandler from Handlers.kt instead of Handler(Looper.getMainLooper())
- Add dispatchAndCleanupOrphanedEventFiles() to handle the case where
  the main process dies after scheduling RemoteHeapAnalyzerWorker:
  on startup, scan filesDir for leftover event files, dispatch any
  recoverable events, and delete all orphaned files
@faraz152
Copy link
Copy Markdown
Author

Pushed fixes for all 3 comments:

  1. Removed the success log — redundant since the event dispatch is visible
  2. Switched to mainHandler from Handlers.kt
  3. Added dispatchAndCleanupOrphanedEventFiles() — on startup, scans filesDir for leftover leakcanary_remote_event_* files, dispatches any recoverable events, then deletes them. Called from InternalLeakCanary.invoke() on a background thread (only if the remote service is in the classpath).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running heap analysis in separate process does not invoke custom OnLeakFoundCallback callback

3 participants