Skip to content

Commit da1ab90

Browse files
quic: handle stream writer reset error
1 parent 4d27d80 commit da1ab90

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

lib/internal/quic/quic.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2313,6 +2313,7 @@ class QuicStream {
23132313
getQuicSessionState(stream.#inner.session).internalErrorCode;
23142314
handle.resetStream(code);
23152315
if (drainWakeup != null) {
2316+
markPromiseAsHandled(drainWakeup.promise);
23162317
drainWakeup.reject(error);
23172318
drainWakeup = null;
23182319
}
@@ -2508,6 +2509,9 @@ class QuicStream {
25082509
}
25092510
if (error !== undefined) {
25102511
inner.pendingClose.reject(error);
2512+
if (!inner.destroying && typeof inner.onerror === 'function') {
2513+
invokeOnerror(inner.onerror, error);
2514+
}
25112515
} else {
25122516
inner.pendingClose.resolve();
25132517
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Flags: --experimental-quic --no-warnings
2+
3+
// Test: Verify that when a peer resets a stream after writer.endSync()
4+
// is called, the stream.onerror handler fires and no unhandled rejection occurs.
5+
6+
import { hasQuic, skip, mustCall } from '../common/index.mjs';
7+
import * as assert from 'node:assert';
8+
9+
const { rejects, strictEqual } = assert;
10+
11+
if (!hasQuic) {
12+
skip('QUIC is not enabled');
13+
}
14+
15+
const { listen, connect } = await import('../common/quic.mjs');
16+
17+
const serverDone = Promise.withResolvers();
18+
19+
// Listen for unhandled rejections to catch regressions.
20+
process.on('unhandledRejection', (reason) => {
21+
assert.fail(`Unhandled rejection: ${reason}`);
22+
});
23+
24+
const serverEndpoint = await listen(mustCall((serverSession) => {
25+
serverSession.onstream = mustCall(async (stream) => {
26+
// Set up onerror to verify it fires on remote reset
27+
const errorFired = Promise.withResolvers();
28+
stream.onerror = mustCall((error) => {
29+
strictEqual(error.code, 'ERR_QUIC_APPLICATION_ERROR');
30+
strictEqual(error.errorCode, 256n);
31+
errorFired.resolve();
32+
});
33+
34+
stream.sendHeaders({ ':status': '200' });
35+
stream.writer.endSync();
36+
37+
await errorFired.promise;
38+
await rejects(stream.closed);
39+
40+
serverSession.close();
41+
serverDone.resolve();
42+
});
43+
}));
44+
45+
const clientSession = await connect(serverEndpoint.address);
46+
await clientSession.opened;
47+
48+
const stream = await clientSession.createBidirectionalStream();
49+
stream.resetStream(256n);
50+
await rejects(stream.closed);
51+
52+
await serverDone.promise;
53+
await clientSession.close();
54+
await serverEndpoint.close();

0 commit comments

Comments
 (0)