@@ -15,6 +15,7 @@ public enum LocalTransportError: Error, CustomStringConvertible {
1515 case connectionClosed
1616 case messageTooLarge
1717 case alreadyRunning
18+ case mismatchedResponse
1819
1920 public var description : String {
2021 switch self {
@@ -26,6 +27,7 @@ public enum LocalTransportError: Error, CustomStringConvertible {
2627 case . connectionClosed: return " Headless host closed the connection "
2728 case . messageTooLarge: return " Headless host message exceeded the size limit "
2829 case . alreadyRunning: return " Another Headless host is already using the local socket "
30+ case . mismatchedResponse: return " Headless host replied to a different request "
2931 }
3032 }
3133}
@@ -97,7 +99,19 @@ public final class LocalSocketClient {
9799
98100 try writeAll ( try ProtocolCodec . encodeLine ( request) , to: fd)
99101 let responseData = try readLine ( from: fd)
100- return try ProtocolCodec . decodeLine ( CommandResponse . self, from: responseData)
102+ let response = try ProtocolCodec . decodeLine ( CommandResponse . self, from: responseData)
103+ // One request, one response, one connection — so a mismatched id means
104+ // this reply belongs to something else. Correlating by convention was
105+ // enough only while nothing ever got it wrong.
106+ //
107+ // `unknownRequestIdentifier` is the documented exception: the host uses
108+ // it only when it could not read the request at all (peer rejected,
109+ // unreadable frame), and those replies still carry the reason the
110+ // caller needs to see.
111+ guard response. id == request. id || response. id == CommandResponse . unknownRequestIdentifier else {
112+ throw LocalTransportError . mismatchedResponse
113+ }
114+ return response
101115 }
102116}
103117
@@ -118,6 +132,8 @@ public final class LocalSocketServer: @unchecked Sendable {
118132 private let stateLock = NSLock ( )
119133 private var listeningDescriptor : Int32 = - 1
120134 private var running = false
135+ /// Roughly 30 seconds of backed-off retries before the listener gives up.
136+ static let maximumAcceptFailures = 64
121137
122138 public init ( socketPath: String = LocalRuntime . socketURL. path) {
123139 self . socketPath = socketPath
@@ -175,13 +191,27 @@ public final class LocalSocketServer: @unchecked Sendable {
175191 }
176192
177193 private func acceptLoop( handler: @escaping Handler ) {
194+ // A persistent accept() failure — a descriptor limit is the realistic
195+ // one — used to spin this loop at full speed forever. Back off instead,
196+ // and give up rather than pretend to serve a socket we cannot accept
197+ // on: a host that exits is recoverable, a host that burns a core while
198+ // silently refusing every agent is not.
199+ var consecutiveFailures = 0
178200 while isRunning {
179201 let client = systemAccept ( currentDescriptor)
180202 if client < 0 {
181203 if !isRunning { return }
182204 if errno == EINTR { continue }
205+ consecutiveFailures += 1
206+ if consecutiveFailures >= Self . maximumAcceptFailures {
207+ stop ( )
208+ return
209+ }
210+ let backoff = min ( 0.05 * Double( consecutiveFailures) , 1.0 )
211+ Thread . sleep ( forTimeInterval: backoff)
183212 continue
184213 }
214+ consecutiveFailures = 0
185215 clientQueue. async { [ weak self] in
186216 guard let self else { systemClose ( client) ; return }
187217 #if canImport(Darwin)
@@ -195,16 +225,23 @@ public final class LocalSocketServer: @unchecked Sendable {
195225 }
196226
197227 private func handleClient( _ fd: Int32 , handler: Handler ) {
228+ // Echo the request id as soon as it is known so a failure reply is
229+ // still correlated. Only a request the host could not read at all
230+ // falls back to the unknown-id sentinel.
231+ var identifier = CommandResponse . unknownRequestIdentifier
198232 do {
199233 try configureNoSigPipe ( fd: fd)
200234 guard try peerUserID ( fd: fd) == currentUserID ( ) else {
201- let response = CommandResponse . failure ( id: " unknown " , code: " PEER_DENIED " , message: " Socket peer user is not authorized. " )
235+ let response = CommandResponse . failure (
236+ id: identifier, code: " PEER_DENIED " , message: " Socket peer user is not authorized. "
237+ )
202238 try writeAll ( try ProtocolCodec . encodeLine ( response) , to: fd)
203239 return
204240 }
205241 try configureTimeout ( fd: fd, seconds: 5 )
206242 let data = try readLine ( from: fd)
207243 let request = try ProtocolCodec . decodeLine ( CommandRequest . self, from: data)
244+ identifier = request. id
208245 try request. validate ( )
209246 try configureTimeout ( fd: fd, seconds: 125 )
210247 // `shutdown` only signals the host's main loop and does not mutate
@@ -233,7 +270,7 @@ public final class LocalSocketServer: @unchecked Sendable {
233270 try writeAll ( payload, to: fd)
234271 } catch {
235272 let response = CommandResponse . failure (
236- id: " unknown " ,
273+ id: identifier ,
237274 code: " INVALID_REQUEST " ,
238275 message: String ( describing: error)
239276 )
0 commit comments