ITADN

Race condition in `onDatagramReceived` causes uncaught exception when session closes

#430Openiamarkdev 创建于 2025-12-05
I
iamarkdevcommented
## Race condition in `onDatagramReceived` causes uncaught exception when session closes ### Description There's a race condition in `HttpWTSession.onDatagramReceived` that throws an uncaught `ERR_INVALID_STATE: ReadableStream is already closed` exception when a UDP datagram arrives after the session's readable stream controller has been closed. ### Environment - Node.js: v24.x - `@fails-components/webtransport`: latest - Tested OS: Linux & macOS ### Error ``` TypeError [ERR_INVALID_STATE]: Invalid state: ReadableStream is already closed at ReadableByteStreamController.enqueue (node:internal/webstreams/readablestream:1183:13) at HttpWTSession.onDatagramReceived (node_modules/@fails-components/webtransport/lib/session.js:706:9) ``` ### Root Cause In `lib/session.js`, the `onDatagramReceived` method unconditionally calls `enqueue()` without checking if the stream/session is closed: ```javascript // Line 704-706 in session.js ;( this.incomDatagramController_ || this.incomDatagramControllerBytes_ ).enqueue(new Uint8Array(args.datagram)) ``` Meanwhile, `onSessionClosed` closes the controller: ```javascript // Line 584-588 in session.js ;( this.incomDatagramController_ || this.incomDatagramControllerBytes_ ).close() this.state = 'closed' ``` The race condition occurs when: 1. A UDP datagram is in flight 2. The session closes and `onSessionClosed` runs, closing the controller 3. The datagram arrives and `onDatagramReceived` is called 4. `enqueue()` throws because the controller is already closed ### Suggested Fix Add a guard in `onDatagramReceived` to check if the session is closed before enqueuing: ```javascript onDatagramReceived(args) { // Add this check if (this.state === 'closed') return; // ... existing code ... ;( this.incomDatagramController_ || this.incomDatagramControllerBytes_ ).enqueue(new Uint8Array(args.datagram)) } ``` Alternatively, wrap the enqueue in a try-catch since this is an expected race condition with UDP: ```javascript try { ;( this.incomDatagramController_ || this.incomDatagramControllerBytes_ ).enqueue(new Uint8Array(args.datagram)) } catch { // Stream closed, datagram arrived too late - expected with UDP } ``` ### Reproduction This occurs under load when clients disconnect while datagrams are in flight. It's intermittent due to the timing-dependent nature of the race condition.
1 条评论