3

I have the following very simple Node project:

https://github.com/tlg-265/chokidar-issue

enter image description here

$ git clone https://github.com/tlg-265/chokidar-issue
$ cd chokidar-issue
$ npm i
$ npm run watch-changes

which basically takes care of detecting changes on file:

/profiles/bill-gates.json

and do an action just after that.

In order to do that I have the following file:

/profile-watcher.js

const fs = require('fs-extra');
const colors = require('colors/safe');
const chokidar = require('chokidar');

const path_file = `profiles/bill-gates.json`;
console.log(`Current Profile: ${colors.red.bgBrightYellow(path_file)}`);

let profile_before = {};

chokidar.watch(path_file).on('change', async (path) => {

  console.log();
  console.log(`${colors.blue.bgYellow(`->`)} Profile changed: ${path}`);

  fs.readFile(path, (err, profile_json) => {
    console.log(`->${profile_json}<-`);
    let profile = JSON.parse(profile_json);
    if (JSON.stringify(profile) != JSON.stringify(profile_before)) {
      console.log('The profile has changed.');
      profile_before = profile;
    }
  });

});

when I run the project with:

$ npm run watch-changes

and do the modifications below on file: /profiles/bill-gates.json

  • modification 1: Bill Gates -> Bill Gates ABC
  • modification 2: Bill Gates ABC -> Bill Gates ABC DEF

it works fine, outputting the content of this file to the console.

But when I do the next modification:

  • modification 3: Bill Gates ABC -> Bill Gates ABC DEF GHI

Then I get the following error:

-> Profile changed: profiles\bill-gates.json
-><-
undefined:1

SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at fs.readFile (\chokidar-issue\profile-watcher.js:17:24)
    at \chokidar-issue\node_modules\graceful-fs\graceful-fs.js:115:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (internal/fs/read_file_context.js:53:3)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! chokidar-issue@1.0.0 watch-changes: `node profile-watcher.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the chokidar-issue@1.0.0 watch-changes script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Roaming\npm-cache\_logs\2020-02-28T23_44_01_038Z-debug.log

/profiles/bill-gates.json (Flags: UTF-8 / CRLF)

{
  "name": "Bill Gates",
  "email": "bill.gates@microsoft.com",
  "password": "windows",
  "country": "USA"
}

By the way, if I change from CRLF to LF normally I can do few modifications more before it crashes.

I'm under the impression that for somre reason the file: /profiles/bill-gates.json gets locked at some point and when Node tries to read it it returns an empty string because it is locked.

Any idea on how to make this work without crashing after few tries?

Thanks!

Viewsonic
  • 827
  • 2
  • 15
  • 34
  • 1
    Can you post the contents of `/profiles/bill-gates.json`? First, remove everything that is in it, then do a fresh run, then post the contents. – Matt Oestreich Feb 29 '20 at 00:12

3 Answers3

3

I had the same problem as you.

There is an option in "chokidar" where you can awaitWriteFinish. It's time-based and checks if the size of the file is changing. If not, then it will call the callback.

const watcher = chokidar.watch(configPathString, { 
    persistent: true,
    awaitWriteFinish: {
        stabilityThreshold: 500
    } 
});
watcher.on('change', (path, stats) => {

    fs.readFile(configPathString,(err, data)=>{
        if (err) throw err;
        
        //console.log('data',data);

        let receivedData = JSON.parse(data);

        //Do whatever you like
    })
});
Alex Shroyer
  • 3,499
  • 2
  • 28
  • 54
0

It may be a race condition. Make your JSON.parse safe like this:

const path = require('path')

chokidar.watch(path_file).on('change', async (path) => {
  fs.readFile(path, 'utf8', (err, profile_json) => {
    if (!profile_json) {
      console.log(`${path} is an empty file!`)
      return
    }
    const profile = JSON.parse(profile_json);
    if (JSON.stringify(profile) != JSON.stringify(profile_before)) {
      console.log('The profile has changed.');
      profile_before = profile;
    }
  });

});
Josh Wulf
  • 4,727
  • 2
  • 20
  • 34
  • I just added `utf8` flag and the issue persisted. I also added your extra debugging logs (updated the repository) and it says the path exists. Also, bear in mind that the first 2 times I mentioned above it works properly but not third time. So why the path should be fine the first two times but not the third time? – Viewsonic Feb 29 '20 at 16:31
  • Unable to reproduce using your repo. `System Version: macOS 10.15.3 (19D76) Kernel Version: Darwin 19.3.0` – elight Feb 29 '20 at 19:42
0

I could make it work by adding some recovery fallback:

const fs = require('fs-extra');
const colors = require('colors/safe');
const chokidar = require('chokidar');
const sleep = require('sleep');

const path_file = `profiles/bill-gates.json`;
console.log(`Current Profile: ${colors.red.bgBrightYellow(path_file)}`);

let profile_before = fs.readFileSync(path_file).toString();

chokidar.watch(path_file).on('change', async (path_changed) => {
  let profile = fs.readFileSync(path_changed).toString();
  if (IsValidJson(profile)) {
    if (profile != profile_before) {
      console.log();
      console.log(`Profile changed: ${colors.red.bgBrightYellow(path_changed)}`);
      process_profile(profile);
      profile_before = profile;
    }
  }
  else {
    sleep.msleep(100); // this is necessary
  }
});

function process_profile(profile_json) {
  const profile = JSON.parse(profile_json);
  console.log(`${profile_json}`);
  console.log(profile.name);
}

function IsValidJson(str) {
  try {
    JSON.parse(str);
  } catch (e) {
    return false;
  }
  return true;
}

It seems that when you save a file (at least on Windows), sometimes there is a time in between (very very short time) that the file gets clear and few milliseconds later it gets the actual content. On both cases the on-change event gets fired. So, we just need to verify whether the content of the file is JSON or not. In that case I just need to ignore it and wait for the next on-change event.

Viewsonic
  • 827
  • 2
  • 15
  • 34