Skip to content

fix(aw-sync): use atomic counter in test tmp_db to fix parallel-test path collision - #655

Open
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/flaky-sync-roundtrip-db-collision
Open

fix(aw-sync): use atomic counter in test tmp_db to fix parallel-test path collision#655
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/flaky-sync-roundtrip-db-collision

Conversation

@TimeToBuildBob

@TimeToBuildBob TimeToBuildBob commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Problem

test_push_does_not_reexport_synced_buckets and test_own_data_does_not_return_via_peer both call round_trip(), which calls datastore("a-local"), datastore("a-export"), etc. with the same names. Rust's test runner executes these tests in parallel.

tmp_db() previously built its path from:

aw-sync-roundtrip-<pid>-<name>-<nanoseconds>.db

On macOS, SystemTime::now() can return the same nanosecond value for two calls that happen within the same clock tick (the OS timer resolution can be coarser than 1 ns). When that happens, both parallel tests try to open the same SQLite file. One test runs the v1→v2 migration (adding the data column to buckets) and the other hits it with the column already present, panicking:

Failed to upgrade database when adding data field to buckets:
  SqliteFailure(..., Some("duplicate column name: data"))

This caused an intermittent Build CI failure — first seen when the tests were added in #648, and again on master after #653.

Fix

Add a process-wide AtomicU64 counter to the path, alongside the existing timestamp. The two components cover different collision dimensions and neither is sufficient alone:

Component Unique across Fails at
Counter calls within one process separate runs (resets to 0)
Timestamp separate runs calls within one clock tick

The counter fixes the parallel-test collision above. The timestamp is retained because the counter resets to 0 in each new process, so a later run that draws a recycled PID could otherwise reconstruct a path left behind in temp_dir() by an earlier run — the cross-run case Greptile flagged on the first revision of this PR.

static DB_COUNTER: AtomicU64 = AtomicU64::new(0);

fn tmp_db(name: &str) -> PathBuf {
    let mut p = std::env::temp_dir();
    let ts = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    p.push(format!(
        "aw-sync-roundtrip-{}-{}-{}-{}.db",
        std::process::id(),
        name,
        DB_COUNTER.fetch_add(1, Ordering::Relaxed),
        ts,
    ));
    p
}

tempfile::TempDir would cover both dimensions unconditionally and self-clean; happy to switch to it if the maintainers prefer that over the added dependency.

No functional change to the tests themselves.

…est path collisions

Tests in sync_roundtrip run in parallel. Both test_push_does_not_reexport_synced_buckets
and test_own_data_does_not_return_via_peer call round_trip(), which calls datastore("a-local")
etc. with the same names. The old tmp_db() used SystemTime nanoseconds, which can collide
on macOS (lower clock resolution) — two parallel tests then share the same SQLite file and
race on migrations, producing "duplicate column name: data".

Replace the timestamp with a process-wide AtomicU64 counter. Each call to tmp_db() gets
a unique value regardless of when it runs, eliminating the race.
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

The PR makes temporary SQLite paths unique both within a parallel test run and across separate runs.

  • Adds a process-wide atomic counter to distinguish concurrent datastore creation.
  • Retains a timestamp component to prevent stale-file reuse when a later process receives the same PID.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the timestamp preserves cross-run uniqueness while the atomic counter guarantees uniqueness among parallel calls within one process.

Important Files Changed

Filename Overview
aw-sync/tests/sync_roundtrip.rs Combines an atomic counter with the existing timestamp and process ID, resolving both parallel path collisions and the previously reported cross-run path-reuse issue.

Reviews (2): Last reviewed commit: "fix(aw-sync): add timestamp to tmp_db pa..." | Re-trigger Greptile

// clock tick (seen on macOS where SystemTime resolution can be coarser than
// nanoseconds, causing path collisions and SQLite migration races).
static DB_COUNTER: AtomicU64 = AtomicU64::new(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Counter paths repeat across runs

If a previous test run leaves its SQLite files in the shared temporary directory and a later invocation receives the same process ID, DB_COUNTER restarts at zero and reuses those paths, causing tests to open stale buckets and events instead of fresh databases.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI is all green (format, clippy, ubuntu/mac/windows/android). Greptile gave this a 4/5 and flagged one non-blocking concern: the process-global AtomicU64 counter resets to 0 on each invocation, so PID reuse across separate test runs could theoretically land on stale SQLite paths. The risk is very low in practice (test databases are small and the PID-reuse scenario is rare in CI), but switching to tempfile::TempDir would make paths unconditionally unique if that feels worth the dep addition. Happy to update if the maintainers prefer it.

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.31%. Comparing base (656f3c9) to head (d1a1a05).
⚠️ Report is 89 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #655      +/-   ##
==========================================
+ Coverage   70.81%   78.31%   +7.49%     
==========================================
  Files          51       65      +14     
  Lines        2916     5409    +2493     
==========================================
+ Hits         2065     4236    +2171     
- Misses        851     1173     +322     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Counter resets to 0 on each process invocation; combining it with a
nanosecond timestamp means paths are unique across separate test runs
even when the PID is reused, addressing the Greptile P2 concern.

The counter still provides the within-run parallel-test guarantee.
No new dependencies required.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

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.

TimeToBuildBob added a commit to TimeToBuildBob/aw-server-rust that referenced this pull request Aug 28, 2026
…mestamp)

The counter alone is unique within a process but not across runs: the
tests never remove their temp dbs, so a PID reuse restarts the counter at
0 and can reopen a leftover file. Keeping the timestamp covers that.

Makes the file byte-identical to ActivityWatch#655, which
fixes the same macOS flake on master, so the two land in either order
without a conflict.
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