stream: fixup error handling in stream/iter adapters · nodejs/node@bea41b1 · GitHub
Skip to content

Commit bea41b1

Browse files
committed
stream: fixup error handling in stream/iter adapters
Signed-off-by: James M Snell <jasnell@gmail.com>
1 parent 3afcc51 commit bea41b1

3 files changed

Lines changed: 51 additions & 21 deletions

File tree

lib/internal/streams/iter/classic.js

Lines changed: 12 additions & 6 deletions

lib/internal/streams/iter/consumers.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ function merge(...args) {
480480
);
481481
}
482482

483+
let primaryError;
483484
try {
484485
while (activeCount > 0 || ready.length > 0) {
485486
signal?.throwIfAborted();
@@ -500,17 +501,42 @@ function merge(...args) {
500501
});
501502
}
502503
}
504+
} catch (err) {
505+
primaryError = err;
503506
} finally {
504-
// Clean up: return all iterators
507+
// Clean up: return all iterators. Cleanup errors are not
508+
// swallowed - a broken iterator.return() (e.g., failing to
509+
// release a resource) should be visible to the caller.
510+
let cleanupError;
505511
await SafePromiseAllReturnVoid(iterators, async (iterator) => {
506512
if (iterator.return) {
507513
try {
508514
await iterator.return();
509-
} catch {
510-
// Ignore return errors
515+
} catch (err) {
516+
// Keep the first cleanup error encountered.
517+
cleanupError ??= err;
511518
}
512519
}
513520
});
521+
// Throws in finally are intentional: in an async generator,
522+
// cleanup must run on consumer break (generator return), and
523+
// code after the try/finally block would not execute.
524+
if (cleanupError !== undefined) {
525+
if (primaryError !== undefined) {
526+
// Both a primary error and a cleanup error occurred.
527+
// Wrap in SuppressedError so neither is lost:
528+
// .error = primaryError, .suppressed = cleanupError.
529+
// eslint-disable-next-line no-restricted-syntax, no-unsafe-finally
530+
throw new SuppressedError(cleanupError, primaryError);
531+
}
532+
// No primary error - the cleanup error is the only error.
533+
// eslint-disable-next-line no-unsafe-finally
534+
throw cleanupError;
535+
}
536+
if (primaryError !== undefined) {
537+
// eslint-disable-next-line no-unsafe-finally
538+
throw primaryError;
539+
}
514540
}
515541
},
516542
};

test/parallel/test-stream-iter-writable-from.js

Lines changed: 10 additions & 12 deletions

0 commit comments

Comments
 (0)