Skip to content

fix(android): re-invert background channel handshake to Dart-initiated (fixes #738) - #739

Merged
ened merged 1 commit into
mainfrom
fix/dart-initiated-background-handshake
Sep 7, 2026
Merged

fix(android): re-invert background channel handshake to Dart-initiated (fixes #738)#739
ened merged 1 commit into
mainfrom
fix/dart-initiated-background-handshake

Conversation

@ened

@ened ened commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

#738 (and #732, closed prematurely) is a handshake race, not a watchdog bug: since the July 2025 Pigeon migration, BackgroundWorker sends backgroundChannelInitialized to the Dart isolate microseconds after executeDartCallback, while the isolate is still booting. A native→Dart message that arrives before the isolate registered its Pigeon handlers is lost, so the Dart callback never runs and the worker either hangs in RUNNING forever (#732) or fails the 30s watchdog (#738). #735 only converted the symptom from hang to visible failure.

This restores the race-free, Dart-initiated handshake that worked for years before the migration:

  • New host call WorkmanagerHostApi.notifyBackgroundChannelInitialized(), sent by Workmanager().executeTask after WorkmanagerFlutterApi.setUp (fire-and-forget — platforms without a native receiver must not block the task).
  • Android BackgroundWorker no longer pushes anything at the isolate. It waits for the signal (routed through the plugin's existing boundWorker binding; watchdog still armed) and only then invokes executeTask down. Ordering is now guaranteed by construction: Dart can only signal once it is alive, and its handlers are registered before the signal leaves.
  • Watchdog semantics unchanged: a dead engine (no signal within 30s) still fails visibly instead of hanging.

Why the old flow could fail at all: messages sent to an isolate that has not yet registered its channel handlers are dropped (no reply), which is what left workers stuck in RUNNING pre-0.10.9 and failing the watchdog in 0.10.9.

Apple platforms

Compile parity only: the new host method is implemented as a no-op because Apple workers still gate on the legacy kick (which doubles as the in-process dispatcher trigger on the main engine). The Apple headless-engine flow has the same latent race and is tracked as a follow-up (needs device validation).

Tests

  • New Dart test: executeTask emits exactly one readiness signal, after handler registration, and does not wait for the native reply (task still executes while the signal is unanswered).
  • Existing suite green: flutter test across all packages (workmanager 24, android 35, apple 28, linux, platform_interface, web), 63 Kotlin unit tests via :workmanager_android:testDebugUnitTest, dart analyze clean, flutter build ios --debug --no-codesign passes.

Fixes #738

fixes #738)

The Pigeon migration (Jul 2025) flipped the background task handshake from
Dart-initiated to native-initiated: BackgroundWorker now sends
backgroundChannelInitialized to the Dart isolate microseconds after
executeDartCallback, while the isolate is still booting. A native->Dart
message that arrives before the isolate registered its handlers is lost
silently, so the Dart callback never runs and the worker either hangs in
RUNNING forever (#732) or, since the 0.10.9 watchdog, fails after 30s
(#738).

Restore the race-free, Dart-initiated design: Workmanager().executeTask
now signals the native side (new host call notifyBackgroundChannelInitialized)
after WorkmanagerFlutterApi.setUp; BackgroundWorker waits for that signal
(watchdog still armed) and only then invokes executeTask. Because Dart can
only signal once it is alive and its handlers are registered, the follow-up
executeTask call can never race isolate startup.

Apple workers keep the legacy kick-based flow for now (compile parity only);
aligning the Apple headless-engine path is a follow-up.
@ened
ened force-pushed the fix/dart-initiated-background-handshake branch from b0121d7 to e8478ab Compare September 7, 2026 08:48
@ened
ened merged commit 02764be into main Sep 7, 2026
21 checks passed
@ened
ened deleted the fix/dart-initiated-background-handshake branch September 7, 2026 10:28
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.

🐞Workmanager().registerOneOffTask no more runs the task immediately and now cancelling tasks as failed after 30 seconds

1 participant