Repository navigation
Conversation
- Bound AccountManager token waits and make them interruptible - Add OkHttp call timeout, session wait timeout, and overall sync timeout - Record interrupted/busy syncs in history and report them to Crashlytics - Log sync stages, session holder, and WorkManager stop reason - Bump versionCode to 17205 and regenerate baseline profiles Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Cancellation races can leak the session lock or duplicate history rows, while blocking Drive maintenance can outlast the sync deadline.
3 open issues
What changed in this PR
This PR hardens AppWatcher’s background update checks against stalls and adds diagnostics to investigate gaps in update notifications.
Changes:
- Adds bounded token requests, HTTP calls, session-lock waits, and sync execution, with cancellation handling.
- Records interrupted and abandoned syncs, exposes new history statuses, and logs session stages and worker stop reasons.
- Adds regression tests, increments
versionCodeto17205, and refreshes the release startup profile.
| File | Description |
|---|---|
app/src/test/java/com/anod/appwatcher/sync/UpdateCheckRegistrationTest.kt |
Tests timeout, cancellation, busy-session, and abandoned-history handling. |
app/src/test/java/com/anod/appwatcher/accounts/AuthTokenBlockingTest.kt |
Tests token interruption and session-wait timeout recovery. |
app/src/release/generated/baselineProfiles/startup-prof.txt |
Refreshes the generated release startup profile. |
app/src/main/java/com/anod/appwatcher/sync/UpdateCheck.kt |
Adds sync deadlines, interruption recording, and stage diagnostics. |
app/src/main/java/com/anod/appwatcher/sync/SyncWorker.kt |
Logs worker lifecycle and supplies stop reasons. |
app/src/main/java/com/anod/appwatcher/sync/SchedulesHistoryScreen.kt |
Displays interrupted and busy-session statuses. |
app/src/main/java/com/anod/appwatcher/database/SchedulesTable.kt |
Adds unfinished-history lookup and conditional interruption updates. |
app/src/main/java/com/anod/appwatcher/database/entities/Schedule.kt |
Defines the new failure statuses. |
app/src/main/java/com/anod/appwatcher/CrashlyticsExceptionFilter.kt |
Keeps interruption diagnostics reportable. |
app/src/main/java/com/anod/appwatcher/AppModule.kt |
Configures a two-minute OkHttp call timeout. |
app/src/main/java/com/anod/appwatcher/accounts/AuthTokenBlocking.kt |
Bounds token waits and makes blocking requests interruptible. |
app/src/main/java/com/anod/appwatcher/accounts/AuthAccountInitializer.kt |
Adds timed session acquisition and holder-stage diagnostics. |
app/build.gradle.kts |
Increments the release build number. |
Review effort: Balanced
| withTimeoutOrNull(timeouts.syncMillis) { | ||
| performSerialized(manualSync, stopReason) | ||
| } ?: -1 |
| val recorded = if (schedule.id == -1L) { | ||
| SchedulesTable.Queries.save(interrupted, database) | ||
| true |
- Remove timed session acquisition and busy status; keep single gate, bounded token/HTTP calls and active sync cap - Add account-allowlisted persistent diagnostic recorder (30 days, 10 MiB) with Settings toggle, share and delete - Record scheduler, worker, session, sync stage and startup state events - Pass diagnostic recorder explicitly to sync and settings components Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved concurrency and cancellation issues can leave callers suspended indefinitely, duplicate sync history, and block UI work, while a missing test dependency causes a timeout.
6 open issues
Bound and cancel Drive maintenance operations Recorder monitor blocks UI and scheduling on slow disk operations · New Full command queue can suspend callers indefinitely during shutdown · New Mutex wait bypasses advertised session timeout and history status · New Test fixture lacks disabled DiagnosticRecorder registration · New Make schedule insert and ID assignment cancellation-safe
1 resolved since last review
Review effort: Balanced
| val result = CompletableDeferred<Unit>() | ||
| pendingResults.add(result) | ||
| try { | ||
| commands.send(Command.Operation(action, result)) |
| playSessionCoordinator.withSession(owner) { | ||
| withTimeoutOrNull(timeouts.syncMillis) { |
- Bind diagnostics consent to the account hash in Preferences - Make recorder operations non-throwing and self-refreshing - Reduce SettingsViewModel diagnostics handling and reuse StartActivity - Fix missing diagnostics dependency in account selection test Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Account switches can retain consent and buffered events, while recorder shutdown, lock-held disk I/O, and unbounded Drive transfers can hang operations or delay cancellation.
7 open issues
Bound and cancel Drive maintenance operations Avoid holding the recorder monitor during filesystem I/O · New Clear consent and invalidate buffered records on account changes · New Bound and cancel Drive maintenance transfers · New Mutex wait bypasses advertised session timeout and history status Full command queue can suspend callers indefinitely during shutdown Make schedule insert and ID assignment cancellation-safe
2 resolved since last review
Review effort: Balanced
| is Command.Record -> synchronized(lock) { | ||
| if (!prefs.isDiagnosticsEnabled || command.account != accountHash || command.generation != generation) return@synchronized | ||
| store.append(command.record.encode(command.time, process), command.time) |
| var isDiagnosticsEnabled: Boolean | ||
| get() = isDiagnosticsAllowed && devicePreferences.getString("diagnostic-consent", null) == | ||
| accountSnapshot?.name?.let(DiagnosticAccounts::hash) |
| withTimeoutOrNull(timeouts.syncMillis) { | ||
| performSerialized(manualSync, stopReason) | ||
| } ?: -1 |


Problem
Beta users (and the maintainer) stop receiving update notifications until the app is opened. On-device refresh history shows every multi-day gap starting with a scheduled sync row that started but never finished.
Likely cause: a scheduled sync blocks forever on AccountManager
getAuthToken(...).result(no timeout, not cancellable) while holding thePlaySessionCoordinatormutex, so every later sync waits on the lock.Fixes
runInterruptible;OperationCanceledExceptioncaused by cancellation is surfaced as cancellation.callTimeoutof 2 minutes.Diagnostics
PlaySession,UpdateCheck, andSyncWorker.Release
versionCode17205; release baseline profiles regenerated on a physical device.Tests
Opt-in diagnostic logs
The AppLog-tee follow-up is currently in the worktree and has not been committed or pushed.