You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Serialize foreground promotion for concurrent Dart-backed periodic workers #95
The application configures native_workmanager with maxConcurrentTasks: 1 and schedules multiple Dart-backed periodic workers as foreground services. In native_workmanager 1.9.0, ForegroundNativeWorker.doWork() calls setForeground() before the worker passes through TaskConcurrency . Callback execution is therefore serialized, but foreground-service promotion is not.
When multiple periodic workers become eligible at approximately the same time, logs show overlapping worker and Flutter-engine startup. Some workers complete, while others are cancelled during engine startup, before their Dart callbacks begin. WorkManager reports a normal CancellationException , marks the run as failed, and re-enqueues the periodic work. This suggests foreground/startup contention, but the WorkManager stop reason has not been captured, so the cause is unconfirmed.
With maxConcurrentTasks: 1 , foreground promotion and Dart callback execution should be admitted serially, or waiting workers should not promote to foreground until admitted. Diagnostics should distinguish a WorkManager stop from a callback failure, while keeping periodic work scheduled.
Hey, thanks for the detailed report, the ordering you pointed out was real. In 1.9.0 ForegroundNativeWorker called setForeground() before the limiter permit, so with maxConcurrentTasks: 1 all the waiting workers still promoted and started their engines at the same time. I measured it on a Pixel 6 Pro, 5 periodic FGS Dart workers got promoted within ~90ms of each other.
PR #96 changes it to wait for slot -> promote -> run, so now they go one by one. It also logs phase, isStopped, stopReason and runAttemptCount when a run gets cancelled.
One thing though, I couldnt reproduce the cancellation itself, not even on 1.9.0 with 12 workers, so I cant say this fixes it yet. Could you try the PR branch with debug logging on and send the cancelled taskId=... stopReason=... line if it still happens? That would tell us if its a WorkManager stop (and which reason) or something else.
Hey, thanks for the detailed report, the ordering you pointed out was real. In 1.9.0 ForegroundNativeWorker called setForeground() before the limiter permit, so with maxConcurrentTasks: 1 all the waiting workers still promoted and started their engines at the same time. I measured it on a Pixel 6 Pro, 5 periodic FGS Dart workers got promoted within ~90ms of each other.
PR #96 changes it to wait for slot -> promote -> run, so now they go one by one. It also logs phase, isStopped, stopReason and runAttemptCount when a run gets cancelled.
One thing though, I couldnt reproduce the cancellation itself, not even on 1.9.0 with 12 workers, so I cant say this fixes it yet. Could you try the PR branch with debug logging on and send the cancelled taskId=... stopReason=... line if it still happens? That would tell us if its a WorkManager stop (and which reason) or something else.
Of course this is the log I got:
D/NativeWorkManager(15544): ForegroundNativeWorker: cancelled taskId=EXPORT_RECEIPT-periodic workId=7ba8eaee-5e97-45d0-bd48-a4eee65bd6df phase=worker-execution isStopped=false stopReason=-256 runAttemptCount=0
Update: this is out in 1.10.1 (pub.dev + GitHub release), so you can try it without the PR branch now.
If you still see workers getting cancelled before the Dart callback starts, could you turn on debug logging and send the line that looks like cancelled taskId=... phase=... isStopped=... stopReason=...? Thats the part we're missing, it will tell us if WorkManager stopped the run (and why) or if its something else. Im keeping this issue open until we have that, since I still cant reproduce the cancellation on my side.
Found it, thanks a lot for the log. isStopped=false stopReason=-256 means WorkManager did NOT stop the worker, the cancel came from our own code. The Dart isolate readiness wait (10s) threw a TimeoutCancellationException, which is a CancellationException, and we rethrow those on purpose, so WorkManager saw a cancelled run instead of a failure (no retry/backoff, no failure event). I reproduced it on a Pixel 6 Pro by lowering that timer and got the exact same log line as yours.
Fixed in 1.10.2: it is a normal failure now (WorkManager records RETRY, backoff applies). Could you try it and tell me if the cancelled lines go away?
One open thing: this makes the failure handled properly, but doesnt explain why the engine takes more than 10s to come up on your device. If you still see DartEngineStartupTimeoutException in the logs, tell me roughly how many workers fire at the same time and what device it is, and I'll look at the startup time itself.
Closing this since both parts are fixed and released: 1.10.1 serializes foreground promotion behind the maxConcurrentTasks permit, and 1.10.2 turns the engine start-up timeout into a normal failure (retry/backoff) instead of a cancellation.
Not answered: why the engine took more than 10s to start on the reporter's device. If you still see DartEngineStartupTimeoutException or a cancelled taskId=... line on 1.10.2, please reopen and include the device, Android version and how many workers fire at the same time.
Activity
vietnguyentuan2019 commentedon Nov 9, 2026
Hey, thanks for the detailed report, the ordering you pointed out was real. In 1.9.0
ForegroundNativeWorkercalledsetForeground()before the limiter permit, so withmaxConcurrentTasks: 1all the waiting workers still promoted and started their engines at the same time. I measured it on a Pixel 6 Pro, 5 periodic FGS Dart workers got promoted within ~90ms of each other.PR #96 changes it to wait for slot -> promote -> run, so now they go one by one. It also logs phase, isStopped, stopReason and runAttemptCount when a run gets cancelled.
One thing though, I couldnt reproduce the cancellation itself, not even on 1.9.0 with 12 workers, so I cant say this fixes it yet. Could you try the PR branch with debug logging on and send the
cancelled taskId=... stopReason=...line if it still happens? That would tell us if its a WorkManager stop (and which reason) or something else.Merge pull request #96 from brewkits/fix/issue-95-foreground-admission
devroble commentedon Nov 9, 2026
Of course this is the log I got:
D/NativeWorkManager(15544): ForegroundNativeWorker: cancelled taskId=EXPORT_RECEIPT-periodic workId=7ba8eaee-5e97-45d0-bd48-a4eee65bd6df phase=worker-execution isStopped=false stopReason=-256 runAttemptCount=0
vietnguyentuan2019 commentedon Nov 9, 2026
Update: this is out in 1.10.1 (pub.dev + GitHub release), so you can try it without the PR branch now.
If you still see workers getting cancelled before the Dart callback starts, could you turn on debug logging and send the line that looks like
cancelled taskId=... phase=... isStopped=... stopReason=...? Thats the part we're missing, it will tell us if WorkManager stopped the run (and why) or if its something else. Im keeping this issue open until we have that, since I still cant reproduce the cancellation on my side.Merge pull request #99 from brewkits/fix/issue-95-engine-startup-timeout
vietnguyentuan2019 commentedon Nov 9, 2026
Found it, thanks a lot for the log.
isStopped=false stopReason=-256means WorkManager did NOT stop the worker, the cancel came from our own code. The Dart isolate readiness wait (10s) threw a TimeoutCancellationException, which is a CancellationException, and we rethrow those on purpose, so WorkManager saw a cancelled run instead of a failure (no retry/backoff, no failure event). I reproduced it on a Pixel 6 Pro by lowering that timer and got the exact same log line as yours.Fixed in 1.10.2: it is a normal failure now (WorkManager records RETRY, backoff applies). Could you try it and tell me if the cancelled lines go away?
One open thing: this makes the failure handled properly, but doesnt explain why the engine takes more than 10s to come up on your device. If you still see
DartEngineStartupTimeoutExceptionin the logs, tell me roughly how many workers fire at the same time and what device it is, and I'll look at the startup time itself.vietnguyentuan2019 commentedon Nov 10, 2026
Closing this since both parts are fixed and released: 1.10.1 serializes foreground promotion behind the maxConcurrentTasks permit, and 1.10.2 turns the engine start-up timeout into a normal failure (retry/backoff) instead of a cancellation.
Not answered: why the engine took more than 10s to start on the reporter's device. If you still see
DartEngineStartupTimeoutExceptionor acancelled taskId=...line on 1.10.2, please reopen and include the device, Android version and how many workers fire at the same time.