Skip to content

Fix event persistence race conditions on session resume#76

Merged
olsonjeffery merged 2 commits into
mainfrom
task/18-resume-session-barfs-when-resu
Jul 25, 2026
Merged

Fix event persistence race conditions on session resume#76
olsonjeffery merged 2 commits into
mainfrom
task/18-resume-session-barfs-when-resu

Conversation

@olsonjeffery

Copy link
Copy Markdown
Owner

Summary

Fixes two interdependent race conditions that cause UNIQUE constraint failed: messages.project_key, messages.session_id, messages.seq errors when resuming conversations, making conversations inert.

Root Causes Fixed

  1. Reader task not cancelled on resumesubscribe_and_spawn() created a new event subscription and reader task on every call but never cancelled the old one. Both ran concurrently, both trying to persist events for the same session, causing unique constraint violations. Also caused MaxListenersExceededWarning from stale SSE subscriptions.

  2. next_seq() not atomicpersist_event() used a separate SELECT MAX(seq) query then INSERT, creating a race window where two calls could get the same seq value.

Changes

Phase 1: Rebase (no-op)

  • Rebased worktree onto latest origin/main

Phase 2: Atomic seq generation (src/services/transcript.rs)

  • Removed next_seq() function
  • Inlined seq computation as a SQL subquery in VALUES, making seq generation and insertion atomic

Phase 3: Reader task cancellation (src/providers/opencode_sdk_provider.rs)

  • Added reader_cancellation: Mutex<Option<Arc<tokio::sync::Notify>>> field
  • Added cancel_inflight() helper that signals the old reader task to stop
  • Added reader_stop.notified() branch to reader task's tokio::select!
  • Updated abort_turn() and shutdown() to use cancel_inflight()

Phase 4: Surface persistence errors (conversations.rs, agent_runs.rs)

  • Changed broadcast tasks from tracing::warn! + continue to tracing::error! + broadcast error event via WebSocket + break loop

Testing

  • 441/441 lib tests pass
  • New test_persist_event_concurrent: 20 concurrent calls produce unique seq values (no violations)
  • New test_reader_task_cancellation: Notify signal correctly cancels reader task
  • cargo fmt and cargo clippy clean

- Make next_seq() atomic by inlining MAX(seq) subquery into INSERT
  VALUES, eliminating the two-round-trip race that caused UNIQUE
  constraint failures
- Add reader_cancellation (Arc<Notify>) field and cancel_inflight()
  helper to OpenCodeSdkProvider for per-task reader cancellation
- Wire cancel_inflight() into subscribe_and_spawn(), abort_turn(),
  and shutdown() so stale reader tasks are stopped before new ones
  are spawned
- Surface persistence errors as ProviderEvent::Error through the
  WebSocket channel in conversations.rs and agent_runs.rs broadcast
  tasks instead of silently logging and continuing
- Add test_persist_event_concurrent: 20 concurrent persist_event
  calls verify unique, monotonically increasing seq values
- Add test_reader_task_cancellation: verify Notify signal exits
  reader task
- Replace  with
  (opencode_sdk_provider.rs:383)
- Remove redundant inner clones in agent_runs.rs broadcast task by
  changing  to  in the inner block
- Remove unnecessary  clone in cleanup section
@olsonjeffery
olsonjeffery merged commit 4ee92a4 into main Jul 25, 2026
3 checks passed
@olsonjeffery
olsonjeffery deleted the task/18-resume-session-barfs-when-resu branch July 25, 2026 17:52
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