Skip to content

Commit 1d9905f

Browse files
committed
fix: fixed corner case where zombie streams are created after destruction
1 parent 54e4b90 commit 1d9905f

File tree

6 files changed

+115
-30
lines changed

6 files changed

+115
-30
lines changed

Cargo.lock

Lines changed: 2 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/QUICConnection.ts

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,30 +1029,47 @@ class QUICConnection {
10291029
);
10301030
continue;
10311031
}
1032-
1033-
quicStream = QUICStream.createQUICStream({
1034-
initiated: 'peer',
1035-
streamId,
1036-
config: this.config,
1037-
connection: this,
1038-
codeToReason: this.codeToReason,
1039-
reasonToCode: this.reasonToCode,
1040-
logger: this.logger.getChild(`${QUICStream.name} ${streamId}`),
1041-
});
1042-
this.streamMap.set(quicStream.streamId, quicStream);
1043-
quicStream.addEventListener(
1044-
events.EventQUICStreamSend.name,
1045-
this.handleEventQUICStreamSend,
1046-
);
1047-
quicStream.addEventListener(
1048-
events.EventQUICStreamDestroyed.name,
1049-
this.handleEventQUICStreamDestroyed,
1050-
{ once: true },
1051-
);
1052-
quicStream.addEventListener(EventAll.name, this.handleEventQUICStream);
1053-
this.dispatchEvent(
1054-
new events.EventQUICConnectionStream({ detail: quicStream }),
1055-
);
1032+
try {
1033+
this.conn.streamSend(streamId, Buffer.alloc(0), false);
1034+
utils.never(
1035+
'We never expect the stream to be writable if it was created during the writable iterator',
1036+
);
1037+
} catch (e) {
1038+
// If we got `FinalSize` during the writable iterator then we cleaned up an errant stream
1039+
if (e.message === 'FinalSize') continue;
1040+
if (utils.isStreamStopped(e) !== false) {
1041+
// In this case it was a stream that was created but errored out. We want to create a new stream for this one case.
1042+
quicStream = QUICStream.createQUICStream({
1043+
initiated: 'peer',
1044+
streamId,
1045+
config: this.config,
1046+
connection: this,
1047+
codeToReason: this.codeToReason,
1048+
reasonToCode: this.reasonToCode,
1049+
logger: this.logger.getChild(`${QUICStream.name} ${streamId}`),
1050+
});
1051+
this.streamMap.set(quicStream.streamId, quicStream);
1052+
quicStream.addEventListener(
1053+
events.EventQUICStreamSend.name,
1054+
this.handleEventQUICStreamSend,
1055+
);
1056+
quicStream.addEventListener(
1057+
events.EventQUICStreamDestroyed.name,
1058+
this.handleEventQUICStreamDestroyed,
1059+
{ once: true },
1060+
);
1061+
quicStream.addEventListener(
1062+
EventAll.name,
1063+
this.handleEventQUICStream,
1064+
);
1065+
this.dispatchEvent(
1066+
new events.EventQUICConnectionStream({ detail: quicStream }),
1067+
);
1068+
quicStream.write();
1069+
continue;
1070+
}
1071+
utils.never(`Expected to throw "FinalSize", got ${e.message}`);
1072+
}
10561073
}
10571074
quicStream.write();
10581075
}

src/errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ class ErrorQUIC<T> extends AbstractError<T> {
66
static description = 'QUIC error';
77
}
88

9+
class ErrorQUICUndefinedBehaviour<T> extends AbstractError<T> {
10+
static description = 'You should never see this error';
11+
}
12+
913
class ErrorQUICHostInvalid<T> extends AbstractError<T> {
1014
static description = 'Host provided was not valid';
1115
}
@@ -293,6 +297,7 @@ class ErrorQUICStreamLimit<T> extends ErrorQUICStream<T> {
293297

294298
export {
295299
ErrorQUIC,
300+
ErrorQUICUndefinedBehaviour,
296301
ErrorQUICHostInvalid,
297302
ErrorQUICPortInvalid,
298303
ErrorQUICConfig,

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import * as errors from './errors';
1818
const textEncoder = new TextEncoder();
1919
const textDecoder = new TextDecoder('utf-8');
2020

21+
function never(message: string): never {
22+
throw new errors.ErrorQUICUndefinedBehaviour(message);
23+
}
24+
2125
/**
2226
* Used to yield to the event loop to allow other micro tasks to process
2327
*/
@@ -572,6 +576,7 @@ function setMaxListeners(
572576
export {
573577
textEncoder,
574578
textDecoder,
579+
never,
575580
yieldMicro,
576581
promisify,
577582
promise,

tests/QUICStream.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as testsUtils from './utils';
1111
import { generateTLSConfig, sleep } from './utils';
1212

1313
describe(QUICStream.name, () => {
14-
const logger = new Logger(`${QUICStream.name} Test`, LogLevel.WARN, [
14+
const logger = new Logger(`${QUICStream.name} Test`, LogLevel.INFO, [
1515
new StreamHandler(
1616
formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`,
1717
),

0 commit comments

Comments
 (0)