Commit990756e7Recorded3 May 2026Repositorysigil-websocket

Drain buffered WebSocket frames before waiting

Message

ws-receive could await socket readability even when a previous read had already buffered one or more complete frames locally. In async contexts that can deadlock a caller waiting for the next frame, as seen in the Enclave WS smoke after JOIN output arrived in the receive buffer.nnTry decoding the existing connection buffer first, and only await readability when no buffered frame is available.nnVerified via Enclave smoke with local redirects:n- PATH=/home/daviwil/Projects/Code/sigil/sigil/build/dev/bin:/home/daviwil/.nix-profile/bin:/home/daviwil/.sigil/bin:/home/daviwil/.bin:/home/daviwil/.local/bin:/home/daviwil/.npm-global/bin:/home/daviwil/.guix-home/profile/bin:/home/daviwil/.guix-home/profile/sbin:/run/privileged/bin:/home/daviwil/.config/guix/current/bin:/home/daviwil/.guix-home/profile/bin:/home/daviwil/.guix-home/profile/sbin:/home/daviwil/.guix-profile/bin:/run/current-system/profile/bin:/run/current-system/profile/sbin packages/enclave-server/test/smoke-ws.sh

Changed
 src/sigil/websocket/connection.sgl | 41 ++++++++++++++++++++++++++++-------------
 1 file changed, 28 insertions(+), 13 deletions(-)
Diff
src/sigil/websocket/connection.sglmodified
@@ -318,20 +318,35 @@
318
'closed
319
(let ((socket (ws-connection-socket conn)))
320
(let loop ()
321
;; Wait for data (yields to scheduler if in async context)
322
(when (current-scheduler)
323
(await-readable socket))
324
;; Try to read and process
325
(let ((result (receive-message conn)))
+321
;; A prior socket read can contain multiple complete
+322
;; WebSocket frames. Drain that buffer before waiting
+323
;; for more readability; otherwise the async path can
+324
;; block forever even though a frame is already local.
+325
(let ((buffered
+326
(let ((buffer (ws-connection-buffer conn)))
+327
(if (> (bytevector-length buffer) 0)
+328
(try-decode-message conn)
+329
'need-more))))
330
(cond
327
;; Got a complete message or connection closed
328
((or (ws-message? result) (eq? result 'closed))
329
result)
330
;; Need more data - loop and wait again
331
((eq? result 'need-more)
332
(loop))
333
;; Error
334
(else #f)))))))
+331
((or (ws-message? buffered) (eq? buffered 'closed))
+332
buffered)
+333
((and buffered (not (eq? buffered 'need-more)))
+334
#f)
+335
(else
+336
;; Wait for data (yields to scheduler if in async context)
+337
(when (current-scheduler)
+338
(await-readable socket))
+339
;; Try to read and process
+340
(let ((result (receive-message conn)))
+341
(cond
+342
;; Got a complete message or connection closed
+343
((or (ws-message? result) (eq? result 'closed))
+344
result)
+345
;; Need more data - loop and wait again
+346
((eq? result 'need-more)
+347
(loop))
+348
;; Error
+349
(else #f))))))))))
350
351
;;; Internal: Read data and try to decode a message
352
;;; Returns ws-message, 'closed, 'need-more, or #f on error