Skip to content

Commit 94e83f0

Browse files
authored
Merge pull request #127 from theia-ide/mp/linux-event-fix
linux: fix missing events
2 parents ad3ff06 + f34d5b1 commit 94e83f0

File tree

12 files changed

+518
-441
lines changed

12 files changed

+518
-441
lines changed

binding.gyp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@
6262
}
6363
}],
6464
["OS=='linux' or OS=='freebsd'", {
65+
"defines": [
66+
"NSFW_TEST_SLOW_<!(node -p process.env.NSFW_TEST_SLOW)"
67+
],
6568
"sources": [
6669
"src/linux/InotifyEventLoop.cpp",
6770
"src/linux/InotifyTree.cpp",
@@ -102,7 +105,7 @@
102105
],
103106
"libraries": [
104107
"-L/usr/local/lib",
105-
"-linotify"
108+
"-linotify"
106109
]
107110
}],
108111
]

includes/linux/InotifyTree.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@
1010
#include <vector>
1111
#include <map>
1212
#include <unordered_set>
13+
#include <functional>
14+
#include <chrono>
15+
#include <thread>
16+
17+
/**
18+
* void EmitCreatedEvent(std::string directory, std::string file);
19+
*/
20+
using EmitCreatedEvent = std::function<void(std::string, std::string)>;
1321

1422
class InotifyTree {
1523
public:
1624
InotifyTree(int inotifyInstance, std::string path);
1725

18-
void addDirectory(int wd, std::string name);
26+
void addDirectory(int wd, std::string name, EmitCreatedEvent emitCreatedEvent = nullptr);
1927
std::string getError();
2028
bool getPath(std::string &out, int wd);
2129
bool hasErrored();
@@ -34,10 +42,11 @@ class InotifyTree {
3442
InotifyNode *parent,
3543
std::string directory,
3644
std::string name,
37-
ino_t inodeNumber
45+
ino_t inodeNumber,
46+
EmitCreatedEvent emitCreatedEvent = nullptr
3847
);
3948

40-
void addChild(std::string name);
49+
void addChild(std::string name, EmitCreatedEvent emitCreatedEvent = nullptr);
4150
void fixPaths();
4251
std::string getFullPath();
4352
std::string getName();
@@ -60,7 +69,8 @@ class InotifyTree {
6069
| IN_MODIFY
6170
| IN_MOVED_FROM
6271
| IN_MOVED_TO
63-
| IN_DELETE_SELF;
72+
| IN_DELETE_SELF
73+
| IN_ONLYDIR;
6474

6575
bool mAlive;
6676
std::map<std::string, InotifyNode *> *mChildren;

js/scripts/test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const cp = require('child_process');
2+
3+
if (process.platform !== 'win32' && process.platform !== 'darwin') {
4+
// When ran as a npm script we can invoke npm bins such as node-gyp and mocha directly
5+
exec('node-gyp rebuild', { env: { ...process.env, NSFW_TEST_SLOW: 1 } });
6+
exec('mocha --exit --expose-gc js/spec/index-slow-spec.js');
7+
exec('node-gyp rebuild');
8+
}
9+
exec('mocha --exit --expose-gc js/spec/index-spec.js');
10+
11+
/**
12+
* @param {string} commandline ...
13+
* @param {cp.ExecSyncOptions} options ...
14+
* @returns {Promise<void>} ...
15+
*/
16+
function exec(commandline, options = {}) {
17+
cp.execSync(commandline, { stdio: 'inherit', ...options });
18+
}

js/spec/common.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const path = require('path');
2+
const util = require('util');
3+
4+
exports.DEBOUNCE = 1000;
5+
exports.TIMEOUT_PER_STEP = 3000;
6+
exports.WORKDIR = path.resolve(__dirname, '../../mockfs');
7+
8+
exports.sleep = util.promisify(setTimeout);

js/spec/index-slow-spec.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const assert = require('assert');
2+
const fse = require('fs-extra');
3+
const path = require('path');
4+
5+
const { DEBOUNCE, TIMEOUT_PER_STEP, WORKDIR: workDir, sleep } = require('./common');
6+
7+
const nsfw = require('../src');
8+
9+
describe('Node Sentinel File Watcher (slow)', function() {
10+
this.timeout(120000);
11+
12+
assert.ok(nsfw._native.NSFW_TEST_SLOW === true, 'NSFW should be built in slow mode');
13+
14+
beforeEach(function() {
15+
return fse.mkdir(workDir, { recursive: true });
16+
});
17+
18+
afterEach(function() {
19+
return fse.remove(workDir);
20+
});
21+
22+
it('can listen for the creation of a deeply nested file', async function () {
23+
const folders = 'a_very_deep_tree'.split(''); // ['a', '_', ...]
24+
const file = 'a_file.txt';
25+
26+
// Resolve a promise as soon as all events have been found
27+
let done; const promise = new Promise(resolve => {
28+
done = resolve;
29+
});
30+
31+
/**
32+
* We will remove or pop entries as we find them.
33+
* This set should be empty at the end of the test.
34+
* @type {Set<string>}
35+
*/
36+
const events = new Set();
37+
let directory = workDir; for (const folder of folders) {
38+
directory = path.join(directory, folder);
39+
events.add(directory);
40+
}
41+
const filePath = path.join(directory, file);
42+
events.add(filePath);
43+
44+
function findEvent(element) {
45+
if (element.action === nsfw.actions.CREATED) {
46+
const file = path.join(element.directory, element.file);
47+
if (events.has(file)) {
48+
events.delete(file);
49+
if (events.size === 0) {
50+
done();
51+
}
52+
}
53+
}
54+
}
55+
56+
let watch = await nsfw(
57+
workDir,
58+
events => events.forEach(findEvent),
59+
{ debounceMS: DEBOUNCE }
60+
);
61+
62+
try {
63+
await watch.start();
64+
await sleep(TIMEOUT_PER_STEP);
65+
await fse.mkdirp(directory);
66+
await fse.close(await fse.open(filePath, 'w'));
67+
await Promise.race([
68+
sleep(folders.length * 500 * 1.5),
69+
promise,
70+
]);
71+
72+
// Make sure that we got the events for each nested directory
73+
assert.ok(events.size === 0, `Some files were not detected:\n${
74+
Array.from(events, file => `- ${file}`).join('\n')
75+
}`);
76+
} finally {
77+
await watch.stop();
78+
watch = null;
79+
}
80+
});
81+
});

js/spec/index-spec.js

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@ const assert = require('assert');
22
const exec = require('executive');
33
const fse = require('fs-extra');
44
const path = require('path');
5-
const { promisify } = require('util');
65

7-
const nsfw = require('../src/');
6+
const { DEBOUNCE, TIMEOUT_PER_STEP, WORKDIR: workDir, sleep } = require('./common');
87

9-
const DEBOUNCE = 1000;
10-
const TIMEOUT_PER_STEP = 3000;
11-
12-
const sleep = promisify(setTimeout);
8+
const nsfw = require('../src');
139

1410
describe('Node Sentinel File Watcher', function() {
1511
this.timeout(120000);
1612

17-
const workDir = path.resolve('./mockfs');
13+
assert.ok(typeof nsfw._native.NSFW_TEST_SLOW === 'undefined', 'NSFW should NOT be built in slow mode');
1814

1915
beforeEach(async function() {
2016
async function makeDir(identifier) {
@@ -438,47 +434,6 @@ describe('Node Sentinel File Watcher', function() {
438434
});
439435

440436
describe('Recursive', function() {
441-
it('can listen for the creation of a deeply nested file', async function() {
442-
const paths = ['d', 'e', 'e', 'p', 'f', 'o', 'l', 'd', 'e', 'r'];
443-
const file = 'a_file.txt';
444-
let foundFileCreateEvent = false;
445-
446-
function findEvent(element) {
447-
if (
448-
element.action === nsfw.actions.CREATED &&
449-
element.directory === path.join(workDir, ...paths) &&
450-
element.file === file
451-
) {
452-
foundFileCreateEvent = true;
453-
}
454-
}
455-
456-
let watch = await nsfw(
457-
workDir,
458-
events => events.forEach(findEvent),
459-
{ debounceMS: DEBOUNCE }
460-
);
461-
462-
try {
463-
await watch.start();
464-
await sleep(TIMEOUT_PER_STEP);
465-
let directory = workDir;
466-
for (const dir of paths) {
467-
directory = path.join(directory, dir);
468-
await fse.mkdir(directory);
469-
await sleep(60);
470-
}
471-
const fd = await fse.open(path.join(directory, file), 'w');
472-
await fse.close(fd);
473-
await sleep(TIMEOUT_PER_STEP);
474-
475-
assert.ok(foundFileCreateEvent);
476-
} finally {
477-
await watch.stop();
478-
watch = null;
479-
}
480-
});
481-
482437
it('can listen for the destruction of a directory and its subtree', async function() {
483438
const inPath = path.resolve(workDir, 'test4');
484439
let deletionCount = 0;

js/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ nsfw.actions = {
102102
RENAMED: 3
103103
};
104104

105+
nsfw._native = NSFW;
106+
105107
if (NSFW.getAllocatedInstanceCount) {
106108
nsfw.getAllocatedInstanceCount = NSFW.getAllocatedInstanceCount;
107109
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "js/src/index.js",
66
"scripts": {
77
"lint": "eslint js/src js/spec",
8-
"test": "yarn lint && mocha --expose-gc js/spec"
8+
"test": "yarn lint && node js/scripts/test.js"
99
},
1010
"repository": {
1111
"type": "git",

src/NSFW.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,13 @@ Napi::Object NSFW::Init(Napi::Env env, Napi::Object exports) {
339339
));
340340
}
341341

342+
#ifdef NSFW_TEST_SLOW_1
343+
nsfwConstructor.DefineProperty(Napi::PropertyDescriptor::Value(
344+
"NSFW_TEST_SLOW",
345+
Napi::Boolean::New(env, true)
346+
));
347+
#endif
348+
342349
constructor = Napi::Persistent(nsfwConstructor);
343350
constructor.SuppressDestruct();
344351

src/linux/InotifyService.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ void InotifyService::createDirectory(int wd, std::string name) {
9898
return;
9999
}
100100

101-
mTree->addDirectory(wd, name);
102101
dispatch(CREATED, wd, name);
102+
mTree->addDirectory(wd, name, [this](std::string directory, std::string file) {
103+
mQueue->enqueue(CREATED, directory, file);
104+
});
103105
}
104106

105107
void InotifyService::removeDirectory(int wd) {

0 commit comments

Comments
 (0)