stream: ensure full-close semantics when closed · nodejs/node@7319198 · GitHub
Skip to content

Commit 7319198

Browse files
jasnelladuh95
authored andcommitted
stream: ensure full-close semantics when closed
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: #65652 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent bf5acb7 commit 7319198

3 files changed

Lines changed: 104 additions & 101 deletions

File tree

‎lib/internal/streams/iter/duplex.js‎

Lines changed: 35 additions & 62 deletions

‎test/parallel/test-stream-iter-duplex.js‎

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,26 @@ async function testBasicDuplex() {
1414

1515
// A writes, B reads
1616
await channelA.writer.write('hello from A');
17-
await channelA.close();
18-
17+
const closing = channelA.close();
1918
const dataAtB = await text(channelB.readable);
19+
await closing;
2020
assert.strictEqual(dataAtB, 'hello from A');
2121
}
2222

2323
async function testBidirectional() {
2424
const [channelA, channelB] = duplex();
2525

26-
// A writes to B, B writes to A concurrently
27-
const writeA = (async () => {
28-
await channelA.writer.write('A to B');
29-
await channelA.close();
30-
})();
31-
32-
const writeB = (async () => {
33-
await channelB.writer.write('B to A');
34-
await channelB.close();
35-
})();
36-
37-
const readAtB = text(channelB.readable);
38-
const readAtA = text(channelA.readable);
26+
await channelA.writer.write('A to B');
27+
await channelB.writer.write('B to A');
3928

40-
await Promise.all([writeA, writeB]);
41-
42-
const [dataAtA, dataAtB] = await Promise.all([readAtA, readAtB]);
29+
const endA = channelA.writer.end();
30+
const endB = channelB.writer.end();
31+
const [dataAtA, dataAtB] = await Promise.all([
32+
text(channelA.readable),
33+
text(channelB.readable),
34+
]);
35+
await Promise.all([endA, endB]);
36+
await Promise.all([channelA.close(), channelB.close()]);
4337

4438
assert.strictEqual(dataAtB, 'A to B');
4539
assert.strictEqual(dataAtA, 'B to A');
@@ -51,19 +45,29 @@ async function testMultipleWrites() {
5145
await channelA.writer.write('one');
5246
await channelA.writer.write('two');
5347
await channelA.writer.write('three');
54-
await channelA.close();
55-
48+
const closing = channelA.close();
5649
const data = await text(channelB.readable);
50+
await closing;
5751
assert.strictEqual(data, 'onetwothree');
5852
}
5953

6054
async function testChannelClose() {
6155
const [channelA, channelB] = duplex();
62-
63-
await channelA.close();
64-
65-
// Should be able to close twice without error
66-
await channelA.close();
56+
const iteratorA = channelA.readable[Symbol.asyncIterator]();
57+
const otherIteratorA = channelA.readable[Symbol.asyncIterator]();
58+
const pendingRead = iteratorA.next();
59+
60+
const closing = channelA.close();
61+
assert.strictEqual(channelA.close(), closing);
62+
await closing;
63+
64+
assert.strictEqual((await pendingRead).done, true);
65+
assert.strictEqual((await otherIteratorA.next()).done, true);
66+
assert.strictEqual(
67+
(await channelA.readable[Symbol.asyncIterator]().next()).done, true);
68+
await assert.rejects(channelB.writer.write('late'), {
69+
code: 'ERR_INVALID_STATE',
70+
});
6771

6872
// B's readable should end (A -> B direction is closed)
6973
const batches = [];
@@ -80,9 +84,9 @@ async function testWithOptions() {
8084
});
8185

8286
await channelA.writer.write('msg');
83-
await channelA.close();
84-
87+
const closing = channelA.close();
8588
const data = await text(channelB.readable);
89+
await closing;
8690
assert.strictEqual(data, 'msg');
8791
}
8892

@@ -95,15 +99,17 @@ async function testPerChannelOptions() {
9599
// Channel A -> B direction uses A's options
96100
// Channel B -> A direction uses B's options
97101
await channelA.writer.write('from-a');
98-
await channelA.close();
99-
100102
await channelB.writer.write('from-b');
101-
await channelB.close();
103+
104+
const endA = channelA.writer.end();
105+
const endB = channelB.writer.end();
102106

103107
const [dataAtA, dataAtB] = await Promise.all([
104108
text(channelA.readable),
105109
text(channelB.readable),
106110
]);
111+
await Promise.all([endA, endB]);
112+
await Promise.all([channelA.close(), channelB.close()]);
107113

108114
assert.strictEqual(dataAtB, 'from-a');
109115
assert.strictEqual(dataAtA, 'from-b');
@@ -146,17 +152,39 @@ async function testWriterEndWithPreAbortedSignal() {
146152
async function testEmptyDuplex() {
147153
const [channelA, channelB] = duplex();
148154

149-
// Close without writing
150-
await channelA.close();
151-
await channelB.close();
155+
await channelA.writer.end();
156+
await channelB.writer.end();
152157

153158
const dataAtA = await bytes(channelA.readable);
154159
const dataAtB = await bytes(channelB.readable);
160+
await Promise.all([channelA.close(), channelB.close()]);
155161

156162
assert.strictEqual(dataAtA.byteLength, 0);
157163
assert.strictEqual(dataAtB.byteLength, 0);
158164
}
159165

166+
async function testCloseWaitsForDrain() {
167+
const [channelA, channelB] = duplex();
168+
await channelA.writer.write('buffered');
169+
170+
let closed = false;
171+
const closing = channelA.close().then(common.mustCall(() => {
172+
closed = true;
173+
}));
174+
await new Promise(setImmediate);
175+
assert.strictEqual(closed, false);
176+
177+
assert.strictEqual(await text(channelB.readable), 'buffered');
178+
await closing;
179+
}
180+
181+
async function testClosePropagatesWriterFailure() {
182+
const [channelA] = duplex();
183+
const reason = new Error('writer failed');
184+
channelA.writer.fail(reason);
185+
await assert.rejects(channelA.close(), (error) => error === reason);
186+
}
187+
160188
// Channel fail propagation
161189
async function testChannelFail() {
162190
const [a, b] = duplex();
@@ -200,6 +228,8 @@ Promise.all([
200228
testAbortSignal(),
201229
testWriterEndWithPreAbortedSignal(),
202230
testEmptyDuplex(),
231+
testCloseWaitsForDrain(),
232+
testClosePropagatesWriterFailure(),
203233
testChannelFail(),
204234
testAbortSignalBothChannels(),
205235
]).then(common.mustCall());

‎test/parallel/test-stream-iter-validation.js‎

Lines changed: 6 additions & 6 deletions

0 commit comments

Comments
 (0)