Skip to content

Commit 919c666

Browse files
authored
fix: ignore listening for the directory itself to be deleted (#251)
After Node.js v22, fs.watch(dir) and deleting a dir will trigger the rename change event. Here we just ignore it and keep the same behavior as before v22 libuv/libuv#4376
1 parent 215eafd commit 919c666

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

lib/watchEventSource.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,52 @@ const directWatchers = new Map();
3434
/** @type {Map<Watcher, RecursiveWatcher | DirectWatcher>} */
3535
const underlyingWatcher = new Map();
3636

37+
function createEPERMError(filePath) {
38+
const error = new Error(`Operation not permitted: ${filePath}`);
39+
error.code = "EPERM";
40+
return error;
41+
}
42+
43+
function createHandleChangeEvent(watcher, filePath, handleChangeEvent) {
44+
return (type, filename) => {
45+
// TODO: After Node.js v22, fs.watch(dir) and deleting a dir will trigger the rename change event.
46+
// Here we just ignore it and keep the same behavior as before v22
47+
// https://github.com/libuv/libuv/pull/4376
48+
if (
49+
type === "rename" &&
50+
path.isAbsolute(filename) &&
51+
path.basename(filename) === path.basename(filePath)
52+
) {
53+
if (!IS_OSX) {
54+
// Before v22, windows will throw EPERM error
55+
watcher.emit("error", createEPERMError(filename));
56+
}
57+
// Before v22, macos nothing to do
58+
return;
59+
}
60+
handleChangeEvent(type, filename);
61+
};
62+
}
63+
3764
class DirectWatcher {
3865
constructor(filePath) {
3966
this.filePath = filePath;
4067
this.watchers = new Set();
4168
this.watcher = undefined;
4269
try {
4370
const watcher = fs.watch(filePath);
71+
4472
this.watcher = watcher;
45-
watcher.on("change", (type, filename) => {
46-
for (const w of this.watchers) {
47-
w.emit("change", type, filename);
73+
const handleChangeEvent = createHandleChangeEvent(
74+
watcher,
75+
filePath,
76+
(type, filename) => {
77+
for (const w of this.watchers) {
78+
w.emit("change", type, filename);
79+
}
4880
}
49-
});
81+
);
82+
watcher.on("change", handleChangeEvent);
5083
watcher.on("error", error => {
5184
for (const w of this.watchers) {
5285
w.emit("error", error);
@@ -331,3 +364,5 @@ exports.batch = fn => {
331364
exports.getNumberOfWatchers = () => {
332365
return watcherCount;
333366
};
367+
368+
exports.createHandleChangeEvent = createHandleChangeEvent;

test/Assumption.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ var TestHelper = require("./helpers/TestHelper");
88

99
var fixtures = path.join(__dirname, "fixtures");
1010
var testHelper = new TestHelper(fixtures);
11+
var { createHandleChangeEvent } = require("../lib/watchEventSource");
1112

1213
const IS_OSX = require("os").platform() === "darwin";
1314
const IS_WIN = require("os").platform() === "win32";
1415
const SUPPORTS_RECURSIVE_WATCHING = IS_OSX || IS_WIN;
1516

17+
1618
describe("Assumption", function() {
1719
this.timeout(10000);
1820
var watcherToClose = null;
@@ -319,10 +321,11 @@ describe("Assumption", function() {
319321
));
320322
let gotSelfRename = false;
321323
let gotPermError = false;
322-
watcher.on("change", function(type, filename) {
324+
let handleChangeEvent = createHandleChangeEvent(watcher, path.join(fixtures, "watch-test-dir"), (type, filename) => {
323325
if (type === "rename" && filename === "watch-test-dir")
324326
gotSelfRename = true;
325327
});
328+
watcher.on("change", handleChangeEvent);
326329
watcher.on("error", function(err) {
327330
if (err && err.code === "EPERM") gotPermError = true;
328331
});

0 commit comments

Comments
 (0)