diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-05 00:36:18 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-05 00:36:18 +0000 |
| commit | 7b9eb14db9b953b3c5882b3b85c819d9baa2dd2c (patch) | |
| tree | a5d4fa8959186d8f34da6dede582d4b0c58ce154 /internal/auth/middleware.go | |
| parent | 0400e59e5d4a0140e98af01ad1acbc83d3f3f0a7 (diff) | |
Widget: fix "complete works once, then stops" race between overlapping completions
Root cause traced from server logs, not guessed: every completion request
was succeeding server-side (100% 200s, including a 5-tap burst spanning
different tasks), and each successful completion's response payload was
correctly shrinking. So the failure wasn't dispatch or network -- it was
that a successful completion could still get silently undone client-side.
fetchAndPersist does an unconditional full overwrite of the cached item
list on every successful GET. CompleteWorker/DeferWorker run one instance
per task id with no ordering guarantee between different ids' workers
(different unique work names, no KEEP protection across them -- that
protection only ever covered same-task double-taps). So: tapping complete
on task A starts a GET that's still in flight; tapping complete on task B
before A's GET returns optimistically removes B locally; A's slower GET
response, captured before B's completion landed, then overwrites the
cache and silently resurrects B.
Fix: track locally-optimistic removals with a timestamp (PendingRemovals.kt)
and filter them out of every fetchAndPersist write for a bounded TTL (2
min), regardless of which worker's fetch is doing the writing. The TTL
means a completion that never actually confirms (permanent network
failure) still self-heals via the next periodic refresh, matching an
existing self-healing property already relied on elsewhere in this
codebase, instead of hiding the task forever.
Added PendingRemovalsTest.kt (pure-function unit tests, no Android
runtime needed) covering the exact race scenario plus TTL expiry and
edge cases. Verified the tests actually catch a regression by deliberately
reverting the fix to a no-op against a real backup, confirming 3 tests
failed with the exact expected assertion, then restoring and confirming
green again.
Built, tested, and published as doot-widget.apk.
Diffstat (limited to 'internal/auth/middleware.go')
0 files changed, 0 insertions, 0 deletions
