Fix have_consumers staying true after an idle consumer disconnects (#267) - #291
Conversation
…ccn#267) The transfer thread only noticed a disconnected peer after a failed write. Outlets that were not pushing never unregisted the queue. Fix: watch the TCP socket for peer close so the idle outlet unregisters the consumer without waiting for a push
|
The idle-disconnect test passes locally, but I found a concurrency issue that needs addressing before merge. In Please use a separate cancellation/wakeup mechanism that does not enqueue from a second producer, or serialize the wakeup with all normal pushes. The current idle-only regression does not cover this; please also test disconnects concurrent with active sample production, preferably under ThreadSanitizer. Validation: built the PR on macOS and ran the new |
|
Merged current I support fixing idle disconnect detection: a marker outlet should stop reporting a consumer that has already disconnected, even if no samples are pushed afterward. The pending asynchronous read is a reasonable event-driven approach, but I recommend revising the cleanup before merging:
#299 makes inlet shutdown deterministic, but the outlet still needs to observe the disconnect. Also, this fix removes stale consumers; it cannot guarantee that a consumer satisfying Validation of this merge: macOS universal build passed; |
|
Fixed. Before the fix I was able to reproduce the race you described under ThreadSanitizer. It does reproduce once a test pushes concurrently with the disconnect on macOS arm64, AppleClang21, Debug, Both threads are in To confirm the fix rather than just fail to see the race, I built the pre-fix commit and this Reproduce on 246d147: cmake -S . -B build-tsan -DCMAKE_BUILD_TYPE=Debug -DLSL_UNITTESTS=ON \
-DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" \
-DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=thread"
cmake --build build-tsan --parallel
for i in $(seq 1 500); do
TSAN_OPTIONS="history_size=7 halt_on_error=0" \
./build-tsan/testing/lsl_test_exported \
"have_consumers becomes false after disconnect during push"
doneI initially tried a Added three new tests:
Synchronous idle disconnects are in #303. I'm not planning to pick that one up, but tell me if you'd rather it land here. TSan also flags a pre-existing |
cboulay
left a comment
There was a problem hiding this comment.
The producer-race blocker is addressed: disconnect wakeups now use the same send-buffer mutex as normal pushes, retaining the queue for the operation. Early subscription and watcher cleanup are preserved. I also added exception-safe cleanup to the active-production regression test in 4a3e886.
Validation of 4a3e886:
- Universal macOS Debug build passed; the focused [outlet],[open],[reopen],[sync] suite passed 1,710 assertions in 20 cases on both arm64 and x86_64.
- Injected assertion and exception failures confirmed the test stops and joins its pusher during unwinding.
- With Xcode 27 / Apple Clang 21 on native arm64, the same focused suite passed under ThreadSanitizer without warnings.
- 100 repeated disconnect-during-push runs passed all functional assertions, with no reports of the outlet queue producer race. Fifteen runs reported the separate sample-recycling warning now tracked in #305 and consequently exited unsuccessfully under TSan; this is not a claim of a fully TSan-clean stress run.
- All 14 PR CI checks passed.
Synchronous idle disconnect detection remains scoped to #303. Ready to merge this asynchronous-outlet fix.
Fixes #267
The transfer thread only noticed a disconnected peer after a failed write. Outlets that were not pushing never unregistered the queue. Watch the TCP socket for peer close so an idle outlet unregisters the consumer without waiting for a push.