Skip to content

Commit 60760ab

Browse files
DanieleAuriliobartlomieju
authored andcommitted
fix(runtime/ops): Fix watchfs remove event (#27041)
Fix related to #26906. Currently, if a file is removed, no event is emitted because the file path no longer exists. As a result, [this check](https://github.com/denoland/deno/blob/12b377247be2b74155ded3a678ff2996ef3d7c9f/runtime/ops/fs_events.rs#L149) returns false. With this PR, an additional check is introduced to verify if the file exists. If the file does not exist, a custom "remove" event is emitted. This change is necessary because, based on tests conducted on macOS and Linux (Ubuntu 24.04.1 LTS), Linux emits a "rename" event instead of a "remove" event when a file is deleted. Introducing a dedicated "remove" event ensures consistent and clearer behavior across platforms.
1 parent 7c9a556 commit 60760ab

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

runtime/ops/fs_events.rs

+15
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ fn starts_with_canonicalized(path: &Path, prefix: &str) -> bool {
109109
}
110110
}
111111

112+
fn is_file_removed(event_path: &PathBuf) -> bool {
113+
let exists_path = std::fs::exists(event_path);
114+
match exists_path {
115+
Ok(res) => !res,
116+
Err(_) => false,
117+
}
118+
}
119+
112120
#[derive(Debug, thiserror::Error)]
113121
pub enum FsEventsError {
114122
#[error(transparent)]
@@ -150,6 +158,13 @@ fn start_watcher(
150158
})
151159
}) {
152160
let _ = sender.try_send(Ok(event.clone()));
161+
} else if event.paths.iter().any(is_file_removed) {
162+
let remove_event = FsEvent {
163+
kind: "remove",
164+
paths: event.paths.clone(),
165+
flag: None,
166+
};
167+
let _ = sender.try_send(Ok(remove_event));
153168
}
154169
}
155170
}

tests/unit/fs_events_test.ts

+30
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ async function makeTempDir(): Promise<string> {
4545
return testDir;
4646
}
4747

48+
async function makeTempFile(): Promise<string> {
49+
const testFile = await Deno.makeTempFile();
50+
// The watcher sometimes witnesses the creation of it's own root
51+
// directory. Delay a bit.
52+
await delay(100);
53+
return testFile;
54+
}
55+
4856
Deno.test(
4957
{ permissions: { read: true, write: true } },
5058
async function watchFsBasic() {
@@ -155,3 +163,25 @@ Deno.test(
155163
assert(done);
156164
},
157165
);
166+
167+
Deno.test(
168+
{ permissions: { read: true, write: true } },
169+
async function watchFsRemove() {
170+
const testFile = await makeTempFile();
171+
using watcher = Deno.watchFs(testFile);
172+
async function waitForRemove() {
173+
for await (const event of watcher) {
174+
if (event.kind === "remove") {
175+
return event;
176+
}
177+
}
178+
}
179+
const eventPromise = waitForRemove();
180+
181+
await Deno.remove(testFile);
182+
183+
// Expect zero events.
184+
const event = await eventPromise;
185+
assertEquals(event!.kind, "remove");
186+
},
187+
);

0 commit comments

Comments
 (0)