source: imaps-frontend/node_modules/@parcel/watcher/src/Watcher.cc@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 5.9 KB
RevLine 
[0c6b92a]1#include "Watcher.hh"
2#include <unordered_set>
3
4using namespace Napi;
5
6struct WatcherHash {
7 std::size_t operator() (WatcherRef const &k) const {
8 return std::hash<std::string>()(k->mDir);
9 }
10};
11
12struct WatcherCompare {
13 size_t operator() (WatcherRef const &a, WatcherRef const &b) const {
14 return *a == *b;
15 }
16};
17
18static std::unordered_set<WatcherRef , WatcherHash, WatcherCompare> sharedWatchers;
19
20WatcherRef Watcher::getShared(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs) {
21 WatcherRef watcher = std::make_shared<Watcher>(dir, ignorePaths, ignoreGlobs);
22 auto found = sharedWatchers.find(watcher);
23 if (found != sharedWatchers.end()) {
24 return *found;
25 }
26
27 sharedWatchers.insert(watcher);
28 return watcher;
29}
30
31void removeShared(Watcher *watcher) {
32 for (auto it = sharedWatchers.begin(); it != sharedWatchers.end(); it++) {
33 if (it->get() == watcher) {
34 sharedWatchers.erase(it);
35 break;
36 }
37 }
38
39 // Free up memory.
40 if (sharedWatchers.size() == 0) {
41 sharedWatchers.rehash(0);
42 }
43}
44
45Watcher::Watcher(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs)
46 : mDir(dir),
47 mIgnorePaths(ignorePaths),
48 mIgnoreGlobs(ignoreGlobs) {
49 mDebounce = Debounce::getShared();
50 mDebounce->add(this, [this] () {
51 triggerCallbacks();
52 });
53 }
54
55Watcher::~Watcher() {
56 mDebounce->remove(this);
57}
58
59void Watcher::wait() {
60 std::unique_lock<std::mutex> lk(mMutex);
61 mCond.wait(lk);
62}
63
64void Watcher::notify() {
65 std::unique_lock<std::mutex> lk(mMutex);
66 mCond.notify_all();
67
68 if (mCallbacks.size() > 0 && mEvents.size() > 0) {
69 // We must release our lock before calling into the debouncer
70 // to avoid a deadlock: the debouncer thread itself will require
71 // our lock from its thread when calling into `triggerCallbacks`
72 // while holding its own debouncer lock.
73 lk.unlock();
74 mDebounce->trigger();
75 }
76}
77
78struct CallbackData {
79 std::string error;
80 std::vector<Event> events;
81 CallbackData(std::string error, std::vector<Event> events) : error(error), events(events) {}
82};
83
84Value callbackEventsToJS(const Env &env, std::vector<Event> &events) {
85 EscapableHandleScope scope(env);
86 Array arr = Array::New(env, events.size());
87 size_t currentEventIndex = 0;
88 for (auto eventIterator = events.begin(); eventIterator != events.end(); eventIterator++) {
89 arr.Set(currentEventIndex++, eventIterator->toJS(env));
90 }
91 return scope.Escape(arr);
92}
93
94void callJSFunction(Napi::Env env, Function jsCallback, CallbackData *data) {
95 HandleScope scope(env);
96 auto err = data->error.size() > 0 ? Error::New(env, data->error).Value() : env.Null();
97 auto events = callbackEventsToJS(env, data->events);
98 jsCallback.Call({err, events});
99 delete data;
100
101 // Throw errors from the callback as fatal exceptions
102 // If we don't handle these node segfaults...
103 if (env.IsExceptionPending()) {
104 Napi::Error err = env.GetAndClearPendingException();
105 napi_fatal_exception(env, err.Value());
106 }
107}
108
109void Watcher::notifyError(std::exception &err) {
110 std::unique_lock<std::mutex> lk(mMutex);
111 for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
112 CallbackData *data = new CallbackData(err.what(), {});
113 it->tsfn.BlockingCall(data, callJSFunction);
114 }
115
116 clearCallbacks();
117}
118
119// This function is called from the debounce thread.
120void Watcher::triggerCallbacks() {
121 std::unique_lock<std::mutex> lk(mMutex);
122 if (mCallbacks.size() > 0 && mEvents.size() > 0) {
123 auto events = mEvents.getEvents();
124 mEvents.clear();
125
126 for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
127 it->tsfn.BlockingCall(new CallbackData("", events), callJSFunction);
128 }
129 }
130}
131
132// This should be called from the JavaScript thread.
133bool Watcher::watch(Function callback) {
134 std::unique_lock<std::mutex> lk(mMutex);
135
136 auto it = findCallback(callback);
137 if (it != mCallbacks.end()) {
138 return false;
139 }
140
141 auto tsfn = ThreadSafeFunction::New(
142 callback.Env(),
143 callback,
144 "Watcher callback",
145 0, // Unlimited queue
146 1 // Initial thread count
147 );
148
149 mCallbacks.push_back(Callback {
150 tsfn,
151 Napi::Persistent(callback),
152 std::this_thread::get_id()
153 });
154
155 return true;
156}
157
158// This should be called from the JavaScript thread.
159std::vector<Callback>::iterator Watcher::findCallback(Function callback) {
160 for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
161 // Only consider callbacks created by the same thread, or V8 will panic.
162 if (it->threadId == std::this_thread::get_id() && it->ref.Value() == callback) {
163 return it;
164 }
165 }
166
167 return mCallbacks.end();
168}
169
170// This should be called from the JavaScript thread.
171bool Watcher::unwatch(Function callback) {
172 std::unique_lock<std::mutex> lk(mMutex);
173
174 bool removed = false;
175 auto it = findCallback(callback);
176 if (it != mCallbacks.end()) {
177 it->tsfn.Release();
178 it->ref.Unref();
179 mCallbacks.erase(it);
180 removed = true;
181 }
182
183 if (removed && mCallbacks.size() == 0) {
184 unref();
185 return true;
186 }
187
188 return false;
189}
190
191void Watcher::unref() {
192 if (mCallbacks.size() == 0) {
193 removeShared(this);
194 }
195}
196
197void Watcher::destroy() {
198 std::unique_lock<std::mutex> lk(mMutex);
199 clearCallbacks();
200}
201
202// Private because it doesn't lock.
203void Watcher::clearCallbacks() {
204 for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
205 it->tsfn.Release();
206 it->ref.Unref();
207 }
208
209 mCallbacks.clear();
210 unref();
211}
212
213bool Watcher::isIgnored(std::string path) {
214 for (auto it = mIgnorePaths.begin(); it != mIgnorePaths.end(); it++) {
215 auto dir = *it + DIR_SEP;
216 if (*it == path || path.compare(0, dir.size(), dir) == 0) {
217 return true;
218 }
219 }
220
221 auto basePath = mDir + DIR_SEP;
222
223 if (path.rfind(basePath, 0) != 0) {
224 return false;
225 }
226
227 auto relativePath = path.substr(basePath.size());
228
229 for (auto it = mIgnoreGlobs.begin(); it != mIgnoreGlobs.end(); it++) {
230 if (it->isIgnored(relativePath)) {
231 return true;
232 }
233 }
234
235 return false;
236}
Note: See TracBrowser for help on using the repository browser.