Skip to content
Closed
Next Next commit
stream: fixup cancelation handling in pull()
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode
  • Loading branch information
jasnell committed Aug 31, 2026
commit f3198c8591cfceb1bb980388e9b27789d9a37217
34 changes: 31 additions & 3 deletions lib/internal/streams/iter/pull.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ const {
isPromise,
isUint8Array,
} = require('internal/util/types');
const { AbortController } = require('internal/abort_controller');
const {
AbortController,
AbortSignal,
} = require('internal/abort_controller');

const {
arrayBufferViewToUint8Array,
Expand Down Expand Up @@ -862,8 +865,33 @@ function pull(source, ...args) {

return {
__proto__: null,
async *[SymbolAsyncIterator]() {
yield* createAsyncPipeline(from(source), transforms, signal);
[SymbolAsyncIterator]() {
const controller = new AbortController();
const iteratorSignal = signal === undefined ?
controller.signal : AbortSignal.any([signal, controller.signal]);

async function* pipeline() {
yield* createAsyncPipeline(from(source), transforms, iteratorSignal);
}
const iterator = pipeline();

return {
__proto__: null,
next(value) {
return iterator.next(value);
},
return(value) {
controller.abort(lazyDOMException('Aborted', 'AbortError'));
return iterator.return(value);
},
throw(error) {
controller.abort(error);
return iterator.throw(error);
},
[SymbolAsyncIterator]() {
return this;
},
};
},
};
}
Expand Down
30 changes: 30 additions & 0 deletions test/parallel/test-stream-iter-pull-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,35 @@ async function testPullSignalAbortWhileSourceNextPending() {
await assert.rejects(next, { name: 'AbortError' });
}

async function testPullReturnWhileSourceNextPending() {
let startNext;
const nextStarted = new Promise((resolve) => { startNext = resolve; });
const source = {
[Symbol.asyncIterator]() {
return {
next() {
startNext();
return new Promise(() => {});
},
};
},
};

const iter = pull(source)[Symbol.asyncIterator]();
const next = assert.rejects(iter.next(), { name: 'AbortError' });
await nextStarted;

const timeout = {};
const result = await Promise.race([
iter.return(),
new Promise((resolve) => setImmediate(resolve, timeout)),
]);

assert.notStrictEqual(result, timeout);
assert.deepStrictEqual(result, { value: undefined, done: true });
await next;
}

async function testPullSignalAbortWithTransformWhileSourceNextPending() {
const source = {
[Symbol.asyncIterator]() {
Expand Down Expand Up @@ -417,6 +446,7 @@ async function testTransformOptionsNotShared() {
testTapCallbackError(),
testPullSignalAbortMidIteration(),
testPullSignalAbortWhileSourceNextPending(),
testPullReturnWhileSourceNextPending(),
testPullSignalAbortWithTransformWhileSourceNextPending(),
testPullConsumerBreakCleanup(),
testPullTransformReturnsPromise(),
Expand Down