Skip to content

refactor(65): deterministic SyncScheduler timing — kill the CI flake#46

Merged
ChrisPelatari merged 5 commits into
developfrom
claude/65-scheduler-flake
Jun 12, 2026
Merged

refactor(65): deterministic SyncScheduler timing — kill the CI flake#46
ChrisPelatari merged 5 commits into
developfrom
claude/65-scheduler-flake

Conversation

@ChrisPelatari

Copy link
Copy Markdown
Member

Summary

  • Inject any Clock<Duration> into SyncScheduler (default ContinuousClock — production unchanged); loop now calls clock.sleep(for:) instead of Task.sleep(for:).
  • Add ManualClock test helper: suspends callers on CheckedContinuations, exposes waitForSleeper() (blocks until the loop actually enters sleep) + advance(by:) (wakes all due sleepers, yields 8× for MainActor work); NSLock-based, zero external dependencies.
  • Rewrite SyncSchedulerTests to use ManualClockwaitForSleeper() closes the TOCTOU window where advance(by:) ran before the loop task had even called sleep(), which was the root cause of the flake.
  • Rewrite SyncSpy.makeSlow() to block on a CheckedContinuation + releaseAll() instead of a 60 s real wall-clock sleep; cleanup is now instantaneous.

Root cause of the flake

SyncScheduler.startLoop() creates an unstructured Task {} (queued but not started). The test immediately called await Task.sleep(for: .milliseconds(200)) hoping to give it time. Under parallel load (Xcode spawns 2 clone processes per suite; Swift Testing randomises ordering), the loop task's continuation was starved on the cooperative thread pool — it never ran during the 200 ms window, so spy.callCount == 0. The fix is not "wait longer" but "don't start checking until we know the loop is blocked in sleep."

Test plan

  • SyncSchedulerTests — 10 consecutive passes on FK-Lane-B (E07B654D) with parallel execution enabled; each run: 8 tests × 2 clones = 16 cases, all green.
  • Full unit suite on FK-Lane-B — SyncSchedulerTests all pass; pre-existing failures in KeychainHelperTests / FizzySyncEngine* are unrelated and present on develop.
  • macOS build (CODE_SIGNING_ALLOWED=NO) — ** BUILD SUCCEEDED **, zero warnings.
  • Elf gate (repo.sh check) — PASS.

Mission: #28 · Status log: #65

🤖 Generated with Claude Code

ChrisPelatari and others added 2 commits June 12, 2026 05:17
Inject `any Clock<Duration>` (default `ContinuousClock`) so tests can
drive time forward without relying on real wall-clock sleeps.  Production
semantics unchanged — 300 s default interval, same sleep-first loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add ManualClock — a zero-dependency test Clock<Duration> that suspends
callers on continuations until the test calls advance(by:).  The key fix
is waitForSleeper(): tests now await the moment the scheduler loop has
actually suspended in clock.sleep(until:) before advancing time, closing
the TOCTOU window where advance() ran before the loop task started.

SyncSpy.makeSlow() now uses a proper CheckedContinuation rather than a
60-second real sleep, so cleanup is immediate.

Verified: 10 consecutive passes on FK-Lane-B (E07B654D) with parallel
execution enabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChrisPelatari added a commit that referenced this pull request Jun 12, 2026
CI runner stalls pushed a ~2s-budget completion to 5.96s. Deterministic
ManualClock rewrite follows after #46 lands (mission task #32).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChrisPelatari and others added 3 commits June 12, 2026 05:48
…r-flake

# Conflicts:
#	FenixKanban.xcodeproj/project.pbxproj
#	FenixKanbanTests/Features/Sync/SyncSchedulerTests.swift
…markers

A prior aborted scripted resolution had git-added the conflicted file,
so checkout --ours pulled markers from the index. File restored from
the branch tip (the ManualClock rewrite), suite verified green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r-flake

# Conflicts:
#	FenixKanban.xcodeproj/project.pbxproj
@ChrisPelatari ChrisPelatari merged commit 1d33dcd into develop Jun 12, 2026
1 check passed
@ChrisPelatari ChrisPelatari deleted the claude/65-scheduler-flake branch June 12, 2026 11:26
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