• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::Isolate;
38 using v8::Local;
39 using v8::Object;
40 using v8::Value;
41 
Watchdog(v8::Isolate * isolate,uint64_t ms,bool * timed_out)42 Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out)
43     : isolate_(isolate), timed_out_(timed_out) {
44 
45   int rc;
46   rc = uv_loop_init(&loop_);
47   if (rc != 0) {
48     OnFatalError("node::Watchdog::Watchdog()", "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   Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
106   // Register this watchdog with the global SIGINT/Ctrl+C listener.
107   SigintWatchdogHelper::GetInstance()->Register(this);
108   // Start the helper thread, if that has not already happened.
109   SigintWatchdogHelper::GetInstance()->Start();
110 }
111 
112 
~SigintWatchdog()113 SigintWatchdog::~SigintWatchdog() {
114   Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
115   SigintWatchdogHelper::GetInstance()->Unregister(this);
116   SigintWatchdogHelper::GetInstance()->Stop();
117 }
118 
HandleSigint()119 SignalPropagation SigintWatchdog::HandleSigint() {
120   *received_signal_ = true;
121   isolate_->TerminateExecution();
122   return SignalPropagation::kStopPropagation;
123 }
124 
Init(Environment * env,Local<Object> target)125 void TraceSigintWatchdog::Init(Environment* env, Local<Object> target) {
126   Isolate* isolate = env->isolate();
127   Local<FunctionTemplate> constructor = NewFunctionTemplate(isolate, New);
128   constructor->InstanceTemplate()->SetInternalFieldCount(
129       TraceSigintWatchdog::kInternalFieldCount);
130   constructor->Inherit(HandleWrap::GetConstructorTemplate(env));
131 
132   SetProtoMethod(isolate, constructor, "start", Start);
133   SetProtoMethod(isolate, constructor, "stop", Stop);
134 
135   SetConstructorFunction(
136       env->context(), target, "TraceSigintWatchdog", constructor);
137 }
138 
New(const FunctionCallbackInfo<Value> & args)139 void TraceSigintWatchdog::New(const FunctionCallbackInfo<Value>& args) {
140   // This constructor should not be exposed to public javascript.
141   // Therefore we assert that we are not trying to call this as a
142   // normal function.
143   CHECK(args.IsConstructCall());
144   Environment* env = Environment::GetCurrent(args);
145   new TraceSigintWatchdog(env, args.This());
146 }
147 
Start(const FunctionCallbackInfo<Value> & args)148 void TraceSigintWatchdog::Start(const FunctionCallbackInfo<Value>& args) {
149   TraceSigintWatchdog* watchdog;
150   ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
151   Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
152   // Register this watchdog with the global SIGINT/Ctrl+C listener.
153   SigintWatchdogHelper::GetInstance()->Register(watchdog);
154   // Start the helper thread, if that has not already happened.
155   int r = SigintWatchdogHelper::GetInstance()->Start();
156   CHECK_EQ(r, 0);
157 }
158 
Stop(const FunctionCallbackInfo<Value> & args)159 void TraceSigintWatchdog::Stop(const FunctionCallbackInfo<Value>& args) {
160   TraceSigintWatchdog* watchdog;
161   ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
162   Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
163   SigintWatchdogHelper::GetInstance()->Unregister(watchdog);
164   SigintWatchdogHelper::GetInstance()->Stop();
165 }
166 
TraceSigintWatchdog(Environment * env,Local<Object> object)167 TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local<Object> object)
168     : HandleWrap(env,
169                  object,
170                  reinterpret_cast<uv_handle_t*>(&handle_),
171                  AsyncWrap::PROVIDER_SIGINTWATCHDOG) {
172   int r = uv_async_init(env->event_loop(), &handle_, [](uv_async_t* handle) {
173     TraceSigintWatchdog* watchdog =
174         ContainerOf(&TraceSigintWatchdog::handle_, handle);
175     watchdog->signal_flag_ = SignalFlags::FromIdle;
176     watchdog->HandleInterrupt();
177   });
178   CHECK_EQ(r, 0);
179   uv_unref(reinterpret_cast<uv_handle_t*>(&handle_));
180 }
181 
HandleSigint()182 SignalPropagation TraceSigintWatchdog::HandleSigint() {
183   /**
184    * In case of uv loop polling, i.e. no JS currently running, activate the
185    * loop to run a piece of JS code to trigger interruption.
186    */
187   CHECK_EQ(uv_async_send(&handle_), 0);
188   env()->isolate()->RequestInterrupt(
189       [](v8::Isolate* isolate, void* data) {
190         TraceSigintWatchdog* self = static_cast<TraceSigintWatchdog*>(data);
191         if (self->signal_flag_ == SignalFlags::None) {
192           self->signal_flag_ = SignalFlags::FromInterrupt;
193         }
194         self->HandleInterrupt();
195       },
196       this);
197   return SignalPropagation::kContinuePropagation;
198 }
199 
HandleInterrupt()200 void TraceSigintWatchdog::HandleInterrupt() {
201   // Do not nest interrupts.
202   if (interrupting) {
203     return;
204   }
205   interrupting = true;
206   if (signal_flag_ == SignalFlags::None) {
207     return;
208   }
209   Environment* env_ = env();
210   // FIXME: Before
211   // https://github.com/nodejs/node/pull/29207#issuecomment-527667993 get
212   // fixed, additional JavaScript code evaluation shall be prevented from
213   // running during interruption.
214   FPrintF(stderr,
215       "KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`\n");
216   if (signal_flag_ == SignalFlags::FromInterrupt) {
217     PrintStackTrace(env_->isolate(),
218                     v8::StackTrace::CurrentStackTrace(
219                         env_->isolate(), 10, v8::StackTrace::kDetailed));
220   }
221   signal_flag_ = SignalFlags::None;
222   interrupting = false;
223 
224   Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
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 
HandleSignal(int signum,siginfo_t * info,void * ucontext)243 void SigintWatchdogHelper::HandleSignal(int signum,
244                                         siginfo_t* info,
245                                         void* ucontext) {
246   uv_sem_post(&instance.sem_);
247 }
248 
249 #else
250 
251 // Windows starts a separate thread for executing the handler, so no extra
252 // helper thread is required.
WinCtrlCHandlerRoutine(DWORD dwCtrlType)253 BOOL WINAPI SigintWatchdogHelper::WinCtrlCHandlerRoutine(DWORD dwCtrlType) {
254   if (!instance.watchdog_disabled_ &&
255       (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT)) {
256     InformWatchdogsAboutSignal();
257 
258     // Return true because the signal has been handled.
259     return TRUE;
260   } else {
261     return FALSE;
262   }
263 }
264 #endif
265 
266 
InformWatchdogsAboutSignal()267 bool SigintWatchdogHelper::InformWatchdogsAboutSignal() {
268   Mutex::ScopedLock list_lock(instance.list_mutex_);
269 
270   bool is_stopping = false;
271 #ifdef __POSIX__
272   is_stopping = instance.stopping_;
273 #endif
274 
275   // If there are no listeners and the helper thread has been awoken by a signal
276   // (= not when stopping it), indicate that by setting has_pending_signal_.
277   if (instance.watchdogs_.empty() && !is_stopping) {
278     instance.has_pending_signal_ = true;
279   }
280 
281   for (auto it = instance.watchdogs_.rbegin(); it != instance.watchdogs_.rend();
282        it++) {
283     SignalPropagation wp = (*it)->HandleSigint();
284     if (wp == SignalPropagation::kStopPropagation) {
285       break;
286     }
287   }
288 
289   return is_stopping;
290 }
291 
292 
Start()293 int SigintWatchdogHelper::Start() {
294   Mutex::ScopedLock lock(mutex_);
295 
296   if (start_stop_count_++ > 0) {
297     return 0;
298   }
299 
300 #ifdef __POSIX__
301   CHECK_EQ(has_running_thread_, false);
302   has_pending_signal_ = false;
303   stopping_ = false;
304 
305   sigset_t sigmask;
306   sigfillset(&sigmask);
307   sigset_t savemask;
308   CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask));
309   sigmask = savemask;
310   int ret = pthread_create(&thread_, nullptr, RunSigintWatchdog, nullptr);
311   CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
312   if (ret != 0) {
313     return ret;
314   }
315   has_running_thread_ = true;
316 
317   RegisterSignalHandler(SIGINT, HandleSignal);
318 #else
319   if (watchdog_disabled_) {
320     watchdog_disabled_ = false;
321   } else {
322     SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, TRUE);
323   }
324 #endif
325 
326   return 0;
327 }
328 
329 
Stop()330 bool SigintWatchdogHelper::Stop() {
331   bool had_pending_signal;
332   Mutex::ScopedLock lock(mutex_);
333 
334   {
335     Mutex::ScopedLock list_lock(list_mutex_);
336 
337     had_pending_signal = has_pending_signal_;
338 
339     if (--start_stop_count_ > 0) {
340       has_pending_signal_ = false;
341       return had_pending_signal;
342     }
343 
344 #ifdef __POSIX__
345     // Set stopping now because it's only protected by list_mutex_.
346     stopping_ = true;
347 #endif
348 
349     watchdogs_.clear();
350   }
351 
352 #ifdef __POSIX__
353   if (!has_running_thread_) {
354     has_pending_signal_ = false;
355     return had_pending_signal;
356   }
357 
358   // Wake up the helper thread.
359   uv_sem_post(&sem_);
360 
361   // Wait for the helper thread to finish.
362   CHECK_EQ(0, pthread_join(thread_, nullptr));
363   has_running_thread_ = false;
364 
365   RegisterSignalHandler(SIGINT, SignalExit, true);
366 #else
367   watchdog_disabled_ = true;
368 #endif
369 
370   had_pending_signal = has_pending_signal_;
371   has_pending_signal_ = false;
372 
373   return had_pending_signal;
374 }
375 
376 
HasPendingSignal()377 bool SigintWatchdogHelper::HasPendingSignal() {
378   Mutex::ScopedLock lock(list_mutex_);
379 
380   return has_pending_signal_;
381 }
382 
Register(SigintWatchdogBase * wd)383 void SigintWatchdogHelper::Register(SigintWatchdogBase* wd) {
384   Mutex::ScopedLock lock(list_mutex_);
385 
386   watchdogs_.push_back(wd);
387 }
388 
Unregister(SigintWatchdogBase * wd)389 void SigintWatchdogHelper::Unregister(SigintWatchdogBase* wd) {
390   Mutex::ScopedLock lock(list_mutex_);
391 
392   auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd);
393 
394   CHECK_NE(it, watchdogs_.end());
395   watchdogs_.erase(it);
396 }
397 
398 
SigintWatchdogHelper()399 SigintWatchdogHelper::SigintWatchdogHelper()
400     : start_stop_count_(0),
401       has_pending_signal_(false) {
402 #ifdef __POSIX__
403   has_running_thread_ = false;
404   stopping_ = false;
405   CHECK_EQ(0, uv_sem_init(&sem_, 0));
406 #else
407   watchdog_disabled_ = false;
408 #endif
409 }
410 
411 
~SigintWatchdogHelper()412 SigintWatchdogHelper::~SigintWatchdogHelper() {
413   start_stop_count_ = 0;
414   Stop();
415 
416 #ifdef __POSIX__
417   CHECK_EQ(has_running_thread_, false);
418   uv_sem_destroy(&sem_);
419 #endif
420 }
421 
422 SigintWatchdogHelper SigintWatchdogHelper::instance;
423 Mutex SigintWatchdogHelper::instance_action_mutex_;
424 
425 namespace watchdog {
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)426 static void Initialize(Local<Object> target,
427                        Local<Value> unused,
428                        Local<Context> context,
429                        void* priv) {
430   Environment* env = Environment::GetCurrent(context);
431   TraceSigintWatchdog::Init(env, target);
432 }
433 }  // namespace watchdog
434 
435 }  // namespace node
436 
437 NODE_BINDING_CONTEXT_AWARE_INTERNAL(watchdog, node::watchdog::Initialize)
438