Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Watcher implementations #2

Merged
merged 54 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b856381
Turn backends into classes
devongovett Mar 17, 2019
8556eb6
wip: watcher
devongovett Mar 18, 2019
b511f53
Backend registry
devongovett Mar 18, 2019
5b2af55
Reuse backends and watchers
devongovett Mar 19, 2019
4cdbe2a
Correctly handle adding and removing callbacks while iterating
devongovett Mar 19, 2019
e1438eb
get non-subscriptions working again
devongovett Mar 19, 2019
e5d1556
Fix crash
devongovett Mar 20, 2019
74bb50a
Watchman watcher backend
devongovett Mar 20, 2019
778443c
Get brute force backend working again
devongovett Mar 20, 2019
bd9b5b8
Fix watchman check and add better error handling
devongovett Mar 20, 2019
4f6e501
Make getDirTree part of BruteForceBackend
devongovett Mar 20, 2019
631f10d
Windows backend
devongovett Mar 21, 2019
63f3cd3
Add debounce implementation
devongovett Mar 21, 2019
80c4d8a
Compile on linux gcc
devongovett Mar 23, 2019
23d2981
Add inotify backend
devongovett Mar 23, 2019
258d37a
Fix issues with closing uv handles
devongovett Mar 25, 2019
74d5d59
Coalesce events, add tests
devongovett Mar 25, 2019
e322036
Fix windows tests
devongovett Mar 25, 2019
24b6546
Init azure pipelines
devongovett Mar 25, 2019
0287048
Install watchman
devongovett Mar 25, 2019
88d13e1
update?
devongovett Mar 25, 2019
800a35a
Newer gcc?
devongovett Mar 25, 2019
f095a97
nope
devongovett Mar 25, 2019
98b438b
arg
devongovett Mar 25, 2019
4425098
still no?
devongovett Mar 25, 2019
6a19a8d
please work
devongovett Mar 25, 2019
a5ae481
🤷‍♂️
devongovett Mar 25, 2019
0c7f71a
grrr
devongovett Mar 25, 2019
4a8346c
try including stdlib.h first
devongovett Mar 25, 2019
2068beb
maybe no stat.h?
devongovett Mar 25, 2019
efa2dc2
attempt workaround
devongovett Mar 25, 2019
8164c18
Ignore update events for directories
devongovett Mar 25, 2019
0392bca
Attempt watchman container on linux
devongovett Mar 25, 2019
af8d556
nope
devongovett Mar 25, 2019
1ff2e5b
hmm
devongovett Mar 25, 2019
4eae6ff
latest
devongovett Mar 25, 2019
ebb86aa
Just use npm since yarn is not in the watchman container
devongovett Mar 26, 2019
2dd0b59
Remove watchman log
devongovett Mar 26, 2019
189eb70
Use mode instead of type for watchman since type isn't available in s…
devongovett Mar 26, 2019
52cdf6b
Deal with fsevents latency
devongovett Mar 27, 2019
a356fda
Store isDir in the snapshot
devongovett Mar 27, 2019
ceb1e82
Use nanosecond precision for mtimes
devongovett Mar 27, 2019
57f697e
Add tests for snapshots and getting historical events
devongovett Mar 27, 2019
6932e3a
Add more latency for CI
devongovett Mar 27, 2019
5e22682
attempt
devongovett Mar 27, 2019
a94d966
oops
devongovett Mar 27, 2019
bebbe50
Attempt tmpdir
devongovett Mar 28, 2019
3a95f5c
realpath
devongovett Mar 28, 2019
dd90659
Test for second mtime precision
devongovett Mar 28, 2019
33b0e63
Don't wait for emptydir
devongovett Mar 28, 2019
3a3a795
Use tmpdir for watcher tests as well
devongovett Mar 28, 2019
d2b00bc
Use shutdown instead of close for socket
devongovett Mar 29, 2019
c813de5
Add flag to signal so it is under lock
devongovett Mar 29, 2019
b5f8a3a
Don't await before nextEvent
devongovett Mar 29, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions azure-pipelines-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
jobs:
- job: ${{ parameters.name }}
pool:
vmImage: ${{ parameters.vmImage }}
${{ if eq(parameters.name, 'Linux') }}:
container: jotadrilo/watchman:latest
strategy:
matrix:
node_8_x:
node_version: 8.x
node_10_x:
node_version: 10.x
maxParallel: 3
steps:
- task: NodeTool@0
inputs:
versionSpec: $(node_version)
displayName: 'Install Node.js'

# Install Watchman
- ${{ if eq(parameters.name, 'macOS') }}:
- script: |
brew update
brew install watchman
displayName: Install Watchman

- script: npm install
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run tests'
15 changes: 15 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
jobs:
- template: azure-pipelines-template.yml
parameters:
name: macOS
vmImage: macOS-10.13

- template: azure-pipelines-template.yml
parameters:
name: Linux
vmImage: ubuntu-16.04

- template: azure-pipelines-template.yml
parameters:
name: Windows
vmImage: vs2017-win2016
24 changes: 16 additions & 8 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
{
"target_name": "fschanges",
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"sources": [ "src/FSChanges.cc" ],
"sources": [ "src/FSChanges.cc", "src/Watcher.cc", "src/Backend.cc" ],
"include_dirs" : ["<!@(node -p \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"cflags!": ["-fexceptions"],
"cflags_cc!": ["-fexceptions"],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
"conditions": [
['OS=="mac"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/watchman.cc",
"src/shared/brute.cc",
"src/shared/BruteForceBackend.cc",
"src/unix/fts.cc",
"src/macos/FSEvents.cc"
],
Expand All @@ -33,22 +33,30 @@
"sources": [
"src/watchman/BSER.cc",
"src/watchman/watchman.cc",
"src/shared/brute.cc",
"src/shared/BruteForceBackend.cc",
"src/linux/InotifyBackend.cc",
"src/unix/fts.cc"
],
"defines": [
"WATCHMAN",
"INOTIFY",
"BRUTE_FORCE"
]
}],
['OS=="win"', {
"sources": [
"src/shared/brute.cc",
"src/windows/win.cc"
"src/shared/BruteForceBackend.cc",
"src/windows/WindowsBackend.cc"
],
"defines": [
"WINDOWS",
"BRUTE_FORCE"
]
],
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1, # /EHsc
}
}
}]
]
}
Expand Down
17 changes: 16 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@ async function run() {
console.timeEnd('write');
}

run();
// run();

let fn = events => {
console.log(events);
// fschanges.unsubscribe(dir, fn, {ignore: [dir + '/.git']});
};

fschanges.subscribe(dir, fn, {ignore: [dir + '/.git']});

// let w = new Watcher(dir);
// w.getEventsSince(snapshotPath);
// w.writeSnapshot(snapshotPath);
// w.subscribe(events => {

// });
// w.unsubscribe();
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"main": "build/Release/fschanges.node",
"scripts": {
"test": "mocha"
},
"dependencies": {
"node-addon-api": "^1.6.2"
},
"devDependencies": {
"fs-extra": "^7.0.1",
"mocha": "^6.0.2"
}
}
129 changes: 129 additions & 0 deletions src/Backend.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#ifdef FS_EVENTS
#include "macos/FSEvents.hh"
#endif
#ifdef WATCHMAN
#include "watchman/watchman.hh"
#endif
#ifdef WINDOWS
#include "windows/WindowsBackend.hh"
#endif
#ifdef INOTIFY
#include "linux/InotifyBackend.hh"
#endif
#include "shared/BruteForceBackend.hh"

#include "Backend.hh"
#include <unordered_map>

static std::unordered_map<std::string, std::shared_ptr<Backend>> sharedBackends;

std::shared_ptr<Backend> getBackend(std::string backend) {
// Use FSEvents on macOS by default.
// Use watchman by default if available on other platforms.
// Fall back to brute force.
#ifdef FS_EVENTS
if (backend == "fs-events" || backend == "default") {
return std::make_shared<FSEventsBackend>();
}
#endif
#ifdef WATCHMAN
if ((backend == "watchman" || backend == "default") && WatchmanBackend::checkAvailable()) {
return std::make_shared<WatchmanBackend>();
}
#endif
#ifdef WINDOWS
if (backend == "windows" || backend == "default") {
return std::make_shared<WindowsBackend>();
}
#endif
#ifdef INOTIFY
if (backend == "inotify" || backend == "default") {
return std::make_shared<InotifyBackend>();
}
#endif
if (backend == "brute-force" || backend == "default") {
return std::make_shared<BruteForceBackend>();
}

return nullptr;
}

std::shared_ptr<Backend> Backend::getShared(std::string backend) {
auto found = sharedBackends.find(backend);
if (found != sharedBackends.end()) {
return found->second;
}

auto result = getBackend(backend);
if (!result) {
return getShared("default");
}

result->run();
sharedBackends.emplace(backend, result);
return result;
}

void removeShared(Backend *backend) {
for (auto it = sharedBackends.begin(); it != sharedBackends.end(); it++) {
if (it->second.get() == backend) {
sharedBackends.erase(it);
break;
}
}
}

void Backend::run() {
mThread = std::thread([this] () {
start();
});

if (mThread.joinable()) {
mStartedSignal.wait();
}
}

void Backend::notifyStarted() {
mStartedSignal.notify();
}

void Backend::start() {
notifyStarted();
}

Backend::~Backend() {
std::unique_lock<std::mutex> lock(mMutex);

// Unwatch all subscriptions so that their state gets cleaned up
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end(); it++) {
unwatch(**it);
}

// Wait for thread to stop
if (mThread.joinable()) {
mThread.join();
}
}

void Backend::watch(Watcher &watcher) {
std::unique_lock<std::mutex> lock(mMutex);
auto res = mSubscriptions.insert(&watcher);
if (res.second) {
this->subscribe(watcher);
}
}

void Backend::unwatch(Watcher &watcher) {
std::unique_lock<std::mutex> lock(mMutex);
size_t deleted = mSubscriptions.erase(&watcher);
if (deleted > 0) {
this->unsubscribe(watcher);
unref();
}
}

void Backend::unref() {
if (mSubscriptions.size() == 0) {
removeShared(this);
}
}
57 changes: 24 additions & 33 deletions src/Backend.hh
Original file line number Diff line number Diff line change
@@ -1,43 +1,34 @@
#ifndef BACKEND_H
#define BACKEND_H

#include "./Event.hh"
#include "Event.hh"
#include "Watcher.hh"
#include "Signal.hh"
#include <thread>

#ifdef FS_EVENTS
struct FSEventsBackend {
static void writeSnapshot(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
static EventList *getEventsSince(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
};
#endif
class Backend {
public:
virtual ~Backend();
void run();
void notifyStarted();

#ifdef WATCHMAN
struct WatchmanBackend {
static bool check();
static void writeSnapshot(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
static EventList *getEventsSince(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
};
#endif
virtual void start();
virtual void writeSnapshot(Watcher &watcher, std::string *snapshotPath) = 0;
virtual void getEventsSince(Watcher &watcher, std::string *snapshotPath) = 0;
virtual void subscribe(Watcher &watcher) = 0;
virtual void unsubscribe(Watcher &watcher) = 0;

struct BruteForceBackend {
static void writeSnapshot(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
static EventList *getEventsSince(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
};
static std::shared_ptr<Backend> getShared(std::string backend);

// Use FSEvents on macOS by default.
// Use watchman by default if available on other platforms.
// Fall back to brute force.
#ifdef FS_EVENTS
#define DEFAULT_BACKEND(method) FSEventsBackend::method
#elif WATCHMAN
#define DEFAULT_BACKEND(method) (WatchmanBackend::check() ? WatchmanBackend::method : BruteForceBackend::method)
#else
#define DEFAULT_BACKEND(method) BruteForceBackend::method
#endif
void watch(Watcher &watcher);
void unwatch(Watcher &watcher);
void unref();

#define GET_BACKEND(backend, method) \
(backend == "watchman" && WATCHMAN && WatchmanBackend::check() ? WatchmanBackend::method \
: backend == "fs-events" && FS_EVENTS ? FSEventsBackend::method \
: backend == "brute-force" ? BruteForceBackend::method \
: DEFAULT_BACKEND(method))
std::mutex mMutex;
std::thread mThread;
private:
std::unordered_set<Watcher *> mSubscriptions;
Signal mStartedSignal;
};

#endif
Loading