diff --git a/packages/fs-node/src/FileHandle.ts b/packages/fs-node/src/FileHandle.ts index 4a8ee36c5..0d333e1c5 100644 --- a/packages/fs-node/src/FileHandle.ts +++ b/packages/fs-node/src/FileHandle.ts @@ -263,9 +263,10 @@ export class FileHandle extends EventEmitter implements IFileHandle { private unref(): void { this.refs--; if (this.refs === 0) { + const currentFd = this.fd; this.fd = -1; if (this.closeResolve) { - promisify(this.fs, 'close')(this.fd).then(this.closeResolve, this.closeReject); + promisify(this.fs, 'close')(currentFd).then(this.closeResolve, this.closeReject); } } } diff --git a/packages/fs-node/src/__tests__/volume/FileHandle.test.ts b/packages/fs-node/src/__tests__/volume/FileHandle.test.ts index 480369008..7d2d70423 100644 --- a/packages/fs-node/src/__tests__/volume/FileHandle.test.ts +++ b/packages/fs-node/src/__tests__/volume/FileHandle.test.ts @@ -389,6 +389,23 @@ describe('FileHandle', () => { }); describe('reference counting', () => { + it.each([false, true])('closes the original descriptor after stream cancellation (read=%s)', async read => { + const fs = createFs({ '/test': 'content' }); + const handle = await fs.promises.open('/test', 'r'); + const fd = handle.fd; + const reader = handle.readableWebStream().getReader(); + if (read) await reader.read(); + const closed = handle.close(); + const closedAgain = handle.close(); + expect(closedAgain).toBe(closed); + expect(fs.fstatSync(fd).isFile()).toBe(true); + await reader.cancel(); + await expect(closed).resolves.toBeUndefined(); + expect(handle.fd).toBe(-1); + expect(() => fs.fstatSync(fd)).toThrow(expect.objectContaining({ code: 'EBADF' })); + await expect(handle.close()).resolves.toBeUndefined(); + }); + it('should handle multiple close calls gracefully', async () => { const fs = createFs(); fs.writeFileSync('/test', 'content');