feat(audience): add DiskStore and EventQueue for persistent event batching (SDK-127)#690
Conversation
a3a7892 to
427b8d6
Compare
…ching (SDK-127) Implements file-per-event disk persistence under imtbl_audience/queue/ with atomic writes, stale-event pruning (30d), and crash recovery. EventQueue wraps a ConcurrentQueue with a background drain thread that flushes to DiskStore on a time interval or when FlushSize is reached; Shutdown/Dispose guarantee a final flush. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
427b8d6 to
b9ebac5
Compare
Addresses cursor[bot] review comment on EventQueue.cs:29. - Mark _disposed as volatile so the shutdown signal is visible across threads. On weakly-ordered architectures (ARM — Apple Silicon, etc.) the C# memory model does not guarantee a non-volatile write is eventually observed by other threads without a fence. - Reorder Shutdown: set _disposed=true first, before canceling the drain thread. The shutdown flag being the first observable state change is a safer ordering and makes the lifecycle easier to reason about. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses cursor[bot] review comment on EventQueue.cs:67. The method blocks synchronously and returns void — the Async suffix is misleading in C# where it conventionally signals a Task-returning, awaitable method. Renaming to FlushSync makes the blocking semantics explicit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ececaa8 to
14b57a1
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 14b57a1. Configure here.
| internal EventQueue(DiskStore store, int flushIntervalSeconds, int flushSize) | ||
| { | ||
| _store = store ?? throw new ArgumentNullException(nameof(store)); | ||
| _flushIntervalMs = Math.Max(1, flushIntervalSeconds) * 1000; |
There was a problem hiding this comment.
Integer overflow in flush interval kills drain thread
Medium Severity
Math.Max(1, flushIntervalSeconds) * 1000 performs unchecked int multiplication that silently overflows for flushIntervalSeconds values ≥ 2,147,484 (~24.8 days), producing a negative _flushIntervalMs. When the drain thread calls _flushGate.Wait(_flushIntervalMs) with that negative value, it throws ArgumentOutOfRangeException (only -1 is valid for negative). Since DrainLoop has no try-catch, the background thread dies silently and events are never persisted to disk. A user setting Int32.MaxValue to disable interval flushing would trigger this immediately.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 14b57a1. Configure here.


Summary
DiskStore(Runtime/Transport/DiskStore.cs): file-per-event persistent store writing{ticks}_{uuid}.jsonfiles atomically toimtbl_audience/queue/; supportsReadBatch(oldest-first, max 100, stale-event pruning after 30 days),Delete, andCount; crash-safe (survives restarts by scanning the queue directory on init)EventQueue(Runtime/Transport/EventQueue.cs): wrapsConcurrentQueue<string>with a background drain thread; flushes toDiskStoreeveryFlushIntervalSecondsor whenFlushSizeevents accumulate;FlushAsync()for on-demand drain;Shutdown()/Dispose()for clean exit with final flushDiskStoreTestsandEventQueueTestscovering write, batch read, stale pruning, crash recovery, size-triggered flush, interval flush, shutdown, and post-shutdown enqueueTest plan
DiskStoreTests: write creates file, contents match JSON, batch returns oldest first, max size respected, clamped toMaxBatchSize, stale files excluded and deleted,Deleteremoves files,Counttracks file count, empty/zero cases, crash recoveryEventQueueTests: enqueue +FlushAsyncpersists, multiple events,FlushSizeauto-drain,Shutdownflushes remainder, double-shutdown safe, enqueue-after-shutdown ignored, interval auto-flush,Disposeflushes🤖 Generated with Claude Code
Note
Medium Risk
Introduces new background-thread and on-disk persistence behavior that can affect event delivery reliability and performance (timing, race conditions, and file I/O edge cases), though changes are additive and covered by tests.
Overview
Adds a disk-backed batching layer to the Audience SDK by introducing
DiskStore(file-per-event persistence underimtbl_audience/queue/, batched reads capped toConstants.MaxBatchSize, and automatic pruning of events older thanConstants.StaleEventDays) andEventQueue(thread-safe in-memory enqueue with a background drain thread that flushes by size threshold or time interval, plusShutdown/Disposefor final flush).Includes new NUnit runtime tests covering atomic writes, ordering and batch sizing, stale-file cleanup and crash recovery for
DiskStore, plus flush triggers, interval draining, and shutdown/dispose behavior forEventQueue.Reviewed by Cursor Bugbot for commit 14b57a1. Bugbot is set up for automated code reviews on this repo. Configure here.