Skip to content

Commit 3876d41

Browse files
committed
fix(assets): drain watcher teardown before deleting dir on windows
Closing chokidar/fs.watch doesn't guarantee libuv has retired the outstanding ReadDirectoryChangesW request. Every assets test's afterEach closes the watcher and immediately rm -rf's the same temp directory, and on Windows CI that race trips a hard libuv assertion (fs-event.c:72), crashing the vitest worker fork outright.
1 parent 228802b commit 3876d41

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

plugins/assets/src/node/watcher.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DevframeNodeContext } from 'devframe/types'
2+
import process from 'node:process'
23
import { watch } from 'chokidar'
34
import { debounce } from 'perfect-debounce'
45
import { CHANGED_EVENT } from '../constants'
@@ -29,5 +30,18 @@ export function watchAssetsDir(ctx: DevframeNodeContext, dir: string): () => Pro
2930
.on('unlinkDir', () => void notify())
3031
.on('change', () => void notify())
3132

32-
return () => watcher.close()
33+
return async () => {
34+
await watcher.close()
35+
// On Windows, closing a chokidar/fs.watch handle doesn't guarantee the
36+
// OS has fully retired the outstanding ReadDirectoryChangesW request —
37+
// ftruncate/close returns before libuv's IOCP completion drains. If the
38+
// watched directory is deleted immediately after (as every test's
39+
// `afterEach` does), that stale completion can reach
40+
// `uv__fs_event_process` after the fact and trip its directory-prefix
41+
// sanity check, hard-crashing the process with
42+
// `Assertion failed: !_wcsnicmp(filename, dir, dirlen)` in fs-event.c.
43+
// A short grace period gives the pending I/O time to actually settle.
44+
if (process.platform === 'win32')
45+
await new Promise(resolve => setTimeout(resolve, 50))
46+
}
3347
}

0 commit comments

Comments
 (0)