1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 #include <algorithm>
23
24 #include "async_wrap-inl.h"
25 #include "debug_utils-inl.h"
26 #include "env-inl.h"
27 #include "node_errors.h"
28 #include "node_internals.h"
29 #include "node_watchdog.h"
30 #include "util-inl.h"
31
32 namespace node {
33
34 using v8::Context;
35 using v8::FunctionCallbackInfo;
36 using v8::FunctionTemplate;
37 using v8::Local;
38 using v8::Object;
39 using v8::Value;
40
Watchdog(v8::Isolate * isolate,uint64_t ms,bool * timed_out)41 Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out)
42 : isolate_(isolate), timed_out_(timed_out) {
43
44 int rc;
45 rc = uv_loop_init(&loop_);
46 if (rc != 0) {
47 FatalError("node::Watchdog::Watchdog()",
48 "Failed to initialize uv loop.");
49 }
50
51 rc = uv_async_init(&loop_, &async_, [](uv_async_t* signal) {
52 Watchdog* w = ContainerOf(&Watchdog::async_, signal);
53 uv_stop(&w->loop_);
54 });
55
56 CHECK_EQ(0, rc);
57
58 rc = uv_timer_init(&loop_, &timer_);
59 CHECK_EQ(0, rc);
60
61 rc = uv_timer_start(&timer_, &Watchdog::Timer, ms, 0);
62 CHECK_EQ(0, rc);
63
64 rc = uv_thread_create(&thread_, &Watchdog::Run, this);
65 CHECK_EQ(0, rc);
66 }
67
68
~Watchdog()69 Watchdog::~Watchdog() {
70 uv_async_send(&async_);
71 uv_thread_join(&thread_);
72
73 uv_close(reinterpret_cast<uv_handle_t*>(&async_), nullptr);
74
75 // UV_RUN_DEFAULT so that libuv has a chance to clean up.
76 uv_run(&loop_, UV_RUN_DEFAULT);
77
78 CheckedUvLoopClose(&loop_);
79 }
80
81
Run(void * arg)82 void Watchdog::Run(void* arg) {
83 Watchdog* wd = static_cast<Watchdog*>(arg);
84
85 // UV_RUN_DEFAULT the loop will be stopped either by the async or the
86 // timer handle.
87 uv_run(&wd->loop_, UV_RUN_DEFAULT);
88
89 // Loop ref count reaches zero when both handles are closed.
90 // Close the timer handle on this side and let ~Watchdog() close async_
91 uv_close(reinterpret_cast<uv_handle_t*>(&wd->timer_), nullptr);
92 }
93
Timer(uv_timer_t * timer)94 void Watchdog::Timer(uv_timer_t* timer) {
95 Watchdog* w = ContainerOf(&Watchdog::timer_, timer);
96 *w->timed_out_ = true;
97 w->isolate()->TerminateExecution();
98 uv_stop(&w->loop_);
99 }
100
101
SigintWatchdog(v8::Isolate * isolate,bool * received_signal)102 SigintWatchdog::SigintWatchdog(
103 v8::Isolate* isolate, bool* received_signal)
104 : isolate_(isolate), received_signal_(received_signal) {
105 // Register this watchdog with the global SIGINT/Ctrl+C listener.
106 SigintWatchdogHelper::GetInstance()->Register(this);
107 // Start the helper thread, if that has not already happened.
108 SigintWatchdogHelper::GetInstance()->Start();
109 }
110
111
~SigintWatchdog()112 SigintWatchdog::~SigintWatchdog() {
113 SigintWatchdogHelper::GetInstance()->Unregister(this);
114 SigintWatchdogHelper::GetInstance()->Stop();
115 }
116
HandleSigint()117 SignalPropagation SigintWatchdog::HandleSigint() {
118 *received_signal_ = true;
119 isolate_->TerminateExecution();
120 return SignalPropagation::kStopPropagation;
121 }
122
Init(Environment * env,Local<Object> target)123 void TraceSigintWatchdog::Init(Environment* env, Local<Object> target) {
124 Local<FunctionTemplate> constructor = env->NewFunctionTemplate(New);
125 constructor->InstanceTemplate()->SetInternalFieldCount(
126 TraceSigintWatchdog::kInternalFieldCount);
127 Local<v8::String> js_sigint_watch_dog =
128 FIXED_ONE_BYTE_STRING(env->isolate(), "TraceSigintWatchdog");
129 constructor->SetClassName(js_sigint_watch_dog);
130 constructor->Inherit(HandleWrap::GetConstructorTemplate(env));
131
132 env->SetProtoMethod(constructor, "start", Start);
133 env->SetProtoMethod(constructor, "stop", Stop);
134
135 target
136 ->Set(env->context(),
137 js_sigint_watch_dog,
138 constructor->GetFunction(env->context()).ToLocalChecked())
139 .Check();
140 }
141
New(const FunctionCallbackInfo<Value> & args)142 void TraceSigintWatchdog::New(const FunctionCallbackInfo<Value>& args) {
143 // This constructor should not be exposed to public javascript.
144 // Therefore we assert that we are not trying to call this as a
145 // normal function.
146 CHECK(args.IsConstructCall());
147 Environment* env = Environment::GetCurrent(args);
148 new TraceSigintWatchdog(env, args.This());
149 }
150
Start(const FunctionCallbackInfo<Value> & args)151 void TraceSigintWatchdog::Start(const FunctionCallbackInfo<Value>& args) {
152 TraceSigintWatchdog* watchdog;
153 ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
154 // Register this watchdog with the global SIGINT/Ctrl+C listener.
155 SigintWatchdogHelper::GetInstance()->Register(watchdog);
156 // Start the helper thread, if that has not already happened.
157 int r = SigintWatchdogHelper::GetInstance()->Start();
158 CHECK_EQ(r, 0);
159 }
160
Stop(const FunctionCallbackInfo<Value> & args)161 void TraceSigintWatchdog::Stop(const FunctionCallbackInfo<Value>& args) {
162 TraceSigintWatchdog* watchdog;
163 ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
164 SigintWatchdogHelper::GetInstance()->Unregister(watchdog);
165 SigintWatchdogHelper::GetInstance()->Stop();
166 }
167
TraceSigintWatchdog(Environment * env,Local<Object> object)168 TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local<Object> object)
169 : HandleWrap(env,
170 object,
171 reinterpret_cast<uv_handle_t*>(&handle_),
172 AsyncWrap::PROVIDER_SIGINTWATCHDOG) {
173 int r = uv_async_init(env->event_loop(), &handle_, [](uv_async_t* handle) {
174 TraceSigintWatchdog* watchdog =
175 ContainerOf(&TraceSigintWatchdog::handle_, handle);
176 watchdog->signal_flag_ = SignalFlags::FromIdle;
177 watchdog->HandleInterrupt();
178 });
179 CHECK_EQ(r, 0);
180 uv_unref(reinterpret_cast<uv_handle_t*>(&handle_));
181 }
182
HandleSigint()183 SignalPropagation TraceSigintWatchdog::HandleSigint() {
184 /**
185 * In case of uv loop polling, i.e. no JS currently running, activate the
186 * loop to run a piece of JS code to trigger interruption.
187 */
188 CHECK_EQ(uv_async_send(&handle_), 0);
189 env()->isolate()->RequestInterrupt(
190 [](v8::Isolate* isolate, void* data) {
191 TraceSigintWatchdog* self = static_cast<TraceSigintWatchdog*>(data);
192 if (self->signal_flag_ == SignalFlags::None) {
193 self->signal_flag_ = SignalFlags::FromInterrupt;
194 }
195 self->HandleInterrupt();
196 },
197 this);
198 return SignalPropagation::kContinuePropagation;
199 }
200
HandleInterrupt()201 void TraceSigintWatchdog::HandleInterrupt() {
202 // Do not nest interrupts.
203 if (interrupting) {
204 return;
205 }
206 interrupting = true;
207 if (signal_flag_ == SignalFlags::None) {
208 return;
209 }
210 Environment* env_ = env();
211 // FIXME: Before
212 // https://github.com/nodejs/node/pull/29207#issuecomment-527667993 get
213 // fixed, additional JavaScript code evaluation shall be prevented from
214 // running during interruption.
215 FPrintF(stderr,
216 "KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`\n");
217 if (signal_flag_ == SignalFlags::FromInterrupt) {
218 PrintStackTrace(env_->isolate(),
219 v8::StackTrace::CurrentStackTrace(
220 env_->isolate(), 10, v8::StackTrace::kDetailed));
221 }
222 signal_flag_ = SignalFlags::None;
223 interrupting = false;
224
225 SigintWatchdogHelper::GetInstance()->Unregister(this);
226 SigintWatchdogHelper::GetInstance()->Stop();
227 raise(SIGINT);
228 }
229
230 #ifdef __POSIX__
RunSigintWatchdog(void * arg)231 void* SigintWatchdogHelper::RunSigintWatchdog(void* arg) {
232 // Inside the helper thread.
233 bool is_stopping;
234
235 do {
236 uv_sem_wait(&instance.sem_);
237 is_stopping = InformWatchdogsAboutSignal();
238 } while (!is_stopping);
239
240 return nullptr;
241 }
242
243
HandleSignal(int signum)244 void SigintWatchdogHelper::HandleSignal(int signum) {
245 uv_sem_post(&instance.sem_);
246 }
247
248 #else
249
250 // Windows starts a separate thread for executing the handler, so no extra
251 // helper thread is required.
WinCtrlCHandlerRoutine(DWORD dwCtrlType)252 BOOL WINAPI SigintWatchdogHelper::WinCtrlCHandlerRoutine(DWORD dwCtrlType) {
253 if (!instance.watchdog_disabled_ &&
254 (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT)) {
255 InformWatchdogsAboutSignal();
256
257 // Return true because the signal has been handled.
258 return TRUE;
259 } else {
260 return FALSE;
261 }
262 }
263 #endif
264
265
InformWatchdogsAboutSignal()266 bool SigintWatchdogHelper::InformWatchdogsAboutSignal() {
267 Mutex::ScopedLock list_lock(instance.list_mutex_);
268
269 bool is_stopping = false;
270 #ifdef __POSIX__
271 is_stopping = instance.stopping_;
272 #endif
273
274 // If there are no listeners and the helper thread has been awoken by a signal
275 // (= not when stopping it), indicate that by setting has_pending_signal_.
276 if (instance.watchdogs_.empty() && !is_stopping) {
277 instance.has_pending_signal_ = true;
278 }
279
280 for (auto it = instance.watchdogs_.rbegin(); it != instance.watchdogs_.rend();
281 it++) {
282 SignalPropagation wp = (*it)->HandleSigint();
283 if (wp == SignalPropagation::kStopPropagation) {
284 break;
285 }
286 }
287
288 return is_stopping;
289 }
290
291
Start()292 int SigintWatchdogHelper::Start() {
293 Mutex::ScopedLock lock(mutex_);
294
295 if (start_stop_count_++ > 0) {
296 return 0;
297 }
298
299 #ifdef __POSIX__
300 CHECK_EQ(has_running_thread_, false);
301 has_pending_signal_ = false;
302 stopping_ = false;
303
304 sigset_t sigmask;
305 sigfillset(&sigmask);
306 sigset_t savemask;
307 CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask));
308 sigmask = savemask;
309 int ret = pthread_create(&thread_, nullptr, RunSigintWatchdog, nullptr);
310 CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
311 if (ret != 0) {
312 return ret;
313 }
314 has_running_thread_ = true;
315
316 RegisterSignalHandler(SIGINT, HandleSignal);
317 #else
318 if (watchdog_disabled_) {
319 watchdog_disabled_ = false;
320 } else {
321 SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, TRUE);
322 }
323 #endif
324
325 return 0;
326 }
327
328
Stop()329 bool SigintWatchdogHelper::Stop() {
330 bool had_pending_signal;
331 Mutex::ScopedLock lock(mutex_);
332
333 {
334 Mutex::ScopedLock list_lock(list_mutex_);
335
336 had_pending_signal = has_pending_signal_;
337
338 if (--start_stop_count_ > 0) {
339 has_pending_signal_ = false;
340 return had_pending_signal;
341 }
342
343 #ifdef __POSIX__
344 // Set stopping now because it's only protected by list_mutex_.
345 stopping_ = true;
346 #endif
347
348 watchdogs_.clear();
349 }
350
351 #ifdef __POSIX__
352 if (!has_running_thread_) {
353 has_pending_signal_ = false;
354 return had_pending_signal;
355 }
356
357 // Wake up the helper thread.
358 uv_sem_post(&sem_);
359
360 // Wait for the helper thread to finish.
361 CHECK_EQ(0, pthread_join(thread_, nullptr));
362 has_running_thread_ = false;
363
364 RegisterSignalHandler(SIGINT, SignalExit, true);
365 #else
366 watchdog_disabled_ = true;
367 #endif
368
369 had_pending_signal = has_pending_signal_;
370 has_pending_signal_ = false;
371
372 return had_pending_signal;
373 }
374
375
HasPendingSignal()376 bool SigintWatchdogHelper::HasPendingSignal() {
377 Mutex::ScopedLock lock(list_mutex_);
378
379 return has_pending_signal_;
380 }
381
Register(SigintWatchdogBase * wd)382 void SigintWatchdogHelper::Register(SigintWatchdogBase* wd) {
383 Mutex::ScopedLock lock(list_mutex_);
384
385 watchdogs_.push_back(wd);
386 }
387
Unregister(SigintWatchdogBase * wd)388 void SigintWatchdogHelper::Unregister(SigintWatchdogBase* wd) {
389 Mutex::ScopedLock lock(list_mutex_);
390
391 auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd);
392
393 CHECK_NE(it, watchdogs_.end());
394 watchdogs_.erase(it);
395 }
396
397
SigintWatchdogHelper()398 SigintWatchdogHelper::SigintWatchdogHelper()
399 : start_stop_count_(0),
400 has_pending_signal_(false) {
401 #ifdef __POSIX__
402 has_running_thread_ = false;
403 stopping_ = false;
404 CHECK_EQ(0, uv_sem_init(&sem_, 0));
405 #else
406 watchdog_disabled_ = false;
407 #endif
408 }
409
410
~SigintWatchdogHelper()411 SigintWatchdogHelper::~SigintWatchdogHelper() {
412 start_stop_count_ = 0;
413 Stop();
414
415 #ifdef __POSIX__
416 CHECK_EQ(has_running_thread_, false);
417 uv_sem_destroy(&sem_);
418 #endif
419 }
420
421 SigintWatchdogHelper SigintWatchdogHelper::instance;
422
423 namespace watchdog {
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)424 static void Initialize(Local<Object> target,
425 Local<Value> unused,
426 Local<Context> context,
427 void* priv) {
428 Environment* env = Environment::GetCurrent(context);
429 TraceSigintWatchdog::Init(env, target);
430 }
431 } // namespace watchdog
432
433 } // namespace node
434
435 NODE_MODULE_CONTEXT_AWARE_INTERNAL(watchdog, node::watchdog::Initialize)
436