Skip to content

Commit 41dc5ba

Browse files
committed
fs: avoid unhandled rejection in filehandle readableWebStream close
Signed-off-by: jorge guerrero <grrr.jrg@gmail.com>
1 parent 6964b53 commit 41dc5ba

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

lib/internal/fs/promises.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,11 @@ class FileHandle extends EventEmitter {
335335
} = require('internal/webstreams/readablestream');
336336
this[kRef]();
337337
this.once('close', () => {
338-
readableStreamCancel(readable);
338+
PromisePrototypeThen(
339+
readableStreamCancel(readable),
340+
undefined,
341+
() => undefined,
342+
);
339343
});
340344

341345
return readable;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
async function runScript(source) {
7+
const child = await common.spawnPromisified(process.execPath, [
8+
'--unhandled-rejections=strict',
9+
'-e',
10+
source,
11+
]);
12+
13+
assert.strictEqual(child.code, 0, child.stderr);
14+
assert.strictEqual(child.signal, null);
15+
assert.strictEqual(child.stderr, '');
16+
}
17+
18+
// Regression: once a readableWebStream read fails, explicitly closing the
19+
// FileHandle must not trigger an unhandled rejection.
20+
(async () => {
21+
await runScript(`
22+
const { closeSync } = require('node:fs');
23+
const { open } = require('node:fs/promises');
24+
const assert = require('node:assert');
25+
26+
async function consume(readable) {
27+
for await (const _ of readable);
28+
}
29+
30+
(async () => {
31+
const file = await open(process.execPath);
32+
const readable = file.readableWebStream();
33+
closeSync(file.fd);
34+
35+
await assert.rejects(consume(readable), { code: 'EBADF' });
36+
await assert.rejects(file.close(), { code: 'EBADF' });
37+
})().catch((err) => {
38+
console.error(err);
39+
process.exitCode = 1;
40+
});
41+
`);
42+
})().then(common.mustCall());
43+
44+
// Edge: BYOB readers should not leak unhandled rejections on the same path.
45+
(async () => {
46+
await runScript(`
47+
const { closeSync } = require('node:fs');
48+
const { open } = require('node:fs/promises');
49+
const assert = require('node:assert');
50+
51+
(async () => {
52+
const file = await open(process.execPath);
53+
const readable = file.readableWebStream();
54+
const reader = readable.getReader({ mode: 'byob' });
55+
closeSync(file.fd);
56+
57+
await assert.rejects(reader.read(new DataView(new ArrayBuffer(1024))), {
58+
code: 'EBADF',
59+
});
60+
await assert.rejects(file.close(), { code: 'EBADF' });
61+
})().catch((err) => {
62+
console.error(err);
63+
process.exitCode = 1;
64+
});
65+
`);
66+
})().then(common.mustCall());
67+
68+
// Safety: successful reads must remain unaffected.
69+
(async () => {
70+
await runScript(`
71+
const { open } = require('node:fs/promises');
72+
73+
(async () => {
74+
const file = await open(process.execPath);
75+
for await (const _ of file.readableWebStream());
76+
await file.close();
77+
})().catch((err) => {
78+
console.error(err);
79+
process.exitCode = 1;
80+
});
81+
`);
82+
})().then(common.mustCall());

0 commit comments

Comments
 (0)