fix(sink): stop() waits for in-flight signals before returning #7

Merged
KofTwentyTwo merged 1 commits from feature/sink-stop-drains-in-flight into develop 2026-10-12 22:50:52 +00:00
KofTwentyTwo commented 2026-10-12 22:48:56 +00:00 (Migrated from github.com)

The release/0.1 build (CircleCI 12) failed in SinkAndScheduleTest.testQueueAndAdapter:74 (expected RESOLVED, got OPEN), although the same commit passed on develop.

Root cause (product, not only the test): QbitAlertsSink.stop() called flush() and then interrupted the drain thread. When the drain thread had already polled a signal and was still writing it, flush() saw an empty queue and stop() returned before that write finished. The interrupt could also land in the middle of the write. A signal submitted just before shutdown (here, a resolve) could be lost.

Fix: stop() sets a stopping flag that ends the drain loop, joins the thread (up to 10 s; it interrupts only if the thread doesn't finish), then flushes whatever is still queued. The drain poll drops from 1 s to 200 ms so stop() returns promptly. A restart resets the flag.

Test: testStopWaitsForInFlightSignals raises then resolves 25 alerts, calling stop() between them, and asserts all 25 are RESOLVED. It fails on the old code (3 of 4 runs) and passes on the fix (3 of 3). The full mvn verify passes with all gates.

Unblocks qbit-alerts 0.1.0-RC.1.

🤖 Generated with Claude Code

The release/0.1 build ([CircleCI 12](https://circleci.com/gh/QRun-IO/qbit-alerts/12)) failed in `SinkAndScheduleTest.testQueueAndAdapter:74` (expected RESOLVED, got OPEN), although the same commit passed on develop. **Root cause (product, not only the test):** `QbitAlertsSink.stop()` called `flush()` and then interrupted the drain thread. When the drain thread had already polled a signal and was still writing it, `flush()` saw an empty queue and `stop()` returned before that write finished. The interrupt could also land in the middle of the write. A signal submitted just before shutdown (here, a resolve) could be lost. **Fix:** `stop()` sets a stopping flag that ends the drain loop, joins the thread (up to 10 s; it interrupts only if the thread doesn't finish), then flushes whatever is still queued. The drain poll drops from 1 s to 200 ms so `stop()` returns promptly. A restart resets the flag. **Test:** `testStopWaitsForInFlightSignals` raises then resolves 25 alerts, calling `stop()` between them, and asserts all 25 are RESOLVED. It fails on the old code (3 of 4 runs) and passes on the fix (3 of 3). The full `mvn verify` passes with all gates. Unblocks qbit-alerts 0.1.0-RC.1. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.