Skip to content

Commit

Permalink
fix(runtime/ops): Fix watchfs remove event (#27041)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
DanieleAurilio authored Nov 25, 2024
1 parent d59bd5e commit 38b618c
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 0 deletions.
15 changes: 15 additions & 0 deletions runtime/ops/fs_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ fn starts_with_canonicalized(path: &Path, prefix: &str) -> bool {
}
}

fn is_file_removed(event_path: &PathBuf) -> bool {
let exists_path = std::fs::exists(event_path);
match exists_path {
Ok(res) => !res,
Err(_) => false,
}
}

#[derive(Debug, thiserror::Error)]
pub enum FsEventsError {
#[error(transparent)]
Expand Down Expand Up @@ -150,6 +158,13 @@ fn start_watcher(
})
}) {
let _ = sender.try_send(Ok(event.clone()));
} else if event.paths.iter().any(is_file_removed) {
let remove_event = FsEvent {
kind: "remove",
paths: event.paths.clone(),
flag: None,
};
let _ = sender.try_send(Ok(remove_event));
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/fs_events_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ async function makeTempDir(): Promise<string> {
return testDir;
}

async function makeTempFile(): Promise<string> {
const testFile = await Deno.makeTempFile();
// The watcher sometimes witnesses the creation of it's own root
// directory. Delay a bit.
await delay(100);
return testFile;
}

Deno.test(
{ permissions: { read: true, write: true } },
async function watchFsBasic() {
Expand Down Expand Up @@ -155,3 +163,25 @@ Deno.test(
assert(done);
},
);

Deno.test(
{ permissions: { read: true, write: true } },
async function watchFsRemove() {
const testFile = await makeTempFile();
using watcher = Deno.watchFs(testFile);
async function waitForRemove() {
for await (const event of watcher) {
if (event.kind === "remove") {
return event;
}
}
}
const eventPromise = waitForRemove();

await Deno.remove(testFile);

// Expect zero events.
const event = await eventPromise;
assertEquals(event!.kind, "remove");
},
);

0 comments on commit 38b618c

Please sign in to comment.