Skip to content

Commit 5e08354

Browse files
committed
emit CREATED event for new directories
Most clients expect every new file from a new tree being added in the watched directory to be reported. This is currently not the case if the tree appears too quickly on disk. This commit propagates a callback down the recursive tree building to make sure that all files added are being reported as created to clients. I also added an environment variable check for use when testing: `NSFW_TEST_INOTIFYNODE_SLEEP`. When set to `1` nsfw will artificially sleep before crawling folders on the fs. This is to simulate the case when nsfw is late to the party and sub-folders where created before we had time to allocate inotify watchers. In such a case, we need to manually emit CREATED events for new files and folders discovered by scandir. Signed-off-by: Paul Maréchal <[email protected]>
1 parent a9c0472 commit 5e08354

File tree

4 files changed

+64
-14
lines changed

4 files changed

+64
-14
lines changed

includes/linux/InotifyTree.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,29 @@
1010
#include <vector>
1111
#include <map>
1212
#include <unordered_set>
13+
#include <functional>
14+
#include <chrono>
15+
#include <thread>
16+
17+
/**
18+
* Environment variable key for testing.
19+
*
20+
* When set to "1", it will trigger a sleep in every InotifyNode constructor.
21+
* This will allow nsfw to miss creation events, and instead we'll have to rely
22+
* on the manual event emitting mechanism. Used by tests.
23+
*/
24+
#define NSFW_TEST_INOTIFYNODE_SLEEP "NSFW_TEST_INOTIFYNODE_SLEEP"
25+
26+
/**
27+
* void EmitCreatedEvent(std::string directory, std::string file);
28+
*/
29+
using EmitCreatedEvent = std::function<void(std::string, std::string)>;
1330

1431
class InotifyTree {
1532
public:
1633
InotifyTree(int inotifyInstance, std::string path);
1734

18-
void addDirectory(int wd, std::string name);
35+
void addDirectory(int wd, std::string name, EmitCreatedEvent emitCreatedEvent = nullptr);
1936
std::string getError();
2037
bool getPath(std::string &out, int wd);
2138
bool hasErrored();
@@ -34,10 +51,12 @@ class InotifyTree {
3451
InotifyNode *parent,
3552
std::string directory,
3653
std::string name,
37-
ino_t inodeNumber
54+
ino_t inodeNumber,
55+
bool testSleep = false,
56+
EmitCreatedEvent emitCreatedEvent = nullptr
3857
);
3958

40-
void addChild(std::string name);
59+
void addChild(std::string name, bool testSleep = false, EmitCreatedEvent emitCreatedEvent = nullptr);
4160
void fixPaths();
4261
std::string getFullPath();
4362
std::string getName();
@@ -86,6 +105,7 @@ class InotifyTree {
86105
std::map<int, InotifyNode *> *mInotifyNodeByWatchDescriptor;
87106
std::unordered_set<ino_t> inodes;
88107
InotifyNode *mRoot;
108+
bool mTestSleep; // meant for testing
89109

90110
friend class InotifyNode;
91111
};

js/spec/index-spec.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ describe('Node Sentinel File Watcher', function() {
442442
const folders = 'a_very_deep_tree'.split(''); // ['a', '_', ...]
443443
const file = 'a_file.txt';
444444

445+
/** @type {Map<string, { file: string, found: boolean }>} */
445446
const events = new Map();
446447
let directory = workDir; for (const folder of folders) {
447448
events.set(directory, { file: folder, found: false });
@@ -459,6 +460,10 @@ describe('Node Sentinel File Watcher', function() {
459460
}
460461
}
461462

463+
// Force nsfw to sleep and hence miss creation events.
464+
// This makes sure we test the right mechanism that should manually
465+
// fire creation events when nested folders are added at once.
466+
process.env['NSFW_TEST_INOTIFYNODE_SLEEP'] = '1';
462467
let watch = await nsfw(
463468
workDir,
464469
events => events.forEach(findEvent),
@@ -470,17 +475,18 @@ describe('Node Sentinel File Watcher', function() {
470475
await sleep(TIMEOUT_PER_STEP);
471476
await fse.mkdirp(directory);
472477
await fse.close(await fse.open(path.join(directory, file), 'w'));
473-
await sleep(TIMEOUT_PER_STEP);
478+
await sleep(folders.length * 200 * 1.5);
474479

480+
// Make sure that we got the events for each nested directory
475481
for (const [directory, item] of events.entries()) {
476482
assert.ok(item.found, `${path.join(directory, item.file)} wasn't found`);
477483
}
478484
} finally {
485+
delete process.env['NSFW_TEST_INOTIFYNODE_SLEEP'];
479486
await watch.stop();
480487
watch = null;
481488
}
482489
});
483-
484490
it('can listen for the destruction of a directory and its subtree', async function() {
485491
const inPath = path.resolve(workDir, 'test4');
486492
let deletionCount = 0;

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) {

src/linux/InotifyTree.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#include "../../includes/linux/InotifyTree.h"
2+
#include <cstdio>
3+
#include <sys/stat.h>
24
/**
35
* InotifyTree ---------------------------------------------------------------------------------------------------------
46
*/
57
InotifyTree::InotifyTree(int inotifyInstance, std::string path):
68
mError(""),
79
mInotifyInstance(inotifyInstance) {
810
mInotifyNodeByWatchDescriptor = new std::map<int, InotifyNode *>;
9-
1011
std::string directory;
1112
std::string watchName;
1213
if (path.length() == 1 && path[0] == '/') {
@@ -24,14 +25,21 @@ InotifyTree::InotifyTree(int inotifyInstance, std::string path):
2425
return;
2526
}
2627

28+
char *_test_sleep_env = std::getenv(NSFW_TEST_INOTIFYNODE_SLEEP);
29+
mTestSleep = _test_sleep_env != nullptr && std::string(_test_sleep_env) == "1";
30+
if (mTestSleep) {
31+
printf("WARNING: test sleep enabled!\n");
32+
}
33+
2734
addInode(file.st_ino);
2835
mRoot = new InotifyNode(
2936
this,
3037
mInotifyInstance,
3138
NULL,
3239
directory,
3340
watchName,
34-
file.st_ino
41+
file.st_ino,
42+
mTestSleep
3543
);
3644

3745
if (!mRoot->isAlive()) {
@@ -41,14 +49,14 @@ InotifyTree::InotifyTree(int inotifyInstance, std::string path):
4149
}
4250
}
4351

44-
void InotifyTree::addDirectory(int wd, std::string name) {
52+
void InotifyTree::addDirectory(int wd, std::string name, EmitCreatedEvent emitCreatedEvent) {
4553
auto nodeIterator = mInotifyNodeByWatchDescriptor->find(wd);
4654
if (nodeIterator == mInotifyNodeByWatchDescriptor->end()) {
4755
return;
4856
}
4957

5058
InotifyNode *node = nodeIterator->second;
51-
node->addChild(name);
59+
node->addChild(name, mTestSleep, emitCreatedEvent);
5260
}
5361

5462
void InotifyTree::addNodeReferenceByWD(int wd, InotifyNode *node) {
@@ -159,7 +167,9 @@ InotifyTree::InotifyNode::InotifyNode(
159167
InotifyNode *parent,
160168
std::string directory,
161169
std::string name,
162-
ino_t inodeNumber
170+
ino_t inodeNumber,
171+
bool testSleep,
172+
EmitCreatedEvent emitCreatedEvent
163173
):
164174
mDirectory(directory),
165175
mInodeNumber(inodeNumber),
@@ -171,6 +181,10 @@ InotifyTree::InotifyNode::InotifyNode(
171181
mFullPath = createFullPath(mDirectory, mName);
172182
mWatchDescriptorInitialized = false;
173183

184+
if (testSleep) {
185+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
186+
}
187+
174188
if (!inotifyInit()) {
175189
return;
176190
}
@@ -200,6 +214,10 @@ InotifyTree::InotifyNode::InotifyNode(
200214
continue;
201215
}
202216

217+
if (emitCreatedEvent) {
218+
emitCreatedEvent(mFullPath, fileName);
219+
}
220+
203221
std::string filePath = createFullPath(mFullPath, fileName);
204222

205223
struct stat file;
@@ -218,7 +236,9 @@ InotifyTree::InotifyNode::InotifyNode(
218236
this,
219237
mFullPath,
220238
fileName,
221-
file.st_ino
239+
file.st_ino,
240+
testSleep,
241+
emitCreatedEvent
222242
);
223243

224244
if (child->isAlive()) {
@@ -250,7 +270,7 @@ InotifyTree::InotifyNode::~InotifyNode() {
250270
delete mChildren;
251271
}
252272

253-
void InotifyTree::InotifyNode::addChild(std::string name) {
273+
void InotifyTree::InotifyNode::addChild(std::string name, bool testSleep, EmitCreatedEvent emitCreatedEvent) {
254274
struct stat file;
255275

256276
if (stat(createFullPath(mFullPath, name).c_str(), &file) >= 0 && mTree->addInode(file.st_ino)) {
@@ -260,7 +280,9 @@ void InotifyTree::InotifyNode::addChild(std::string name) {
260280
this,
261281
mFullPath,
262282
name,
263-
file.st_ino
283+
file.st_ino,
284+
testSleep,
285+
emitCreatedEvent
264286
);
265287

266288
if (child->isAlive()) {

0 commit comments

Comments
 (0)