Skip to content

Commit caa48a3

Browse files
mcollinaaduh95
authored andcommitted
stream: keep async context of pipeTo watchers
The ClosedPromiseHook records that pipeTo() and tee() install as the [[closedPromise]] of their reader and writer enqueued the watcher from whoever settled the record. The source-closed, source-errored and dest-errored steps of pipeTo() therefore ran in the async context of the code that closed or errored the stream, so the destination's close() and abort() and the source's cancel() observed the wrong AsyncLocalStorage store. Capture the async context frame when the hook is installed and register the reaction in it, as the spec's promise reaction would. Add a test that records the context each underlying source, sink and transformer callback observes across readable, writable, transform, pipe and tee scenarios, so scheduling shortcuts that drop the context are caught. Refs: #66906 Refs: #66424 Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #67052 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent bf7bef4 commit caa48a3

2 files changed

Lines changed: 621 additions & 2 deletions

File tree

‎lib/internal/webstreams/readablestream.js‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const {
4747
DOMException,
4848
} = internalBinding('messaging');
4949

50+
const AsyncContextFrame = require('internal/async_context_frame');
51+
5052
const {
5153
isArrayBufferView,
5254
isDataView,
@@ -1547,21 +1549,30 @@ function readableStreamFromIterable(iterable) {
15471549
// record enqueues the watcher at the microtask position a reaction on the
15481550
// promise would have had, without materializing the promise; the shared
15491551
// pending promise satisfies the probes on the erroring/release paths.
1552+
// The reaction is registered in the async context captured at install
1553+
// time, where the spec registers it, rather than in the context of
1554+
// whoever settles the record.
15501555
class ClosedPromiseHook {
15511556
constructor(onResolved, onRejected) {
15521557
this.promise = kPendingPromise;
15531558
this.onResolved = onResolved;
15541559
this.onRejected = onRejected;
1560+
this.contextFrame = AsyncContextFrame.current();
15551561
}
15561562

15571563
resolve() {
1558-
if (this.onResolved !== undefined)
1559-
PromisePrototypeThen(kResolvedPromise, this.onResolved);
1564+
if (this.onResolved === undefined)
1565+
return;
1566+
const prior = AsyncContextFrame.exchange(this.contextFrame);
1567+
PromisePrototypeThen(kResolvedPromise, this.onResolved);
1568+
AsyncContextFrame.set(prior);
15601569
}
15611570

15621571
reject(error) {
15631572
const onRejected = this.onRejected;
1573+
const prior = AsyncContextFrame.exchange(this.contextFrame);
15641574
PromisePrototypeThen(kResolvedPromise, () => onRejected(error));
1575+
AsyncContextFrame.set(prior);
15651576
}
15661577
}
15671578

0 commit comments

Comments
 (0)