Drain buffered WebSocket frames before waiting
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
src/sigil/websocket/connection.sgl | 41 ++++++++++++++++++++++++++++-------------
1 file changed, 28 insertions(+), 13 deletions(-)src/sigil/websocket/connection.sglmodified
'closed (let ((socket (ws-connection-socket conn))) (let loop () ;; Wait for data (yields to scheduler if in async context) (when (current-scheduler) (await-readable socket)) ;; Try to read and process (let ((result (receive-message conn))) ;; A prior socket read can contain multiple complete ;; WebSocket frames. Drain that buffer before waiting ;; for more readability; otherwise the async path can ;; block forever even though a frame is already local. (let ((buffered (let ((buffer (ws-connection-buffer conn))) (if (> (bytevector-length buffer) 0) (try-decode-message conn) 'need-more)))) (cond ;; Got a complete message or connection closed ((or (ws-message? result) (eq? result 'closed)) result) ;; Need more data - loop and wait again ((eq? result 'need-more) (loop)) ;; Error (else #f))))))) ((or (ws-message? buffered) (eq? buffered 'closed)) buffered) ((and buffered (not (eq? buffered 'need-more))) #f) (else ;; Wait for data (yields to scheduler if in async context) (when (current-scheduler) (await-readable socket)) ;; Try to read and process (let ((result (receive-message conn))) (cond ;; Got a complete message or connection closed ((or (ws-message? result) (eq? result 'closed)) result) ;; Need more data - loop and wait again ((eq? result 'need-more) (loop)) ;; Error (else #f)))))))))) ;;; Internal: Read data and try to decode a message ;;; Returns ws-message, 'closed, 'need-more, or #f on error