chokidar: after closing, chokidar can still process events, can crash, can mess up jest

After calling .close(), some async events might still be in some “pipeline”. Handling those and emitting events is unexpected to the user, but can also lead to exceptions or even crashes of the node vm. (Events can be queued anywhere from await based function blocks, in nodejs, in the kernel.)

For us this was painful because we use jest for testing, it removes the require function after testing is done, but readdirp calls require() and can be invoked after close().

But worse, it seems interfacing with fsevents after close() can lead to assertions from the c-code, these two I’ve seen (but not investigated):

  • Assertion failed: (napi_call_function(env, recv, callback, 3, args, &recv) == napi_ok), function fse_dispatch_events, file ../src/fsevents.c, line 45.
  • Assertion failed: (handle->flags & UV_HANDLE_CLOSING), function uv__finish_close, file ../deps/uv/src/unix/core.c, line 252.

Technically, chokidar should remove all its registered callbacks, abort all await based code blocks. Practically, a few early returns suffice. The below diff seems to solve all problems we had:

diff --git a/index.js b/index.js
index a79ba31..9ac49ac 100644
--- a/index.js
+++ b/index.js
@@ -501,6 +501,7 @@ emitWithAll(event, args) {
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
  */
 async _emit(event, path, val1, val2, val3) {
+  if (this.closed) return;
   const opts = this.options;
   if (opts.cwd) path = sysPath.relative(opts.cwd, path);
   /** @type Array<any> */
diff --git a/lib/fsevents-handler.js b/lib/fsevents-handler.js
index 7eba95c..c45ff70 100644
--- a/lib/fsevents-handler.js
+++ b/lib/fsevents-handler.js
@@ -221,6 +221,7 @@ async checkFd(path, fullPath, realPath, parent, watchedDir, item, info, opts) {
 }
 
 handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts) {
+  if (this.fsw.closed) return;
   if (this.checkIgnored(path)) return;
 
   if (event === 'unlink') {
@@ -329,6 +330,7 @@ _watchWithFsEvents(watchPath, realPath, transform, globFilter) {
  * @returns {void}
  */
 async _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) {
+  if (this.fsw.closed) return;
   // don't follow the same symlink more than once
   if (this.fsw._symlinkPaths.has(fullPath)) return;
 
@@ -403,6 +405,7 @@ initWatch(realPath, path, wh, processPath) {
  * @returns {void}
  */
 async _addToFsEvents(path, transform, forceAdd, priorDepth) {
+  if (this.fsw.closed) return;
   const opts = this.fsw.options;
   const processPath = typeof transform === 'function' ? transform : (val => val);
 
@@ -411,6 +414,7 @@ async _addToFsEvents(path, transform, forceAdd, priorDepth) {
   // evaluate what is at the path we're being asked to watch
   try {
     const stats = await statMethods[wh.statMethod](wh.watchPath);
+    if (this.fsw.closed) return;
     if (this.fsw._isIgnored(wh.watchPath, stats)) {
       throw null;
     }
@@ -427,6 +431,7 @@ async _addToFsEvents(path, transform, forceAdd, priorDepth) {
         directoryFilter: entry => wh.filterDir(entry),
         ...Option("depth", opts.depth - (priorDepth || 0))
       }).on('data', (entry) => {
+        if (this.fsw.closed) return;
         // need to check filterPath on dirs b/c filterDir is less restrictive
         if (entry.stats.isDirectory() && !wh.filterPath(entry)) return;

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (5 by maintainers)

Commits related to this issue

Most upvoted comments

This issue is still there on macOS 10.14.6!

I’m still seeing this issue in 3.0.2. Code was:

const watcher = chokidar.watch(file, { ignoreInitial: true });
const listener = () => {
  console.log('saw change');
  watcher.close();
};
watcher.on('change', listener);
watcher.on('unlink', listener);

On OSX 10.14.5

I’ll push something out ASAP.

Pushed 3.0.1.

@paulmillr also seeing this with our VSCode tests that start and stop chokidar instances after switching to Chokidar 3:

  Chokidar watching
    1) simple file operations, single root, no ignore
    2) simple file operations, ignore
    3) simple file operations, multiple roots
    ✓ simple file operations, nested roots (703ms)
Assertion failed: (napi_call_function(env, recv, callback, 3, args, &recv) == napi_ok), function fse_dispatch_events, file ../src/fsevents.c, line 45.

I’ve spent two days and found another cause of the process crash. Try 3.2.4 in a few days, or github version right now.

Important change: close() is now async, so you will need to do await close() for correct work.

cc @bpasero

Wondering what the plan is for releasing this fix? Hitting this issue and hope I can get my dev setup working again soon 😃