Skip to content

fix(tracker): serve /tracker/* from tick-independent snapshot - #50

Open
addadi wants to merge 1 commit into
nullclaw:mainfrom
addadi:fix/tracker-snapshot-endpoints
Open

fix(tracker): serve /tracker/* from tick-independent snapshot#50
addadi wants to merge 1 commit into
nullclaw:mainfrom
addadi:fix/tracker-snapshot-endpoints

Conversation

@addadi

@addadi addadi commented Aug 22, 2026

Copy link
Copy Markdown

Fixes #41

tick() holds state.mutex across synchronous dispatch and NullTickets HTTP calls (heartbeat, claim, reconcile), so /tracker/status, /tracker/tasks, /tracker/stats, and /tracker/task/{id} all blocked for the duration of the tick — easily >10s whenever a dispatch was in flight.

TrackerState now publishes a TrackerSnapshot (running-task JSON array, counts, task_id→detail map) at the end of every tick and once at run() startup, under a dedicated snapshot_mutex held only for pointer swaps — never across I/O. Handlers copy the view under that mutex (never state.mutex) and serve distinguishable errors: 404 tracker-not-configured, 503 no-snapshot-yet, 500 OOM. Wire format is unchanged (formatRunningTask moved to tracker.zig as runningTaskJson). The dispatch path is untouched.

Test proves the decoupling property: copySnapshotView succeeds while the tick mutex is held by another caller. Full suite green (zig build test, 357/357).

tick() holds state.mutex across synchronous dispatch and NullTickets
HTTP calls (heartbeat, claim, reconcile), so /tracker/status, /tasks,
/stats, and /task/{id} all blocked for the duration of the tick —
easily >10s whenever a dispatch was in flight.

TrackerState now publishes a TrackerSnapshot (running-task JSON array,
counts, task_id→detail map) at the end of every tick and once at run()
startup, under a dedicated snapshot_mutex held only for pointer swaps —
never across I/O. Handlers copy the view under that mutex (never
state.mutex) and serve distinguishable errors: 404 tracker-not-
configured, 503 no-snapshot-yet, 500 OOM. Wire format is unchanged
(formatRunningTask moved to tracker.zig as runningTaskJson). The
dispatch path is untouched.

Test proves the decoupling property: copySnapshotView succeeds while
the tick mutex is held by another caller.

Fixes nullclaw#41
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.

BUG: /tracker/* endpoints block for the duration of tick() network I/O

1 participant