diff --git a/src/Backend.cc b/src/Backend.cc index 042b304..fcf5544 100644 --- a/src/Backend.cc +++ b/src/Backend.cc @@ -138,13 +138,13 @@ Backend::~Backend() { #endif } -void Backend::watch(Watcher &watcher) { +void Backend::watch(WatcherRef watcher) { std::unique_lock lock(mMutex); - auto res = mSubscriptions.find(&watcher); + auto res = mSubscriptions.find(watcher); if (res == mSubscriptions.end()) { try { this->subscribe(watcher); - mSubscriptions.insert(&watcher); + mSubscriptions.insert(watcher); } catch (std::exception &err) { unref(); throw; @@ -152,9 +152,9 @@ void Backend::watch(Watcher &watcher) { } } -void Backend::unwatch(Watcher &watcher) { +void Backend::unwatch(WatcherRef watcher) { std::unique_lock lock(mMutex); - size_t deleted = mSubscriptions.erase(&watcher); + size_t deleted = mSubscriptions.erase(watcher); if (deleted > 0) { this->unsubscribe(watcher); unref(); @@ -168,7 +168,7 @@ void Backend::unref() { } void Backend::handleWatcherError(WatcherError &err) { - unwatch(*err.mWatcher); + unwatch(err.mWatcher); err.mWatcher->notifyError(err); } diff --git a/src/Backend.hh b/src/Backend.hh index cb940df..d673bd1 100644 --- a/src/Backend.hh +++ b/src/Backend.hh @@ -13,22 +13,22 @@ public: void notifyStarted(); 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; + virtual void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) = 0; + virtual void getEventsSince(WatcherRef watcher, std::string *snapshotPath) = 0; + virtual void subscribe(WatcherRef watcher) = 0; + virtual void unsubscribe(WatcherRef watcher) = 0; static std::shared_ptr getShared(std::string backend); - void watch(Watcher &watcher); - void unwatch(Watcher &watcher); + void watch(WatcherRef watcher); + void unwatch(WatcherRef watcher); void unref(); void handleWatcherError(WatcherError &err); std::mutex mMutex; std::thread mThread; private: - std::unordered_set mSubscriptions; + std::unordered_set mSubscriptions; Signal mStartedSignal; void handleError(std::exception &err); diff --git a/src/Watcher.cc b/src/Watcher.cc index 36c85b2..abb7ae6 100644 --- a/src/Watcher.cc +++ b/src/Watcher.cc @@ -4,21 +4,21 @@ using namespace Napi; struct WatcherHash { - std::size_t operator() (std::shared_ptr const &k) const { + std::size_t operator() (WatcherRef const &k) const { return std::hash()(k->mDir); } }; struct WatcherCompare { - size_t operator() (std::shared_ptr const &a, std::shared_ptr const &b) const { + size_t operator() (WatcherRef const &a, WatcherRef const &b) const { return *a == *b; } }; -static std::unordered_set, WatcherHash, WatcherCompare> sharedWatchers; +static std::unordered_set sharedWatchers; -std::shared_ptr Watcher::getShared(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs) { - std::shared_ptr watcher = std::make_shared(dir, ignorePaths, ignoreGlobs); +WatcherRef Watcher::getShared(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs) { + WatcherRef watcher = std::make_shared(dir, ignorePaths, ignoreGlobs); auto found = sharedWatchers.find(watcher); if (found != sharedWatchers.end()) { return *found; diff --git a/src/Watcher.hh b/src/Watcher.hh index ad39e39..f89e9f5 100644 --- a/src/Watcher.hh +++ b/src/Watcher.hh @@ -13,18 +13,26 @@ using namespace Napi; +struct Watcher; +using WatcherRef = std::shared_ptr; + struct Callback { Napi::ThreadSafeFunction tsfn; Napi::FunctionReference ref; std::thread::id threadId; }; +class WatcherState { +public: + virtual ~WatcherState() = default; +}; + struct Watcher { std::string mDir; std::unordered_set mIgnorePaths; std::unordered_set mIgnoreGlobs; EventList mEvents; - void *state; + std::shared_ptr state; Watcher(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs); ~Watcher(); @@ -42,7 +50,7 @@ struct Watcher { bool isIgnored(std::string path); void destroy(); - static std::shared_ptr getShared(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs); + static WatcherRef getShared(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs); private: std::mutex mMutex; @@ -57,9 +65,9 @@ private: class WatcherError : public std::runtime_error { public: - Watcher *mWatcher; - WatcherError(std::string msg, Watcher *watcher) : std::runtime_error(msg), mWatcher(watcher) {} - WatcherError(const char *msg, Watcher *watcher) : std::runtime_error(msg), mWatcher(watcher) {} + WatcherRef mWatcher; + WatcherError(std::string msg, WatcherRef watcher) : std::runtime_error(msg), mWatcher(watcher) {} + WatcherError(const char *msg, WatcherRef watcher) : std::runtime_error(msg), mWatcher(watcher) {} }; #endif diff --git a/src/binding.cc b/src/binding.cc index 18e757e..c478ca3 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -79,11 +79,11 @@ class WriteSnapshotRunner : public PromiseRunner { } private: std::shared_ptr backend; - std::shared_ptr watcher; + WatcherRef watcher; std::string snapshotPath; void execute() override { - backend->writeSnapshot(*watcher, &snapshotPath); + backend->writeSnapshot(watcher, &snapshotPath); } }; @@ -107,11 +107,11 @@ class GetEventsSinceRunner : public PromiseRunner { } private: std::shared_ptr backend; - std::shared_ptr watcher; + WatcherRef watcher; std::string snapshotPath; void execute() override { - backend->getEventsSince(*watcher, &snapshotPath); + backend->getEventsSince(watcher, &snapshotPath); } Value getResult() override { @@ -169,13 +169,13 @@ class SubscribeRunner : public PromiseRunner { } private: - std::shared_ptr watcher; + WatcherRef watcher; std::shared_ptr backend; FunctionReference callback; void execute() override { try { - backend->watch(*watcher); + backend->watch(watcher); } catch (std::exception &err) { watcher->destroy(); throw; @@ -197,13 +197,13 @@ class UnsubscribeRunner : public PromiseRunner { } private: - std::shared_ptr watcher; + WatcherRef watcher; std::shared_ptr backend; bool shouldUnwatch; void execute() override { if (shouldUnwatch) { - backend->unwatch(*watcher); + backend->unwatch(watcher); } } }; diff --git a/src/kqueue/KqueueBackend.cc b/src/kqueue/KqueueBackend.cc index 5a9ff8e..1f0b26f 100644 --- a/src/kqueue/KqueueBackend.cc +++ b/src/kqueue/KqueueBackend.cc @@ -57,7 +57,7 @@ void KqueueBackend::start() { } // Track all of the watchers that are touched so we can notify them at the end of the events. - std::unordered_set watchers; + std::unordered_set watchers; for (int i = 0; i < event_count; i++) { int flags = events[i].fflags; @@ -118,20 +118,20 @@ KqueueBackend::~KqueueBackend() { mEndedSignal.wait(); } -void KqueueBackend::subscribe(Watcher &watcher) { +void KqueueBackend::subscribe(WatcherRef watcher) { // Build a full directory tree recursively, and watch each directory. std::shared_ptr tree = getTree(watcher); for (auto it = tree->entries.begin(); it != tree->entries.end(); it++) { bool success = watchDir(watcher, it->second.path, tree); if (!success) { - throw WatcherError(std::string("error watching " + watcher.mDir + ": " + strerror(errno)), &watcher); + throw WatcherError(std::string("error watching " + watcher->mDir + ": " + strerror(errno)), watcher); } } } -bool KqueueBackend::watchDir(Watcher &watcher, std::string path, std::shared_ptr tree) { - if (watcher.isIgnored(path)) { +bool KqueueBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr tree) { + if (watcher->isIgnored(path)) { return false; } @@ -141,7 +141,7 @@ bool KqueueBackend::watchDir(Watcher &watcher, std::string path, std::shared_ptr } KqueueSubscription sub = { - .watcher = &watcher, + .watcher = watcher, .path = path, .tree = tree }; @@ -189,7 +189,7 @@ std::vector KqueueBackend::findSubscriptions(std::string & return subs; } -bool KqueueBackend::compareDir(int fd, std::string &path, std::unordered_set &watchers) { +bool KqueueBackend::compareDir(int fd, std::string &path, std::unordered_set &watchers) { // macOS doesn't support fdclosedir, so we have to duplicate the file descriptor // to ensure the closedir doesn't also stop watching. #if __APPLE__ @@ -240,7 +240,7 @@ bool KqueueBackend::compareDir(int fd, std::string &path, std::unordered_setwatcher->mEvents.create(fullpath); watchers.emplace(sub->watcher); - bool success = watchDir(*sub->watcher, fullpath, sub->tree); + bool success = watchDir(sub->watcher, fullpath, sub->tree); if (!success) { sub->tree->remove(fullpath); return false; @@ -289,10 +289,10 @@ bool KqueueBackend::compareDir(int fd, std::string &path, std::unordered_setsecond.watcher == &watcher) { + if (it->second.watcher.get() == watcher.get()) { if (mSubscriptions.count(it->first) == 1) { // Closing the file descriptor automatically unwatches it in the kqueue. close(it->second.fd); diff --git a/src/kqueue/KqueueBackend.hh b/src/kqueue/KqueueBackend.hh index e1c8c3e..3c6a9cd 100644 --- a/src/kqueue/KqueueBackend.hh +++ b/src/kqueue/KqueueBackend.hh @@ -8,7 +8,7 @@ #include "../Signal.hh" struct KqueueSubscription { - Watcher *watcher; + WatcherRef watcher; std::string path; std::shared_ptr tree; int fd; @@ -18,8 +18,8 @@ class KqueueBackend : public BruteForceBackend { public: void start() override; ~KqueueBackend(); - void subscribe(Watcher &watcher) override; - void unsubscribe(Watcher &watcher) override; + void subscribe(WatcherRef watcher) override; + void unsubscribe(WatcherRef watcher) override; private: int mKqueue; int mPipe[2]; @@ -27,8 +27,8 @@ private: std::unordered_map mFdToEntry; Signal mEndedSignal; - bool watchDir(Watcher &watcher, std::string path, std::shared_ptr tree); - bool compareDir(int fd, std::string &dir, std::unordered_set &watchers); + bool watchDir(WatcherRef watcher, std::string path, std::shared_ptr tree); + bool compareDir(int fd, std::string &dir, std::unordered_set &watchers); std::vector findSubscriptions(std::string &path); }; diff --git a/src/linux/InotifyBackend.cc b/src/linux/InotifyBackend.cc index b81d0bb..ec92691 100644 --- a/src/linux/InotifyBackend.cc +++ b/src/linux/InotifyBackend.cc @@ -64,7 +64,7 @@ InotifyBackend::~InotifyBackend() { } // This function is called by Backend::watch which takes a lock on mMutex -void InotifyBackend::subscribe(Watcher &watcher) { +void InotifyBackend::subscribe(WatcherRef watcher) { // Build a full directory tree recursively, and watch each directory. std::shared_ptr tree = getTree(watcher); @@ -72,13 +72,13 @@ void InotifyBackend::subscribe(Watcher &watcher) { if (it->second.isDir) { bool success = watchDir(watcher, it->second.path, tree); if (!success) { - throw WatcherError(std::string("inotify_add_watch on '") + it->second.path + std::string("' failed: ") + strerror(errno), &watcher); + throw WatcherError(std::string("inotify_add_watch on '") + it->second.path + std::string("' failed: ") + strerror(errno), watcher); } } } } -bool InotifyBackend::watchDir(Watcher &watcher, std::string path, std::shared_ptr tree) { +bool InotifyBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr tree) { int wd = inotify_add_watch(mInotify, path.c_str(), INOTIFY_MASK); if (wd == -1) { return false; @@ -87,7 +87,7 @@ bool InotifyBackend::watchDir(Watcher &watcher, std::string path, std::shared_pt std::shared_ptr sub = std::make_shared(); sub->tree = tree; sub->path = path; - sub->watcher = &watcher; + sub->watcher = watcher; mSubscriptions.emplace(wd, sub); return true; @@ -98,7 +98,7 @@ void InotifyBackend::handleEvents() { struct inotify_event *event; // Track all of the watchers that are touched so we can notify them at the end of the events. - std::unordered_set watchers; + std::unordered_set watchers; while (true) { int n = read(mInotify, &buf, BUFFER_SIZE); @@ -131,7 +131,7 @@ void InotifyBackend::handleEvents() { } } -void InotifyBackend::handleEvent(struct inotify_event *event, std::unordered_set &watchers) { +void InotifyBackend::handleEvent(struct inotify_event *event, std::unordered_set &watchers) { std::unique_lock lock(mMutex); // Find the subscriptions for this watch descriptor @@ -150,7 +150,7 @@ void InotifyBackend::handleEvent(struct inotify_event *event, std::unordered_set bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared_ptr sub) { // Build full path and check if its in our ignore list. - Watcher *watcher = sub->watcher; + std::shared_ptr watcher = sub->watcher; std::string path = std::string(sub->path); bool isDir = event->mask & IN_ISDIR; @@ -174,7 +174,7 @@ bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared DirEntry *entry = sub->tree->add(path, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode)); if (entry->isDir) { - bool success = watchDir(*watcher, path, sub->tree); + bool success = watchDir(watcher, path, sub->tree); if (!success) { sub->tree->remove(path); return false; @@ -213,14 +213,14 @@ bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared } // This function is called by Backend::unwatch which takes a lock on mMutex -void InotifyBackend::unsubscribe(Watcher &watcher) { +void InotifyBackend::unsubscribe(WatcherRef watcher) { // Find any subscriptions pointing to this watcher, and remove them. for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) { - if (it->second->watcher == &watcher) { + if (it->second->watcher.get() == watcher.get()) { if (mSubscriptions.count(it->first) == 1) { int err = inotify_rm_watch(mInotify, it->first); if (err == -1) { - throw WatcherError(std::string("Unable to remove watcher: ") + strerror(errno), &watcher); + throw WatcherError(std::string("Unable to remove watcher: ") + strerror(errno), watcher); } } diff --git a/src/linux/InotifyBackend.hh b/src/linux/InotifyBackend.hh index 992b476..f34cd1f 100644 --- a/src/linux/InotifyBackend.hh +++ b/src/linux/InotifyBackend.hh @@ -10,24 +10,24 @@ struct InotifySubscription { std::shared_ptr tree; std::string path; - Watcher *watcher; + WatcherRef watcher; }; class InotifyBackend : public BruteForceBackend { public: void start() override; ~InotifyBackend(); - void subscribe(Watcher &watcher) override; - void unsubscribe(Watcher &watcher) override; + void subscribe(WatcherRef watcher) override; + void unsubscribe(WatcherRef watcher) override; private: int mPipe[2]; int mInotify; std::unordered_multimap> mSubscriptions; Signal mEndedSignal; - bool watchDir(Watcher &watcher, std::string path, std::shared_ptr tree); + bool watchDir(WatcherRef watcher, std::string path, std::shared_ptr tree); void handleEvents(); - void handleEvent(struct inotify_event *event, std::unordered_set &watchers); + void handleEvent(struct inotify_event *event, std::unordered_set &watchers); bool handleSubscription(struct inotify_event *event, std::shared_ptr sub); }; diff --git a/src/macos/FSEventsBackend.cc b/src/macos/FSEventsBackend.cc index 191bf04..4e3ced0 100644 --- a/src/macos/FSEventsBackend.cc +++ b/src/macos/FSEventsBackend.cc @@ -38,7 +38,8 @@ bool pathExists(char *path) { return res; } -struct State { +class State: public WatcherState { +public: FSEventStreamRef stream; std::shared_ptr tree; uint64_t since; @@ -53,9 +54,15 @@ void FSEventsCallback( const FSEventStreamEventId eventIds[] ) { char **paths = (char **)eventPaths; - Watcher *watcher = (Watcher *)clientCallBackInfo; - EventList *list = &watcher->mEvents; - State *state = (State *)watcher->state; + std::shared_ptr& watcher = *static_cast *>(clientCallBackInfo); + + EventList& list = watcher->mEvents; + if (watcher->state == nullptr) { + return; + } + + auto stateGuard = watcher->state; + auto* state = static_cast(stateGuard.get()); uint64_t since = state->since; bool deletedRoot = false; @@ -94,10 +101,10 @@ void FSEventsCallback( // Handle unambiguous events first if (isCreated && !(isRemoved || isModified || isRenamed)) { state->tree->add(paths[i], 0, isDir); - list->create(paths[i]); + list.create(paths[i]); } else if (isRemoved && !(isCreated || isModified || isRenamed)) { state->tree->remove(paths[i]); - list->remove(paths[i]); + list.remove(paths[i]); if (paths[i] == watcher->mDir) { deletedRoot = true; } @@ -125,7 +132,7 @@ void FSEventsCallback( state->tree->add(paths[i], mtime, S_ISDIR(file.st_mode)); } - list->update(paths[i]); + list.update(paths[i]); } else { // If multiple flags were set, then we need to call `stat` to determine if the file really exists. // This helps disambiguate creates, updates, and deletes. @@ -137,7 +144,7 @@ void FSEventsCallback( // we'd rather ignore this event completely). This will result in some extra delete events // being emitted for files we don't know about, but that is the best we can do. state->tree->remove(paths[i]); - list->remove(paths[i]); + list.remove(paths[i]); if (paths[i] == watcher->mDir) { deletedRoot = true; } @@ -155,10 +162,10 @@ void FSEventsCallback( // Some mounted file systems report a creation time of 0/unix epoch which we special case. if (isModified && (entry || (ctime <= since && ctime != 0))) { state->tree->update(paths[i], mtime); - list->update(paths[i]); + list.update(paths[i]); } else { state->tree->add(paths[i], mtime, S_ISDIR(file.st_mode)); - list->create(paths[i]); + list.create(paths[i]); } } } @@ -168,29 +175,28 @@ void FSEventsCallback( // Stop watching if the root directory was deleted. if (deletedRoot) { stopStream((FSEventStreamRef)streamRef, CFRunLoopGetCurrent()); - delete state; - watcher->state = NULL; + watcher->state = nullptr; } } -void checkWatcher(Watcher &watcher) { +void checkWatcher(WatcherRef watcher) { struct stat file; - if (stat(watcher.mDir.c_str(), &file)) { - throw WatcherError(strerror(errno), &watcher); + if (stat(watcher->mDir.c_str(), &file)) { + throw WatcherError(strerror(errno), watcher); } if (!S_ISDIR(file.st_mode)) { - throw WatcherError(strerror(ENOTDIR), &watcher); + throw WatcherError(strerror(ENOTDIR), watcher); } } -void FSEventsBackend::startStream(Watcher &watcher, FSEventStreamEventId id) { +void FSEventsBackend::startStream(WatcherRef watcher, FSEventStreamEventId id) { checkWatcher(watcher); CFAbsoluteTime latency = 0.001; CFStringRef fileWatchPath = CFStringCreateWithCString( NULL, - watcher.mDir.c_str(), + watcher->mDir.c_str(), kCFStringEncodingUTF8 ); @@ -201,7 +207,9 @@ void FSEventsBackend::startStream(Watcher &watcher, FSEventStreamEventId id) { NULL ); - FSEventStreamContext callbackInfo {0, (void *)&watcher, nullptr, nullptr, nullptr}; + // Make a watcher reference we can pass into the callback. This ensures bumped ref-count. + std::shared_ptr* callbackWatcher = new std::shared_ptr (watcher); + FSEventStreamContext callbackInfo {0, static_cast (callbackWatcher), nullptr, nullptr, nullptr}; FSEventStreamRef stream = FSEventStreamCreate( NULL, &FSEventsCallback, @@ -212,8 +220,8 @@ void FSEventsBackend::startStream(Watcher &watcher, FSEventStreamEventId id) { kFSEventStreamCreateFlagFileEvents ); - CFMutableArrayRef exclusions = CFArrayCreateMutable(NULL, watcher.mIgnorePaths.size(), NULL); - for (auto it = watcher.mIgnorePaths.begin(); it != watcher.mIgnorePaths.end(); it++) { + CFMutableArrayRef exclusions = CFArrayCreateMutable(NULL, watcher->mIgnorePaths.size(), NULL); + for (auto it = watcher->mIgnorePaths.begin(); it != watcher->mIgnorePaths.end(); it++) { CFStringRef path = CFStringCreateWithCString( NULL, it->c_str(), @@ -233,11 +241,12 @@ void FSEventsBackend::startStream(Watcher &watcher, FSEventStreamEventId id) { if (!started) { FSEventStreamRelease(stream); - throw WatcherError("Error starting FSEvents stream", &watcher); + throw WatcherError("Error starting FSEvents stream", watcher); } - State *s = (State *)watcher.state; - s->tree = std::make_shared(watcher.mDir); + auto stateGuard = watcher->state; + State* s = static_cast(stateGuard.get()); + s->tree = std::make_shared(watcher->mDir); s->stream = stream; } @@ -260,7 +269,7 @@ FSEventsBackend::~FSEventsBackend() { CFRelease(mRunLoop); } -void FSEventsBackend::writeSnapshot(Watcher &watcher, std::string *snapshotPath) { +void FSEventsBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) { std::unique_lock lock(mMutex); checkWatcher(watcher); @@ -274,7 +283,7 @@ void FSEventsBackend::writeSnapshot(Watcher &watcher, std::string *snapshotPath) ofs << CONVERT_TIME(now); } -void FSEventsBackend::getEventsSince(Watcher &watcher, std::string *snapshotPath) { +void FSEventsBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) { std::unique_lock lock(mMutex); std::ifstream ifs(*snapshotPath); if (ifs.fail()) { @@ -286,33 +295,31 @@ void FSEventsBackend::getEventsSince(Watcher &watcher, std::string *snapshotPath ifs >> id; ifs >> since; - State *s = new State; + auto s = std::make_shared(); s->since = since; - watcher.state = (void *)s; + watcher->state = s; startStream(watcher, id); - watcher.wait(); + watcher->wait(); stopStream(s->stream, mRunLoop); - delete s; - watcher.state = NULL; + watcher->state = nullptr; } // This function is called by Backend::watch which takes a lock on mMutex -void FSEventsBackend::subscribe(Watcher &watcher) { - State *s = new State; +void FSEventsBackend::subscribe(WatcherRef watcher) { + auto s = std::make_shared(); s->since = 0; - watcher.state = (void *)s; + watcher->state = s; startStream(watcher, kFSEventStreamEventIdSinceNow); } // This function is called by Backend::unwatch which takes a lock on mMutex -void FSEventsBackend::unsubscribe(Watcher &watcher) { - State *s = (State *)watcher.state; - if (s != NULL) { +void FSEventsBackend::unsubscribe(WatcherRef watcher) { + auto stateGuard = watcher->state; + State* s = static_cast(stateGuard.get()); + if (s != nullptr) { stopStream(s->stream, mRunLoop); - - delete s; - watcher.state = NULL; + watcher->state = nullptr; } } diff --git a/src/macos/FSEventsBackend.hh b/src/macos/FSEventsBackend.hh index a981401..57ded66 100644 --- a/src/macos/FSEventsBackend.hh +++ b/src/macos/FSEventsBackend.hh @@ -8,12 +8,12 @@ class FSEventsBackend : public Backend { public: void start() override; ~FSEventsBackend(); - void writeSnapshot(Watcher &watcher, std::string *snapshotPath) override; - void getEventsSince(Watcher &watcher, std::string *snapshotPath) override; - void subscribe(Watcher &watcher) override; - void unsubscribe(Watcher &watcher) override; + void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) override; + void getEventsSince(WatcherRef watcher, std::string *snapshotPath) override; + void subscribe(WatcherRef watcher) override; + void unsubscribe(WatcherRef watcher) override; private: - void startStream(Watcher &watcher, FSEventStreamEventId id); + void startStream(WatcherRef watcher, FSEventStreamEventId id); CFRunLoopRef mRunLoop; }; diff --git a/src/shared/BruteForceBackend.cc b/src/shared/BruteForceBackend.cc index 28b5533..0e9b84f 100644 --- a/src/shared/BruteForceBackend.cc +++ b/src/shared/BruteForceBackend.cc @@ -3,8 +3,8 @@ #include "../Event.hh" #include "./BruteForceBackend.hh" -std::shared_ptr BruteForceBackend::getTree(Watcher &watcher, bool shouldRead) { - auto tree = DirTree::getCached(watcher.mDir); +std::shared_ptr BruteForceBackend::getTree(WatcherRef watcher, bool shouldRead) { + auto tree = DirTree::getCached(watcher->mDir); // If the tree is not complete, read it if needed. if (!tree->isComplete && shouldRead) { @@ -15,7 +15,7 @@ std::shared_ptr BruteForceBackend::getTree(Watcher &watcher, bool shoul return tree; } -void BruteForceBackend::writeSnapshot(Watcher &watcher, std::string *snapshotPath) { +void BruteForceBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) { std::unique_lock lock(mMutex); auto tree = getTree(watcher); FILE *f = fopen(snapshotPath->c_str(), "w"); @@ -27,15 +27,15 @@ void BruteForceBackend::writeSnapshot(Watcher &watcher, std::string *snapshotPat fclose(f); } -void BruteForceBackend::getEventsSince(Watcher &watcher, std::string *snapshotPath) { +void BruteForceBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) { std::unique_lock lock(mMutex); FILE *f = fopen(snapshotPath->c_str(), "r"); if (!f) { throw std::runtime_error(std::string("Unable to open snapshot file: ") + strerror(errno)); } - DirTree snapshot{watcher.mDir, f}; + DirTree snapshot{watcher->mDir, f}; auto now = getTree(watcher); - now->getChanges(&snapshot, watcher.mEvents); + now->getChanges(&snapshot, watcher->mEvents); fclose(f); } diff --git a/src/shared/BruteForceBackend.hh b/src/shared/BruteForceBackend.hh index c53f8c9..de7a73d 100644 --- a/src/shared/BruteForceBackend.hh +++ b/src/shared/BruteForceBackend.hh @@ -7,19 +7,19 @@ class BruteForceBackend : public Backend { public: - void writeSnapshot(Watcher &watcher, std::string *snapshotPath) override; - void getEventsSince(Watcher &watcher, std::string *snapshotPath) override; - void subscribe(Watcher &watcher) override { + void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) override; + void getEventsSince(WatcherRef watcher, std::string *snapshotPath) override; + void subscribe(WatcherRef watcher) override { throw "Brute force backend doesn't support subscriptions."; } - void unsubscribe(Watcher &watcher) override { + void unsubscribe(WatcherRef watcher) override { throw "Brute force backend doesn't support subscriptions."; } - std::shared_ptr getTree(Watcher &watcher, bool shouldRead = true); + std::shared_ptr getTree(WatcherRef watcher, bool shouldRead = true); private: - void readTree(Watcher &watcher, std::shared_ptr tree); + void readTree(WatcherRef watcher, std::shared_ptr tree); }; #endif diff --git a/src/unix/fts.cc b/src/unix/fts.cc index 14a538b..d50c3e4 100644 --- a/src/unix/fts.cc +++ b/src/unix/fts.cc @@ -16,11 +16,11 @@ #define st_mtim st_mtimespec #endif -void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree) { - char *paths[2] {(char *)watcher.mDir.c_str(), NULL}; +void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr tree) { + char *paths[2] {(char *)watcher->mDir.c_str(), NULL}; FTS *fts = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL); if (!fts) { - throw WatcherError(strerror(errno), &watcher); + throw WatcherError(strerror(errno), watcher); } FTSENT *node; @@ -29,15 +29,15 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree while ((node = fts_read(fts)) != NULL) { if (node->fts_errno) { fts_close(fts); - throw WatcherError(strerror(node->fts_errno), &watcher); + throw WatcherError(strerror(node->fts_errno), watcher); } if (isRoot && !(node->fts_info & FTS_D)) { fts_close(fts); - throw WatcherError(strerror(ENOTDIR), &watcher); + throw WatcherError(strerror(ENOTDIR), watcher); } - if (watcher.isIgnored(std::string(node->fts_path))) { + if (watcher->isIgnored(std::string(node->fts_path))) { fts_set(fts, node, FTS_SKIP); continue; } diff --git a/src/unix/legacy.cc b/src/unix/legacy.cc index 97963c9..60490c6 100644 --- a/src/unix/legacy.cc +++ b/src/unix/legacy.cc @@ -24,7 +24,7 @@ #endif #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) -void iterateDir(Watcher &watcher, const std::shared_ptr tree, const char *relative, int parent_fd, const std::string &dirname) { +void iterateDir(WatcherRef watcher, const std::shared_ptr tree, const char *relative, int parent_fd, const std::string &dirname) { int open_flags = (O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); int new_fd = openat(parent_fd, relative, open_flags); if (new_fd == -1) { @@ -32,7 +32,7 @@ void iterateDir(Watcher &watcher, const std::shared_ptr tree, const ch return; // ignore insufficient permissions } - throw WatcherError(strerror(errno), &watcher); + throw WatcherError(strerror(errno), watcher); } struct stat rootAttributes; @@ -45,7 +45,7 @@ void iterateDir(Watcher &watcher, const std::shared_ptr tree, const ch std::string fullPath = dirname + "/" + ent->d_name; - if (!watcher.isIgnored(fullPath)) { + if (!watcher->isIgnored(fullPath)) { struct stat attrib; fstatat(new_fd, ent->d_name, &attrib, AT_SYMLINK_NOFOLLOW); bool isDir = ent->d_type == DT_DIR; @@ -64,14 +64,14 @@ void iterateDir(Watcher &watcher, const std::shared_ptr tree, const ch } if (errno) { - throw WatcherError(strerror(errno), &watcher); + throw WatcherError(strerror(errno), watcher); } } -void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree) { - int fd = open(watcher.mDir.c_str(), O_RDONLY); +void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr tree) { + int fd = open(watcher->mDir.c_str(), O_RDONLY); if (fd) { - iterateDir(watcher, tree, ".", fd, watcher.mDir); + iterateDir(watcher, tree, ".", fd, watcher->mDir); close(fd); } } diff --git a/src/wasm/WasmBackend.cc b/src/wasm/WasmBackend.cc index a1118c2..9514109 100644 --- a/src/wasm/WasmBackend.cc +++ b/src/wasm/WasmBackend.cc @@ -7,7 +7,7 @@ void WasmBackend::start() { notifyStarted(); } -void WasmBackend::subscribe(Watcher &watcher) { +void WasmBackend::subscribe(WatcherRef watcher) { // Build a full directory tree recursively, and watch each directory. std::shared_ptr tree = getTree(watcher); @@ -18,12 +18,12 @@ void WasmBackend::subscribe(Watcher &watcher) { } } -void WasmBackend::watchDir(Watcher &watcher, std::string path, std::shared_ptr tree) { +void WasmBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr tree) { int wd = wasm_backend_add_watch(path.c_str(), (void *)this); std::shared_ptr sub = std::make_shared(); sub->tree = tree; sub->path = path; - sub->watcher = &watcher; + sub->watcher = watcher; mSubscriptions.emplace(wd, sub); } @@ -49,7 +49,7 @@ void WasmBackend::handleEvent(int wd, int type, char *filename) { bool WasmBackend::handleSubscription(int type, char *filename, std::shared_ptr sub) { // Build full path and check if its in our ignore list. - Watcher *watcher = sub->watcher; + WatcherRef watcher = sub->watcher; std::string path = std::string(sub->path); if (filename[0] != '\0') { @@ -108,7 +108,7 @@ bool WasmBackend::handleSubscription(int type, char *filename, std::shared_ptrtree->add(path, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode)); if (entry->isDir) { - watchDir(*watcher, path, sub->tree); + watchDir(watcher, path, sub->tree); } } } @@ -116,10 +116,10 @@ bool WasmBackend::handleSubscription(int type, char *filename, std::shared_ptrsecond->watcher == &watcher) { + if (it->second->watcher.get() == watcher.get()) { if (mSubscriptions.count(it->first) == 1) { wasm_backend_remove_watch(it->first); } diff --git a/src/wasm/WasmBackend.hh b/src/wasm/WasmBackend.hh index 2a25e80..9facac8 100644 --- a/src/wasm/WasmBackend.hh +++ b/src/wasm/WasmBackend.hh @@ -14,20 +14,20 @@ extern "C" { struct WasmSubscription { std::shared_ptr tree; std::string path; - Watcher *watcher; + WatcherRef watcher; }; class WasmBackend : public BruteForceBackend { public: void start() override; - void subscribe(Watcher &watcher) override; - void unsubscribe(Watcher &watcher) override; + void subscribe(WatcherRef watcher) override; + void unsubscribe(WatcherRef watcher) override; void handleEvent(int wd, int type, char *filename); private: int mWasm; std::unordered_multimap> mSubscriptions; - void watchDir(Watcher &watcher, std::string path, std::shared_ptr tree); + void watchDir(WatcherRef watcher, std::string path, std::shared_ptr tree); bool handleSubscription(int type, char *filename, std::shared_ptr sub); }; diff --git a/src/watchman/WatchmanBackend.cc b/src/watchman/WatchmanBackend.cc index 0464003..82a23f5 100644 --- a/src/watchman/WatchmanBackend.cc +++ b/src/watchman/WatchmanBackend.cc @@ -109,10 +109,10 @@ bool WatchmanBackend::checkAvailable() { } } -void handleFiles(Watcher &watcher, BSER::Object obj) { +void handleFiles(WatcherRef watcher, BSER::Object obj) { auto found = obj.find("files"); if (found == obj.end()) { - throw WatcherError("Error reading changes from watchman", &watcher); + throw WatcherError("Error reading changes from watchman", watcher); } auto files = found->second.arrayValue(); @@ -125,17 +125,17 @@ void handleFiles(Watcher &watcher, BSER::Object obj) { auto mode = file.find("mode")->second.intValue(); auto isNew = file.find("new")->second.boolValue(); auto exists = file.find("exists")->second.boolValue(); - auto path = watcher.mDir + DIR_SEP + name; - if (watcher.isIgnored(path)) { + auto path = watcher->mDir + DIR_SEP + name; + if (watcher->isIgnored(path)) { continue; } if (isNew && exists) { - watcher.mEvents.create(path); + watcher->mEvents.create(path); } else if (exists && !S_ISDIR(mode)) { - watcher.mEvents.update(path); + watcher->mEvents.update(path); } else if (!isNew && !exists) { - watcher.mEvents.remove(path); + watcher->mEvents.remove(path); } } } @@ -150,7 +150,7 @@ void WatchmanBackend::handleSubscription(BSER::Object obj) { auto watcher = it->second; try { - handleFiles(*watcher, obj); + handleFiles(watcher, obj); watcher->notify(); } catch (WatcherError &err) { handleWatcherError(err); @@ -223,64 +223,64 @@ WatchmanBackend::~WatchmanBackend() { mEndedSignal.wait(); } -std::string WatchmanBackend::clock(Watcher &watcher) { +std::string WatchmanBackend::clock(WatcherRef watcher) { BSER::Array cmd; cmd.push_back("clock"); - cmd.push_back(normalizePath(watcher.mDir)); + cmd.push_back(normalizePath(watcher->mDir)); BSER::Object obj = watchmanRequest(cmd); auto found = obj.find("clock"); if (found == obj.end()) { - throw WatcherError("Error reading clock from watchman", &watcher); + throw WatcherError("Error reading clock from watchman", watcher); } return found->second.stringValue(); } -void WatchmanBackend::writeSnapshot(Watcher &watcher, std::string *snapshotPath) { +void WatchmanBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) { std::unique_lock lock(mMutex); - watchmanWatch(watcher.mDir); + watchmanWatch(watcher->mDir); std::ofstream ofs(*snapshotPath); ofs << clock(watcher); } -void WatchmanBackend::getEventsSince(Watcher &watcher, std::string *snapshotPath) { +void WatchmanBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) { std::unique_lock lock(mMutex); std::ifstream ifs(*snapshotPath); if (ifs.fail()) { return; } - watchmanWatch(watcher.mDir); + watchmanWatch(watcher->mDir); std::string clock; ifs >> clock; BSER::Array cmd; cmd.push_back("since"); - cmd.push_back(normalizePath(watcher.mDir)); + cmd.push_back(normalizePath(watcher->mDir)); cmd.push_back(clock); BSER::Object obj = watchmanRequest(cmd); handleFiles(watcher, obj); } -std::string getId(Watcher &watcher) { +std::string getId(WatcherRef watcher) { std::ostringstream id; id << "parcel-"; - id << (void *)&watcher; + id << static_cast(watcher.get()); return id.str(); } // This function is called by Backend::watch which takes a lock on mMutex -void WatchmanBackend::subscribe(Watcher &watcher) { - watchmanWatch(watcher.mDir); +void WatchmanBackend::subscribe(WatcherRef watcher) { + watchmanWatch(watcher->mDir); std::string id = getId(watcher); BSER::Array cmd; cmd.push_back("subscribe"); - cmd.push_back(normalizePath(watcher.mDir)); + cmd.push_back(normalizePath(watcher->mDir)); cmd.push_back(id); BSER::Array fields; @@ -293,13 +293,13 @@ void WatchmanBackend::subscribe(Watcher &watcher) { opts.emplace("fields", fields); opts.emplace("since", clock(watcher)); - if (watcher.mIgnorePaths.size() > 0) { + if (watcher->mIgnorePaths.size() > 0) { BSER::Array ignore; BSER::Array anyOf; anyOf.push_back("anyof"); - for (auto it = watcher.mIgnorePaths.begin(); it != watcher.mIgnorePaths.end(); it++) { - std::string pathStart = watcher.mDir + DIR_SEP; + for (auto it = watcher->mIgnorePaths.begin(); it != watcher->mIgnorePaths.end(); it++) { + std::string pathStart = watcher->mDir + DIR_SEP; if (it->rfind(pathStart, 0) == 0) { auto relative = it->substr(pathStart.size()); BSER::Array dirname; @@ -318,19 +318,19 @@ void WatchmanBackend::subscribe(Watcher &watcher) { cmd.push_back(opts); watchmanRequest(cmd); - mSubscriptions.emplace(id, &watcher); + mSubscriptions.emplace(id, watcher); mRequestSignal.notify(); } // This function is called by Backend::unwatch which takes a lock on mMutex -void WatchmanBackend::unsubscribe(Watcher &watcher) { +void WatchmanBackend::unsubscribe(WatcherRef watcher) { std::string id = getId(watcher); auto erased = mSubscriptions.erase(id); if (erased) { BSER::Array cmd; cmd.push_back("unsubscribe"); - cmd.push_back(normalizePath(watcher.mDir)); + cmd.push_back(normalizePath(watcher->mDir)); cmd.push_back(id); watchmanRequest(cmd); diff --git a/src/watchman/WatchmanBackend.hh b/src/watchman/WatchmanBackend.hh index 903f228..699cded 100644 --- a/src/watchman/WatchmanBackend.hh +++ b/src/watchman/WatchmanBackend.hh @@ -12,21 +12,21 @@ public: void start() override; WatchmanBackend() : mStopped(false) {}; ~WatchmanBackend(); - void writeSnapshot(Watcher &watcher, std::string *snapshotPath) override; - void getEventsSince(Watcher &watcher, std::string *snapshotPath) override; - void subscribe(Watcher &watcher) override; - void unsubscribe(Watcher &watcher) override; + void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) override; + void getEventsSince(WatcherRef watcher, std::string *snapshotPath) override; + void subscribe(WatcherRef watcher) override; + void unsubscribe(WatcherRef watcher) override; private: std::unique_ptr mIPC; Signal mRequestSignal; Signal mResponseSignal; BSER::Object mResponse; std::string mError; - std::unordered_map mSubscriptions; + std::unordered_map mSubscriptions; bool mStopped; Signal mEndedSignal; - std::string clock(Watcher &watcher); + std::string clock(WatcherRef watcher); void watchmanWatch(std::string dir); BSER::Object watchmanRequest(BSER cmd); void handleSubscription(BSER::Object obj); diff --git a/src/windows/WindowsBackend.cc b/src/windows/WindowsBackend.cc index 4b69e57..eabce1e 100644 --- a/src/windows/WindowsBackend.cc +++ b/src/windows/WindowsBackend.cc @@ -9,10 +9,10 @@ #define NETWORK_BUF_SIZE 64 * 1024 #define CONVERT_TIME(ft) ULARGE_INTEGER{ft.dwLowDateTime, ft.dwHighDateTime}.QuadPart -void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree) { +void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr tree) { std::stack directories; - directories.push(watcher.mDir); + directories.push(watcher->mDir); while (!directories.empty()) { HANDLE hFind = INVALID_HANDLE_VALUE; @@ -25,9 +25,9 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree hFind = FindFirstFile(spec.c_str(), &ffd); if (hFind == INVALID_HANDLE_VALUE) { - if (path == watcher.mDir) { + if (path == watcher->mDir) { FindClose(hFind); - throw WatcherError("Error opening directory", &watcher); + throw WatcherError("Error opening directory", watcher); } tree->remove(path); @@ -37,7 +37,7 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree do { if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) { std::string fullPath = path + "\\" + ffd.cFileName; - if (watcher.isIgnored(fullPath)) { + if (watcher->isIgnored(fullPath)) { continue; } @@ -67,9 +67,9 @@ WindowsBackend::~WindowsBackend() { QueueUserAPC([](__in ULONG_PTR) {}, mThread.native_handle(), (ULONG_PTR)this); } -class Subscription { +class Subscription: public WatcherState { public: - Subscription(WindowsBackend *backend, Watcher *watcher, std::shared_ptr tree) { + Subscription(WindowsBackend *backend, WatcherRef watcher, std::shared_ptr tree) { mRunning = true; mBackend = backend; mWatcher = watcher; @@ -109,7 +109,7 @@ class Subscription { } } - ~Subscription() { + virtual ~Subscription() { stop(); } @@ -250,7 +250,7 @@ class Subscription { private: WindowsBackend *mBackend; - Watcher *mWatcher; + std::shared_ptr mWatcher; std::shared_ptr mTree; bool mRunning; HANDLE mDirectoryHandle; @@ -260,16 +260,16 @@ class Subscription { }; // This function is called by Backend::watch which takes a lock on mMutex -void WindowsBackend::subscribe(Watcher &watcher) { +void WindowsBackend::subscribe(WatcherRef watcher) { // Create a subscription for this watcher - Subscription *sub = new Subscription(this, &watcher, getTree(watcher, false)); - watcher.state = (void *)sub; + auto sub = std::make_shared(this, watcher, getTree(watcher, false)); + watcher->state = sub; // Queue polling for this subscription in the correct thread. bool success = QueueUserAPC([](__in ULONG_PTR ptr) { Subscription *sub = (Subscription *)ptr; sub->run(); - }, mThread.native_handle(), (ULONG_PTR)sub); + }, mThread.native_handle(), (ULONG_PTR)sub.get()); if (!success) { throw std::runtime_error("Unable to queue APC"); @@ -277,7 +277,6 @@ void WindowsBackend::subscribe(Watcher &watcher) { } // This function is called by Backend::unwatch which takes a lock on mMutex -void WindowsBackend::unsubscribe(Watcher &watcher) { - Subscription *sub = (Subscription *)watcher.state; - delete sub; +void WindowsBackend::unsubscribe(WatcherRef watcher) { + watcher->state = nullptr; } diff --git a/src/windows/WindowsBackend.hh b/src/windows/WindowsBackend.hh index 74b5c56..d679782 100644 --- a/src/windows/WindowsBackend.hh +++ b/src/windows/WindowsBackend.hh @@ -9,8 +9,8 @@ class WindowsBackend : public BruteForceBackend { public: void start() override; ~WindowsBackend(); - void subscribe(Watcher &watcher) override; - void unsubscribe(Watcher &watcher) override; + void subscribe(WatcherRef watcher) override; + void unsubscribe(WatcherRef watcher) override; private: bool mRunning; }; diff --git a/test/since.js b/test/since.js index c3db999..0cccee7 100644 --- a/test/since.js +++ b/test/since.js @@ -68,7 +68,7 @@ describe('since', () => { describe('files', () => { it('should emit when a file is created', async function () { - this.timeout(5000); + this.timeout(10000); let f = getFilename(); await watcher.writeSnapshot(tmpDir, snapshotPath, {backend}); if (isSecondPrecision) { diff --git a/wasm/index.mjs b/wasm/index.mjs index 67cfcae..fd40394 100644 --- a/wasm/index.mjs +++ b/wasm/index.mjs @@ -156,7 +156,7 @@ const wasm_env = { emscripten_resize_heap() { return 0; }, - abort() {}, + _abort_js() {}, wasm_backend_add_watch(filename, backend) { let path = env.getString(filename); let watch = fs.watch(path, {encoding: 'buffer'}, (eventType, filename) => {