Skip to content

Commit 2d8febc

Browse files
committed
fs: deprecate closing FileHandle on garbage collection
Closing the FileHandle on garbage collection is a bad practice. Runtime deprecate and indicate that an error will be thrown in the future. PR-URL: #28396 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 018c3e8 commit 2d8febc

File tree

6 files changed

+102
-2
lines changed

6 files changed

+102
-2
lines changed

doc/api/deprecations.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2538,7 +2538,6 @@ an officially supported API.
25382538
changes:
25392539
- version: v13.0.0
25402540
pr-url: https://github.com/nodejs/node/pull/29061
2541-
description: Runtime deprecation.
25422541
-->
25432542
25442543
Type: Runtime
@@ -2569,6 +2568,37 @@ accordingly instead to avoid the ambigiuty.
25692568
To maintain existing behaviour `response.finished` should be replaced with
25702569
`response.writableEnded`.
25712570
2571+
<a id="DEP00XX"></a>
2572+
### DEP00XX: Closing fs.FileHandle on garbage collection
2573+
<!-- YAML
2574+
changes:
2575+
- version: REPLACEME
2576+
pr-url: https://github.com/nodejs/node/pull/28396
2577+
description: Runtime deprecation.
2578+
-->
2579+
2580+
Type: Runtime
2581+
2582+
Allowing a [`fs.FileHandle`][] object to be closed on garbage collection is
2583+
deprecated. In the future, doing so may result in a thrown error that will
2584+
terminate the process.
2585+
2586+
Please ensure that all `fs.FileHandle` objects are explicitly closed using
2587+
`FileHandle.prototype.close()` when the `fs.FileHandle` is no longer needed:
2588+
2589+
```js
2590+
const fsPromises = require('fs').promises;
2591+
async function openAndClose() {
2592+
let filehandle;
2593+
try {
2594+
filehandle = await fsPromises.open('thefile.txt', 'r');
2595+
} finally {
2596+
if (filehandle !== undefined)
2597+
await filehandle.close();
2598+
}
2599+
}
2600+
```
2601+
25722602
[`--pending-deprecation`]: cli.html#cli_pending_deprecation
25732603
[`--throw-deprecation`]: cli.html#cli_throw_deprecation
25742604
[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
@@ -2606,6 +2636,7 @@ To maintain existing behaviour `response.finished` should be replaced with
26062636
[`domain`]: domain.html
26072637
[`ecdh.setPublicKey()`]: crypto.html#crypto_ecdh_setpublickey_publickey_encoding
26082638
[`emitter.listenerCount(eventName)`]: events.html#events_emitter_listenercount_eventname
2639+
[`fs.FileHandle`]: fs.html#fs_class_filehandle
26092640
[`fs.access()`]: fs.html#fs_fs_access_path_mode_callback
26102641
[`fs.createReadStream()`]: fs.html#fs_fs_createreadstream_path_options
26112642
[`fs.createWriteStream()`]: fs.html#fs_fs_createwritestream_path_options

src/env-inl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,14 @@ inline bool Environment::owns_inspector() const {
888888
return flags_ & kOwnsInspector;
889889
}
890890

891+
bool Environment::filehandle_close_warning() const {
892+
return emit_filehandle_warning_;
893+
}
894+
895+
void Environment::set_filehandle_close_warning(bool on) {
896+
emit_filehandle_warning_ = on;
897+
}
898+
891899
inline uint64_t Environment::thread_id() const {
892900
return thread_id_;
893901
}

src/env.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,9 @@ class Environment : public MemoryRetainer {
10731073
inline node_module* extra_linked_bindings_head();
10741074
inline const Mutex& extra_linked_bindings_mutex() const;
10751075

1076+
inline bool filehandle_close_warning() const;
1077+
inline void set_filehandle_close_warning(bool on);
1078+
10761079
inline void ThrowError(const char* errmsg);
10771080
inline void ThrowTypeError(const char* errmsg);
10781081
inline void ThrowRangeError(const char* errmsg);
@@ -1288,6 +1291,7 @@ class Environment : public MemoryRetainer {
12881291
bool trace_sync_io_ = false;
12891292
bool emit_env_nonstring_warning_ = true;
12901293
bool emit_err_name_warning_ = true;
1294+
bool emit_filehandle_warning_ = true;
12911295
size_t async_callback_scope_depth_ = 0;
12921296
std::vector<double> destroy_async_id_list_;
12931297

src/node_file.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,21 @@ inline void FileHandle::Close() {
205205
// If the close was successful, we still want to emit a process warning
206206
// to notify that the file descriptor was gc'd. We want to be noisy about
207207
// this because not explicitly closing the FileHandle is a bug.
208+
208209
env()->SetUnrefImmediate([detail](Environment* env) {
209210
ProcessEmitWarning(env,
210211
"Closing file descriptor %d on garbage collection",
211212
detail.fd);
213+
if (env->filehandle_close_warning()) {
214+
env->set_filehandle_close_warning(false);
215+
ProcessEmitDeprecationWarning(
216+
env,
217+
"Closing a FileHandle object on garbage collection is deprecated. "
218+
"Please close FileHandle objects explicitly using "
219+
"FileHandle.prototype.close(). In the future, an error will be "
220+
"thrown if a file descriptor is closed during garbage collection.",
221+
"DEP00XX").IsNothing();
222+
}
212223
});
213224
}
214225

test/parallel/test-fs-filehandle.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ let fdnum;
1919
assert.strictEqual(ctx.errno, undefined);
2020
}
2121

22+
const deprecationWarning =
23+
'Closing a FileHandle object on garbage collection is deprecated. ' +
24+
'Please close FileHandle objects explicitly using ' +
25+
'FileHandle.prototype.close(). In the future, an error will be ' +
26+
'thrown if a file descriptor is closed during garbage collection.';
27+
2228
common.expectWarning({
2329
'internal/test/binding': [
2430
'These APIs are for internal testing only. Do not use them.'
2531
],
2632
'Warning': [
2733
`Closing file descriptor ${fdnum} on garbage collection`
28-
]
34+
],
35+
'DeprecationWarning': [[deprecationWarning, 'DEP00XX']]
2936
});
3037

3138
global.gc();
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Flags: --expose-gc --no-warnings
2+
'use strict';
3+
4+
// Test that a runtime warning is emitted when a FileHandle object
5+
// is allowed to close on garbage collection. In the future, this
6+
// test should verify that closing on garbage collection throws a
7+
// process fatal exception.
8+
9+
const common = require('../common');
10+
const assert = require('assert');
11+
const { promises: fs } = require('fs');
12+
13+
const warning =
14+
'Closing a FileHandle object on garbage collection is deprecated. ' +
15+
'Please close FileHandle objects explicitly using ' +
16+
'FileHandle.prototype.close(). In the future, an error will be ' +
17+
'thrown if a file descriptor is closed during garbage collection.';
18+
19+
async function doOpen() {
20+
const fh = await fs.open(__filename);
21+
22+
common.expectWarning({
23+
Warning: [[`Closing file descriptor ${fh.fd} on garbage collection`]],
24+
DeprecationWarning: [[warning, 'DEP00XX']]
25+
});
26+
27+
return fh;
28+
}
29+
30+
// Perform the file open assignment within a block.
31+
// When the block scope exits, the file handle will
32+
// be eligible for garbage collection.
33+
{
34+
doOpen().then(common.mustCall((fd) => {
35+
assert.strictEqual(typeof fd, 'object');
36+
}));
37+
}
38+
39+
setTimeout(() => global.gc(), 10);

0 commit comments

Comments
 (0)