Repository navigation
feat(sync): automatic one-shot staging reset (#296) - #328
TimeToBuildBob wants to merge 2 commits into
Conversation
Implement automatic one-shot reset of the phone's staging database to remove pre-ActivityWatch/aw-server-rust#713 duplicates. The reset: - Runs once per device (SharedPreferences flag) - Executes before first push after upgrade - Runs under syncInFlight guard (no concurrent access) - Requires corresponding Rust JNI implementation in aw-sync/src/android.rs Changes: - Add JNI binding for resetStaging(hostname) - Implement one-shot logic with device-specific preference tracking - Integrate into sync pre-operations (push and full-sync paths) - Add unit tests for one-shot behavior - Document Rust-side implementation requirements The reset removes staging files for own device only, preserving peer data. Subsequent push rebuilds staging from clean local source, and desktop pull will recognize re-imported records as duplicates (via #713 fingerprint fix). Closes: ActivityWatch#296 Relates-to: ActivityWatch/aw-server-rust#717 Git-Session-Id: e1f2
|
…ive symbol - Add AWPreferences.isStagingResetDone/setStagingResetDone (the previous getBoolean/setBoolean calls did not exist). - Extract the one-shot flag logic into StagingReset.runOnce, free of Android types, and replace the Mockito tests (no Mockito dependency; the tests asserted nothing) with JVM tests of that logic. - Catch LinkageError: until aw-server-rust ships resetStaging, the native call throws UnsatisfiedLinkError, which escaped both catch (Exception) blocks and would kill the sync executor before its callback cleared syncInFlight. - Run the reset only inside the full-sync path, which is the one that holds syncInFlight (push/pull-all wrappers have no callers and no guard). - Drop STAGING_RESET_IMPLEMENTATION.md from the repo root; the Rust spec lives in ActivityWatch#296. Git-Session-Id: ac9fc99f-bc9b-569f-b59a-2817d7711006
|
@greptileai review |
|
Ready for maintainer review: all three Greptile P1s are fixed in e0bcfec. The re-review on that head scores 5/5, CI is green (Test + E2E), and every thread is resolved. I can't merge this one myself (pull-only access on aw-android), so it's waiting on a maintainer. |
|
CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click. This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted. |
🤖 AI code reviewSafe to merge — 1 P1 disposed (accepted-tradeoff)Updated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
Adds a one-shot staging-db reset for the full-sync path. StagingReset.runOnce is a JVM-free helper that calls a native resetStaging(hostname) and records per-hostname completion in SharedPreferences only on a success response. SyncInterface declares the external resetStaging and invokes resetStagingOnce inside the syncBoth executor task before syncBoth. AWPreferences gains isStagingResetDone/setStagingResetDone keyed by hostname. Adds unit tests for the runOnce flag logic. Not safe to merge — 1 P1 openConfidence 3/5 1 finding · ❌ 1 P1❌ P1 high — The resetStagingOnce function is called inside the syncBoth executor task, but it is not wrapped in a try-catch itself. StagingReset.runOnce catches Exception and LinkageError, but the reset lambda calls resetStaging(hostname), which is a JNI call. If the native library throws any other Error (e.g. an AssertionError from the native side, or a NoClassDefFoundError), it would escape runOnce and propagate up through the executor task. The executor task in performSyncAsync catches Exception, but not Error. An Error escaping would kill the executor thread before the callback clears syncInFlight, leaving syncInFlight true forever and rejecting all future syncs as 'already in flight'. The PR description explicitly mentions UnsatisfiedLinkError being caught, but other LinkageError subclasses are caught, yet other Errors are not. The comment in StagingReset says 'Never throws', but it only catches Exception and LinkageError, not all Throwable. A native crash or an OutOfMemoryError would still escape. This is a real defect: the sync executor can be permanently wedged by an Error from the native reset. How this was verified: Checked StagingReset.kt: catches Exception and LinkageError only. performSyncAsync in SyncInterface.kt catches Exception only (line 345). An Error from resetStaging would propagate out of the executor task, skipping the finally? Actually finally runs, but the callback is not posted, so syncInFlight remains true. The comment in StagingReset claims 'Never throws' but that is not true for Errors other than LinkageError. Consensus: 3/3 passes agreed Files changed (4) — the diff as I read it
Reviewed Maintainer commands
|
Summary
Kotlin half of #296: an automatic one-shot reset of the phone's push staging db, so pre-ActivityWatch/aw-server-rust#713 duplicate copies stop being re-imported by desktops. Item 2 of ActivityWatch/aw-server-rust#717 (item 1, desktop dedupe, merged in ActivityWatch/aw-server-rust#718).
What it does
StagingReset.runOnce(Android-free, unit-tested): skips if this hostname's flag is set; otherwise calls the native reset and sets the flag only on{"success": true}. Failures leave the flag unset, so the reset retries on the next sync. It never throws, includingUnsatisfiedLinkError.SyncInterface: declaresexternal fun resetStaging(hostname), and runs the reset inside the full-sync executor task, after the legacy-folder migration and beforesyncBoth. That path holdssyncInFlight, so the reset cannot race a push writing the same staging db. (The push/pull-all wrappers have no callers and no guard, so they are left alone.)AWPreferences.isStagingResetDone/setStagingResetDone(hostname): the flag is keyed per hostname because staging lives under{sync_dir}/{hostname}/.Dependency: the Rust side is not written yet
resetStagingmust be added toaw-sync/src/android.rs(spec in #296: deletetest.db/-wal/-shmfor the own device dir only, refuse paths outside it). Until a build carries it, the native call throwsUnsatisfiedLinkError. That error is caught and logged, the flag stays unset, and sync carries on, so this PR is safe to merge before the Rust PR. The reset then fires on the first sync after the submodule bump.Verify before closing #296
android-test/android-unlockbuckets are not duplicated. If they are, re-staging reproduces the copies.Relates to #296, ActivityWatch/aw-server-rust#717, ActivityWatch/aw-server-rust#713, #295.