win: fix watch loop logic (#5013)

Refs: https://github.com/nodejs/node/issues/61398
This commit is contained in:
Stefan Stojanovic 2026-03-31 04:09:25 +02:00 committed by GitHub
parent 84816e064a
commit 44125af62a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 0 deletions

View File

@ -433,6 +433,7 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
WCHAR* filenamew = NULL; WCHAR* filenamew = NULL;
WCHAR* long_filenamew = NULL; WCHAR* long_filenamew = NULL;
DWORD offset = 0; DWORD offset = 0;
int dir_event_detected = 0;
assert(req->type == UV_FS_EVENT_REQ); assert(req->type == UV_FS_EVENT_REQ);
assert(handle->req_pending); assert(handle->req_pending);
@ -459,6 +460,10 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
assert(!filenamew); assert(!filenamew);
assert(!long_filenamew); assert(!long_filenamew);
if (file_info->FileNameLength == 0) {
dir_event_detected = 1;
}
/* /*
* Fire the event only if we were asked to watch a directory, * Fire the event only if we were asked to watch a directory,
* or if the filename filter matches. * or if the filename filter matches.
@ -587,6 +592,7 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
sizeof(info)) && sizeof(info)) &&
info.Directory && info.Directory &&
info.DeletePending) { info.DeletePending) {
dir_event_detected = 1;
uv__convert_utf16_to_utf8(handle->dirw, -1, &filename); uv__convert_utf16_to_utf8(handle->dirw, -1, &filename);
handle->cb(handle, filename, UV_RENAME, 0); handle->cb(handle, filename, UV_RENAME, 0);
uv__free(filename); uv__free(filename);
@ -599,6 +605,26 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
if (handle->flags & UV_HANDLE_CLOSING) { if (handle->flags & UV_HANDLE_CLOSING) {
uv__want_endgame(loop, (uv_handle_t*)handle); uv__want_endgame(loop, (uv_handle_t*)handle);
} else if (uv__is_active(handle)) { } else if (uv__is_active(handle)) {
/*
* Check if the handle has become a zombie pointing to \$Extend\$Deleted\.
* Only perform the check if we detected an event on the directory, which
* may indicate deletion.
*/
if (dir_event_detected) {
WCHAR path_buf[MAX_PATH];
DWORD path_len = GetFinalPathNameByHandleW(handle->dir_handle,
path_buf,
ARRAY_SIZE(path_buf),
FILE_NAME_NORMALIZED | VOLUME_NAME_NONE);
if (path_len > 0 && path_len < ARRAY_SIZE(path_buf)) {
if (wcsstr(path_buf, L"\\$Extend\\$Deleted\\") != NULL) {
handle->cb(handle, NULL, 0, UV_ENOENT);
return;
}
}
}
uv__fs_event_queue_readdirchanges(loop, handle); uv__fs_event_queue_readdirchanges(loop, handle);
} }
} }

View File

@ -519,6 +519,51 @@ TEST_IMPL(fs_event_watch_delete_dir) {
return 0; return 0;
} }
#ifdef _WIN32
static int fs_event_cb_del_dir_perm_got_enoent;
static void fs_event_cb_del_dir_perm(uv_fs_event_t* handle,
const char* filename,
int events,
int status) {
if (status == UV_ENOENT) {
fs_event_cb_del_dir_perm_got_enoent = 1;
uv_close((uv_handle_t*)handle, close_cb);
}
}
TEST_IMPL(fs_event_watch_delete_dir_win) {
uv_loop_t* loop = uv_default_loop();
int r;
/* Setup */
fs_event_cb_del_dir_perm_got_enoent = 0;
fs_event_unlink_files(NULL);
delete_dir("watch_del_dir/");
create_dir("watch_del_dir");
r = uv_fs_event_init(loop, &fs_event);
ASSERT_OK(r);
r = uv_fs_event_start(&fs_event, fs_event_cb_del_dir_perm, "watch_del_dir", 0);
ASSERT_OK(r);
r = uv_timer_init(loop, &timer);
ASSERT_OK(r);
r = uv_timer_start(&timer, fs_event_del_dir, 100, 0);
ASSERT_OK(r);
uv_run(loop, UV_RUN_DEFAULT);
ASSERT_EQ(1, fs_event_cb_del_dir_perm_got_enoent);
ASSERT_EQ(2, close_cb_called);
/* Cleanup */
fs_event_unlink_files(NULL);
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
#endif
TEST_IMPL(fs_event_watch_dir_recursive) { TEST_IMPL(fs_event_watch_dir_recursive) {
#if defined(__APPLE__) && defined(__TSAN__) #if defined(__APPLE__) && defined(__TSAN__)

View File

@ -410,6 +410,9 @@ TEST_FS_DECLARE (fs_read_bufs)
TEST_FS_DECLARE (fs_read_file_eof) TEST_FS_DECLARE (fs_read_file_eof)
TEST_DECLARE (fs_event_watch_dir) TEST_DECLARE (fs_event_watch_dir)
TEST_DECLARE (fs_event_watch_delete_dir) TEST_DECLARE (fs_event_watch_delete_dir)
#ifdef _WIN32
TEST_DECLARE (fs_event_watch_delete_dir_win)
#endif
TEST_DECLARE (fs_event_watch_dir_recursive) TEST_DECLARE (fs_event_watch_dir_recursive)
#ifdef _WIN32 #ifdef _WIN32
TEST_DECLARE (fs_event_watch_dir_short_path) TEST_DECLARE (fs_event_watch_dir_short_path)
@ -1143,6 +1146,9 @@ TASK_LIST_START
TEST_FS_ENTRY (fs_file_open_append) TEST_FS_ENTRY (fs_file_open_append)
TEST_ENTRY (fs_event_watch_dir) TEST_ENTRY (fs_event_watch_dir)
TEST_ENTRY (fs_event_watch_delete_dir) TEST_ENTRY (fs_event_watch_delete_dir)
#ifdef _WIN32
TEST_ENTRY (fs_event_watch_delete_dir_win)
#endif
TEST_ENTRY (fs_event_watch_dir_recursive) TEST_ENTRY (fs_event_watch_dir_recursive)
#ifdef _WIN32 #ifdef _WIN32
TEST_ENTRY (fs_event_watch_dir_short_path) TEST_ENTRY (fs_event_watch_dir_short_path)