Skip to content

Commit cbb89d2

Browse files
committed
fix OSX hang: selectRead-bounded accept (accept ignores SO_RCVTIMEO on macOS)
1 parent 812e5ef commit cbb89d2

1 file changed

Lines changed: 26 additions & 14 deletions

File tree

‎tests/stream/test_streaming_sse.nim‎

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import std/[atomics, json, jsonutils, net, os, sequtils, strutils,
1313
unittest]
14+
from std/nativesockets import selectRead
1415
from std/times import epochTime
1516
when defined(posix):
1617
from std/posix import Timeval, Time, Suseconds, SockLen, SOL_SOCKET,
@@ -196,13 +197,34 @@ proc setSocketTimeoutMs(sock: Socket; ms: int) =
196197
proc acceptWithinDeadline(server: SseServer; client: var Socket): bool =
197198
## Accept with a deadline. Returns false when no client dialed in time,
198199
## letting serve threads (and their joiners) terminate instead of hanging
199-
## on an accept that will never come.
200+
## on an accept that will never come. Waits via selectRead, not
201+
## SO_RCVTIMEO on the listening socket: accept(2) ignores that option on
202+
## macOS, so a timeout-wrapped accept blocks forever there and the
203+
## joiner hangs (observed as a deterministic 300s CI kill on macos-14).
200204
while epochTime() < server.acceptDeadline:
205+
var fds = @[server.socket.getFd]
201206
try:
207+
if selectRead(fds, 200) == 0:
208+
continue # No pending connection yet: re-check the deadline.
202209
server.socket.accept(client)
203210
return true
204211
except OSError:
205-
discard # SO_RCVTIMEO wake: loop and re-check the deadline.
212+
discard
213+
false
214+
215+
proc acceptUntilDone(server: SseServer; client: var Socket): bool =
216+
## acceptWithinDeadline, plus a stop flag the caller flips before
217+
## joinThread: a serve loop that would otherwise park in accept until
218+
## the deadline joins within one 200ms selectRead window.
219+
while not server.done.load(moAcquire) and epochTime() < server.acceptDeadline:
220+
var fds = @[server.socket.getFd]
221+
try:
222+
if selectRead(fds, 200) == 0:
223+
continue
224+
server.socket.accept(client)
225+
return true
226+
except OSError:
227+
discard
206228
false
207229

208230
proc newSseServer(response: string): SseServer =
@@ -279,25 +301,16 @@ proc logDiag(msg: string) =
279301
stderr.writeLine("[diag ", epochTime().formatFloat(ffDecimal, 3), "] ", msg)
280302

281303
proc serveRetryable(server: SseServer) {.thread.} =
282-
logDiag "retryable server up port " & $server.port.uint16
283304
## Like serveThread, but serves every connection until the test flips
284305
## `server.done` or the accept deadline passes. The transport re-dials
285306
## once when a first attempt dies on a socket error (the RST race
286307
## documented at drainRequestBody); against a one-shot server that
287308
## retry then waits out the whole quiet window for a head the dead
288309
## server can never send — the 300s CI watchdog kills. Serving the
289310
## re-dial turns that flake into a slightly slower green run.
290-
var wakes = 0
291-
while not server.done.load(moAcquire) and epochTime() < server.acceptDeadline:
311+
while true:
292312
var client: Socket
293-
try:
294-
server.socket.accept(client)
295-
except OSError:
296-
inc wakes
297-
if wakes <= 3 or wakes mod 100 == 0:
298-
logDiag "accept wake " & $wakes
299-
continue # 200ms SO_RCVTIMEO wake: re-check done and the deadline.
300-
logDiag "accepted conn " & $wakes
313+
if not server.acceptUntilDone(client): return
301314
let contentLength = client.readRequestHead()
302315
let body = server.response
303316
let resp = "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n"
@@ -307,7 +320,6 @@ proc serveRetryable(server: SseServer) {.thread.} =
307320
client.send("0\r\n\r\n")
308321
client.drainRequestBody(contentLength)
309322
client.close()
310-
logDiag "served conn " & $wakes
311323

312324
proc url(server: SseServer): string =
313325
# Bare endpoint like production provider urls; the transport appends

0 commit comments

Comments
 (0)