Skip to content

Commit 8ff0b20

Browse files
committed
fs: runtime deprecate closing fs.Dir on garbage collection
1 parent fd6cc1d commit 8ff0b20

File tree

5 files changed

+77
-6
lines changed

5 files changed

+77
-6
lines changed

doc/api/deprecations.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4108,12 +4108,15 @@ an internal nodejs implementation rather than a public facing API, use `node:htt
41084108

41094109
<!-- YAML
41104110
changes:
4111+
- version: REPLACEME
4112+
pr-url: https://github.com/nodejs/node/pull/58850
4113+
description: Runtime deprecation.
41114114
- version: REPLACEME
41124115
pr-url: https://github.com/nodejs/node/pull/59839
41134116
description: Documentation-only deprecation.
41144117
-->
41154118

4116-
Type: Documentation-only
4119+
Type: Runtime
41174120

41184121
Allowing a [`fs.Dir`][] object to be closed on garbage collection is
41194122
deprecated. In the future, doing so might result in a thrown error that will

src/env-inl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,14 @@ inline void Environment::RemoveHeapSnapshotNearHeapLimitCallback(
919919
heap_limit);
920920
}
921921

922+
bool Environment::dir_gc_close_warning() const {
923+
return emit_dir_gc_warning_;
924+
}
925+
926+
void Environment::set_dir_gc_close_warning(bool on) {
927+
emit_dir_gc_warning_ = on;
928+
}
929+
922930
} // namespace node
923931

924932
// These two files depend on each other. Including base_object-inl.h after this

src/env.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,9 @@ class Environment final : public MemoryRetainer {
820820
inline node_module* extra_linked_bindings_tail();
821821
inline const Mutex& extra_linked_bindings_mutex() const;
822822

823+
inline bool dir_gc_close_warning() const;
824+
inline void set_dir_gc_close_warning(bool on);
825+
823826
inline void set_source_maps_enabled(bool on);
824827
inline bool source_maps_enabled() const;
825828

@@ -1103,6 +1106,7 @@ class Environment final : public MemoryRetainer {
11031106
std::shared_ptr<KVStore> env_vars_;
11041107
bool printed_error_ = false;
11051108
bool trace_sync_io_ = false;
1109+
bool emit_dir_gc_warning_ = true;
11061110
bool emit_env_nonstring_warning_ = true;
11071111
bool emit_err_name_warning_ = true;
11081112
bool source_maps_enabled_ = false;

src/node_dir.cc

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,27 @@ inline void DirHandle::GCClose() {
164164
}
165165

166166
// If the close was successful, we still want to emit a process warning
167-
// to notify that the file descriptor was gc'd. We want to be noisy about
167+
// to notify that the directory handle was gc'd. We want to be noisy about
168168
// this because not explicitly closing the DirHandle is a bug.
169169

170-
env()->SetImmediate([](Environment* env) {
171-
ProcessEmitWarning(env,
172-
"Closing directory handle on garbage collection");
173-
}, CallbackFlags::kUnrefed);
170+
env()->SetImmediate(
171+
[](Environment* env) {
172+
ProcessEmitWarning(env,
173+
"Closing directory handle on garbage collection");
174+
if (env->dir_gc_close_warning()) {
175+
env->set_dir_gc_close_warning(false);
176+
USE(ProcessEmitDeprecationWarning(
177+
env,
178+
"Closing a Dir object on garbage collection is deprecated. "
179+
"Please close Dir objects explicitly using "
180+
"Dir.prototype.close(), "
181+
"or by using explicit resource management ('using' keyword). "
182+
"In the future, an error will be thrown if a directory is closed "
183+
"during garbage collection.",
184+
"DEP0200"));
185+
}
186+
},
187+
CallbackFlags::kUnrefed);
174188
}
175189

176190
void AfterClose(uv_fs_t* req) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Flags: --expose-gc --no-warnings
2+
3+
// Test that a runtime warning is emitted when a Dir object
4+
// is allowed to close on garbage collection. In the future, this
5+
// test should verify that closing on garbage collection throws a
6+
// process fatal exception.
7+
8+
import { expectWarning, mustCall } from '../common/index.mjs';
9+
import { rejects } from 'node:assert';
10+
import { opendir } from 'node:fs/promises';
11+
import { setImmediate } from 'node:timers';
12+
13+
const warning =
14+
'Closing a Dir object on garbage collection is deprecated. ' +
15+
'Please close Dir objects explicitly using Dir.prototype.close(), ' +
16+
"or by using explicit resource management ('using' keyword). " +
17+
'In the future, an error will be thrown if a directory is closed during garbage collection.';
18+
19+
// Test that closing Dir automatically using iterator doesn't emit warning
20+
{
21+
const dir = await opendir(import.meta.dirname);
22+
await Array.fromAsync(dir);
23+
await rejects(dir.close(), { code: 'ERR_DIR_CLOSED' });
24+
}
25+
26+
{
27+
expectWarning({
28+
Warning: [['Closing directory handle on garbage collection']],
29+
DeprecationWarning: [[warning, 'DEP0200']],
30+
});
31+
32+
await opendir(import.meta.dirname).then(mustCall(() => {
33+
setImmediate(() => {
34+
// The dir is out of scope now
35+
globalThis.gc();
36+
37+
// Wait an extra event loop turn, as the warning is emitted from the
38+
// native layer in an unref()'ed setImmediate() callback.
39+
setImmediate(mustCall());
40+
});
41+
}));
42+
}

0 commit comments

Comments
 (0)