Skip to content

Restart queued entries on a fresh WorkManager chain at boot (Android) - #56

Closed
arnonate wants to merge 1 commit into
masterfrom
arnonate/RAD-16862-boot-restart
Closed

arnonate wants to merge 1 commit into
masterfrom
arnonate/RAD-16862-boot-restart

Conversation

@arnonate

@arnonate arnonate commented Oct 27, 2026 •

Copy link
Copy Markdown

Jira: RAD-16862

On Android, a customer's queue of 89 captures has sat queued for six days with 0 bytes sent. That's 534 entries. Every launch runs the boot sweep, and no entry has started a run since. Force-stops and long foreground sessions didn't help either.

The sweep queued work through schedule(). That call does nothing when the entry's WorkManager chain already holds a run that hasn't started. When such a run is left over from an earlier process and never starts, every later launch skips the entry. The logs can't confirm that this is what happened on the customer's phone, because they don't include WorkManager state. It is the one path in the library that drops a run without logging anything.

Before: the boot sweep kept whatever WorkManager already held for a queued entry.
After: the boot sweep replaces each queued entry's main chain with a fresh run (ExistingWorkPolicy.REPLACE). Long-backoff wakes and parked-entry wakes are kept as before. Runs scheduled at any other time still use schedule().

What to look at

  • Scheduler.kt: the new restart().
  • QueueController.kt: scheduleRun(boot = true), which the sweep now calls.

Decisions and trade-offs

  • Restart on every launch, not once. A one-time migration would only fix phones that are stuck today. The cost is one WorkManager replace per queued entry at each launch.
  • The sweep still skips an entry whose worker runs in this process. A replace would cancel that worker.
  • A worker WorkManager starts between that check and the replace gets cancelled. Its run moves back to queued, and the fresh run picks it up.

Test Plan

  • ./gradlew :react-native-background-upload:testDebugUnitTest: 307 tests, 0 failures. The sweep tests now assert restart() rather than schedule(). They also assert that a worker running in this process is not restarted.
  • yarn lint:ci passes.
  • I didn't reproduce the stall on a device, and these tests use a fake scheduler, so the real WorkManager replace hasn't been run.

🤖 Generated with Claude Code

The boot sweep scheduled queued entries through schedule(), which does
nothing when the entry's WorkManager chain already holds a run that has
not started. A run left from an earlier process could then hold the entry
back across every launch. The sweep now replaces the main chain. Wakes
and entries whose worker runs in this process are left as before.

RAD-16862

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arnonate

Copy link
Copy Markdown
Author

Closing in favor of #57. Stale expiry wakes fill WorkManager's 20 JobScheduler slots and its in-process scheduler's 200-oldest window. A fresh row from this restart would sit behind them and still not run.

@arnonate arnonate closed this Oct 28, 2026
@arnonate
arnonate deleted the arnonate/RAD-16862-boot-restart branch October 28, 2026 21:00
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.

1 participant