fix(fgs): migrate background engine to sibling-isolate spawn#1192
Draft
SERDUN wants to merge 9 commits into
Draft
fix(fgs): migrate background engine to sibling-isolate spawn#1192SERDUN wants to merge 9 commits into
SERDUN wants to merge 9 commits into
Conversation
…rtup failure (WT-1373) Replaces standalone FlutterEngine + executeDartCallback with FlutterEngineGroup.makeEngine to fix a silent Dart isolate startup failure on Samsung Android 13 (and similar devices). Root cause: when the main FlutterEngine is already running in the same process, calling executeDartCallback on an independent FlutterEngine silently fails to start the Dart isolate at the JNI level. No exception is thrown, but signalingServiceCallbackDispatcher never executes — confirmed by a debugPrint canary that never appears in logcat across 8+ restart cycles. FlutterEngineGroup.makeEngine uses FlutterJNI.spawn() for all engines after the first, which creates a proper child isolate sharing the process-wide Dart VM. This avoids the root-isolate conflict that causes the silent failure. The custom createEngine override preserves automaticallyRegisterPlugins=false so audio and hardware plugins cannot block the Dart VM startup on Android 16+ REMOTE_MESSAGING services (regression guard for the fix in commit 455f170). The engineGroup singleton is double-checked-locked and process-scoped so the shared Dart VM is initialised at most once per process lifetime.
FlutterEngineGroup.createAndRunEngine calls executeDartEntrypoint which requires the 3-arg DartEntrypoint(bundle, libraryUri, functionName) form. Without the library URI the AOT linker cannot resolve the entry point by name alone and logs "Could not resolve main entrypoint function". Passes callbackInformation.callbackLibraryPath as the second argument so the runtime can locate signalingServiceCallbackDispatcher in the snapshot.
…ate conflict
When the main FlutterEngine is running, FlutterEngineGroup.createAndRunEngine
for the first engine in a fresh group falls back to executeDartEntrypoint.
In AOT mode this calls Dart_LookupLibrary("package:...") on a newly created
isolate group where package URIs are not registered, resulting in:
"Could not resolve main entrypoint function"
The spawn path (FlutterJNI.spawn) creates a sibling isolate inside the
existing Dart VM where the library table is already populated — lookup succeeds.
WebtritSignalingServicePlugin now stores binding.flutterEngine in a static
field. FlutterEngineHelper reads it and:
- if main engine is active → calls FlutterEngine.spawn() via reflection
(spawn() is package-private, not accessible outside io.flutter.embedding.engine)
- if no main engine (push cold-start) → FlutterEngineGroup fallback
(no existing root isolate, so executeDartEntrypoint works correctly)
…ted ErrorNotification subclasses (#1190)
… into FlutterEngineHelper
Removes the implicit static dependency on WebtritSignalingServicePlugin.mainFlutterEngine
from FlutterEngineHelper. The coupling was hidden — FlutterEngineHelper silently read a
field on an unrelated plugin class with no visible dependency in its API.
- Introduce FlutterEngineHolder (single-responsibility object) to hold a reference to
whichever running FlutterEngine most recently attached WebtritSignalingServicePlugin
- Rename the field to runningEngine to reflect the actual semantics: any engine with an
active Dart VM qualifies (main UI engine, push-handler engine, etc.), not just "main"
- FlutterEngineHelper constructor now receives mainEngineProvider: () -> FlutterEngine?
making the dependency explicit and the class independently testable
- SignalingForegroundService passes { FlutterEngineHolder.runningEngine } at construction
… recovery Startup watchdog: 30s → 10s With the spawn-from-main-engine path the Dart isolate starts in 1–3s; 10s is sufficient margin for loaded devices while cutting worst-case user-visible recovery time roughly in half. Hub no-port timeout: 15s → 8s Hub port appears after the background isolate registers it (~2–3s). 8s gives enough margin while allowing faster detection when the FGS fails silently (e.g. during a cold push-start with no running engine).
…ation - Wrap spawnFromEngine() in try-catch(ReflectiveOperationException): if the Flutter embedding changes spawn()'s signature, fall back to FlutterEngineGroup instead of leaving backgroundEngine null and entering a WorkManager restart loop - Use 'as? FlutterEngine ?: throw IllegalStateException' instead of hard cast so a null return from Method.invoke() produces a clear error rather than a misleading NPE - Pin spawn() reflection to flutter_embedding 3.32.4 with a re-verify comment - Clarify PlatformViewsController() comment: instance is intentionally headless - Document FlutterEngineHolder thread-safety assumption (main-thread reads/writes) - Document engineGroup singleton lifetime and why reuse across restarts is correct
- Broaden inner catch from ReflectiveOperationException to Exception so
that IllegalStateException from spawnFromEngine (spawn() returned null)
also triggers the FlutterEngineGroup fallback instead of bypassing it
- Assign backgroundEngine after attachToService() succeeds rather than
inside .also{} so a throw from attachToService cannot leave the engine
running but unreferenced (resource leak)
- Add comment to engineGroup singleton noting that bad internal state
requires a process restart to recover
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Re-opening #1191 as draft for further investigation and testing.
Original fix: spawn background engine as sibling isolate from main engine to avoid root-isolate conflict in AOT mode.