Race condition in `onDatagramReceived` causes uncaught exception when session closes
## 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 条评论