From 6becb2a2086f088da2f4bd8153b19d1e43dba36d Mon Sep 17 00:00:00 2001 From: Etan Joseph Heyman Date: Wed, 18 Mar 2026 02:18:24 +0200 Subject: [PATCH] fix: prevent zombie client re-insertion after disconnectClient After sendResponse() calls disconnectClient(fd:) to drop a stalled client, readFromClient() would write the ClientState copy back into the clients dictionary at line 178, re-inserting a zombie entry with a cancelled DispatchSource. Now checks clients[fd] after each sendResponse() and bails if the client was removed. Also: write() returning 0 now properly disconnects instead of silently breaking the write loop. Found by code review agent on PR #87. Co-Authored-By: Claude Opus 4.6 (1M context) --- brain-bar/Sources/BrainBar/BrainBarServer.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/brain-bar/Sources/BrainBar/BrainBarServer.swift b/brain-bar/Sources/BrainBar/BrainBarServer.swift index 0d019e6c..e3cdbb73 100644 --- a/brain-bar/Sources/BrainBar/BrainBarServer.swift +++ b/brain-bar/Sources/BrainBar/BrainBarServer.swift @@ -173,6 +173,8 @@ final class BrainBarServer: @unchecked Sendable { if !response.isEmpty { sendResponse(fd: fd, response: response) } + // sendResponse may have called disconnectClient — stop processing + if clients[fd] == nil { return } } clients[fd] = state @@ -200,7 +202,11 @@ final class BrainBarServer: @unchecked Sendable { disconnectClient(fd: fd) return } - if n == 0 { break } // EOF + if n == 0 { + NSLog("[BrainBar] Write returned 0 on fd %d — peer closed", fd) + disconnectClient(fd: fd) + return + } totalWritten += n eagainRetries = 0 // reset on successful partial write }