GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_watchdog.cc Lines: 162 192 84.4 %
Date: 2022-05-23 04:15:47 Branches: 37 68 54.4 %

Line Branch Exec Source
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
41
18
Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out)
42
18
    : isolate_(isolate), timed_out_(timed_out) {
43
44
  int rc;
45
18
  rc = uv_loop_init(&loop_);
46
18
  if (rc != 0) {
47
    FatalError("node::Watchdog::Watchdog()",
48
               "Failed to initialize uv loop.");
49
  }
50
51
18
  rc = uv_async_init(&loop_, &async_, [](uv_async_t* signal) {
52
5
    Watchdog* w = ContainerOf(&Watchdog::async_, signal);
53
5
    uv_stop(&w->loop_);
54
5
  });
55
56
18
  CHECK_EQ(0, rc);
57
58
18
  rc = uv_timer_init(&loop_, &timer_);
59
18
  CHECK_EQ(0, rc);
60
61
18
  rc = uv_timer_start(&timer_, &Watchdog::Timer, ms, 0);
62
18
  CHECK_EQ(0, rc);
63
64
18
  rc = uv_thread_create(&thread_, &Watchdog::Run, this);
65
18
  CHECK_EQ(0, rc);
66
18
}
67
68
69
36
Watchdog::~Watchdog() {
70
18
  uv_async_send(&async_);
71
18
  uv_thread_join(&thread_);
72
73
18
  uv_close(reinterpret_cast<uv_handle_t*>(&async_), nullptr);
74
75
  // UV_RUN_DEFAULT so that libuv has a chance to clean up.
76
18
  uv_run(&loop_, UV_RUN_DEFAULT);
77
78
18
  CheckedUvLoopClose(&loop_);
79
18
}
80
81
82
18
void Watchdog::Run(void* arg) {
83
18
  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
18
  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
18
  uv_close(reinterpret_cast<uv_handle_t*>(&wd->timer_), nullptr);
92
18
}
93
94
13
void Watchdog::Timer(uv_timer_t* timer) {
95
13
  Watchdog* w = ContainerOf(&Watchdog::timer_, timer);
96
13
  *w->timed_out_ = true;
97
13
  w->isolate()->TerminateExecution();
98
13
  uv_stop(&w->loop_);
99
13
}
100
101
102
192
SigintWatchdog::SigintWatchdog(
103
192
  v8::Isolate* isolate, bool* received_signal)
104
192
    : isolate_(isolate), received_signal_(received_signal) {
105
  // Register this watchdog with the global SIGINT/Ctrl+C listener.
106
192
  SigintWatchdogHelper::GetInstance()->Register(this);
107
  // Start the helper thread, if that has not already happened.
108
192
  SigintWatchdogHelper::GetInstance()->Start();
109
192
}
110
111
112
378
SigintWatchdog::~SigintWatchdog() {
113
378
  SigintWatchdogHelper::GetInstance()->Unregister(this);
114
378
  SigintWatchdogHelper::GetInstance()->Stop();
115
}
116
117
11
SignalPropagation SigintWatchdog::HandleSigint() {
118
11
  *received_signal_ = true;
119
11
  isolate_->TerminateExecution();
120
11
  return SignalPropagation::kStopPropagation;
121
}
122
123
2
void TraceSigintWatchdog::Init(Environment* env, Local<Object> target) {
124
2
  Local<FunctionTemplate> constructor = env->NewFunctionTemplate(New);
125
4
  constructor->InstanceTemplate()->SetInternalFieldCount(
126
      TraceSigintWatchdog::kInternalFieldCount);
127
2
  constructor->Inherit(HandleWrap::GetConstructorTemplate(env));
128
129
2
  env->SetProtoMethod(constructor, "start", Start);
130
2
  env->SetProtoMethod(constructor, "stop", Stop);
131
132
2
  env->SetConstructorFunction(target, "TraceSigintWatchdog", constructor);
133
2
}
134
135
1
void TraceSigintWatchdog::New(const FunctionCallbackInfo<Value>& args) {
136
  // This constructor should not be exposed to public javascript.
137
  // Therefore we assert that we are not trying to call this as a
138
  // normal function.
139
1
  CHECK(args.IsConstructCall());
140
1
  Environment* env = Environment::GetCurrent(args);
141
1
  new TraceSigintWatchdog(env, args.This());
142
1
}
143
144
2
void TraceSigintWatchdog::Start(const FunctionCallbackInfo<Value>& args) {
145
  TraceSigintWatchdog* watchdog;
146
2
  ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
147
  // Register this watchdog with the global SIGINT/Ctrl+C listener.
148
2
  SigintWatchdogHelper::GetInstance()->Register(watchdog);
149
  // Start the helper thread, if that has not already happened.
150
2
  int r = SigintWatchdogHelper::GetInstance()->Start();
151
2
  CHECK_EQ(r, 0);
152
}
153
154
2
void TraceSigintWatchdog::Stop(const FunctionCallbackInfo<Value>& args) {
155
  TraceSigintWatchdog* watchdog;
156
2
  ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
157
2
  SigintWatchdogHelper::GetInstance()->Unregister(watchdog);
158
2
  SigintWatchdogHelper::GetInstance()->Stop();
159
}
160
161
1
TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local<Object> object)
162
    : HandleWrap(env,
163
                 object,
164
1
                 reinterpret_cast<uv_handle_t*>(&handle_),
165
1
                 AsyncWrap::PROVIDER_SIGINTWATCHDOG) {
166
1
  int r = uv_async_init(env->event_loop(), &handle_, [](uv_async_t* handle) {
167
    TraceSigintWatchdog* watchdog =
168
        ContainerOf(&TraceSigintWatchdog::handle_, handle);
169
    watchdog->signal_flag_ = SignalFlags::FromIdle;
170
    watchdog->HandleInterrupt();
171
1
  });
172
1
  CHECK_EQ(r, 0);
173
1
  uv_unref(reinterpret_cast<uv_handle_t*>(&handle_));
174
1
}
175
176
SignalPropagation TraceSigintWatchdog::HandleSigint() {
177
  /**
178
   * In case of uv loop polling, i.e. no JS currently running, activate the
179
   * loop to run a piece of JS code to trigger interruption.
180
   */
181
  CHECK_EQ(uv_async_send(&handle_), 0);
182
  env()->isolate()->RequestInterrupt(
183
      [](v8::Isolate* isolate, void* data) {
184
        TraceSigintWatchdog* self = static_cast<TraceSigintWatchdog*>(data);
185
        if (self->signal_flag_ == SignalFlags::None) {
186
          self->signal_flag_ = SignalFlags::FromInterrupt;
187
        }
188
        self->HandleInterrupt();
189
      },
190
      this);
191
  return SignalPropagation::kContinuePropagation;
192
}
193
194
void TraceSigintWatchdog::HandleInterrupt() {
195
  // Do not nest interrupts.
196
  if (interrupting) {
197
    return;
198
  }
199
  interrupting = true;
200
  if (signal_flag_ == SignalFlags::None) {
201
    return;
202
  }
203
  Environment* env_ = env();
204
  // FIXME: Before
205
  // https://github.com/nodejs/node/pull/29207#issuecomment-527667993 get
206
  // fixed, additional JavaScript code evaluation shall be prevented from
207
  // running during interruption.
208
  FPrintF(stderr,
209
      "KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`\n");
210
  if (signal_flag_ == SignalFlags::FromInterrupt) {
211
    PrintStackTrace(env_->isolate(),
212
                    v8::StackTrace::CurrentStackTrace(
213
                        env_->isolate(), 10, v8::StackTrace::kDetailed));
214
  }
215
  signal_flag_ = SignalFlags::None;
216
  interrupting = false;
217
218
  SigintWatchdogHelper::GetInstance()->Unregister(this);
219
  SigintWatchdogHelper::GetInstance()->Stop();
220
  raise(SIGINT);
221
}
222
223
#ifdef __POSIX__
224
212
void* SigintWatchdogHelper::RunSigintWatchdog(void* arg) {
225
  // Inside the helper thread.
226
  bool is_stopping;
227
228
14
  do {
229
212
    uv_sem_wait(&instance.sem_);
230
212
    is_stopping = InformWatchdogsAboutSignal();
231
212
  } while (!is_stopping);
232
233
198
  return nullptr;
234
}
235
236
14
void SigintWatchdogHelper::HandleSignal(int signum,
237
                                        siginfo_t* info,
238
                                        void* ucontext) {
239
14
  uv_sem_post(&instance.sem_);
240
14
}
241
242
#else
243
244
// Windows starts a separate thread for executing the handler, so no extra
245
// helper thread is required.
246
BOOL WINAPI SigintWatchdogHelper::WinCtrlCHandlerRoutine(DWORD dwCtrlType) {
247
  if (!instance.watchdog_disabled_ &&
248
      (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT)) {
249
    InformWatchdogsAboutSignal();
250
251
    // Return true because the signal has been handled.
252
    return TRUE;
253
  } else {
254
    return FALSE;
255
  }
256
}
257
#endif
258
259
260
212
bool SigintWatchdogHelper::InformWatchdogsAboutSignal() {
261
212
  Mutex::ScopedLock list_lock(instance.list_mutex_);
262
263
212
  bool is_stopping = false;
264
#ifdef __POSIX__
265
212
  is_stopping = instance.stopping_;
266
#endif
267
268
  // If there are no listeners and the helper thread has been awoken by a signal
269
  // (= not when stopping it), indicate that by setting has_pending_signal_.
270

212
  if (instance.watchdogs_.empty() && !is_stopping) {
271
3
    instance.has_pending_signal_ = true;
272
  }
273
274
212
  for (auto it = instance.watchdogs_.rbegin(); it != instance.watchdogs_.rend();
275
       it++) {
276
11
    SignalPropagation wp = (*it)->HandleSigint();
277
11
    if (wp == SignalPropagation::kStopPropagation) {
278
11
      break;
279
    }
280
  }
281
282
212
  return is_stopping;
283
}
284
285
286
383
int SigintWatchdogHelper::Start() {
287
766
  Mutex::ScopedLock lock(mutex_);
288
289
383
  if (start_stop_count_++ > 0) {
290
185
    return 0;
291
  }
292
293
#ifdef __POSIX__
294
198
  CHECK_EQ(has_running_thread_, false);
295
198
  has_pending_signal_ = false;
296
198
  stopping_ = false;
297
298
  sigset_t sigmask;
299
198
  sigfillset(&sigmask);
300
  sigset_t savemask;
301
198
  CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask));
302
198
  sigmask = savemask;
303
198
  int ret = pthread_create(&thread_, nullptr, RunSigintWatchdog, nullptr);
304
198
  CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
305
198
  if (ret != 0) {
306
    return ret;
307
  }
308
198
  has_running_thread_ = true;
309
310
198
  RegisterSignalHandler(SIGINT, HandleSignal);
311
#else
312
  if (watchdog_disabled_) {
313
    watchdog_disabled_ = false;
314
  } else {
315
    SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, TRUE);
316
  }
317
#endif
318
319
198
  return 0;
320
}
321
322
323
5632
bool SigintWatchdogHelper::Stop() {
324
  bool had_pending_signal;
325
11264
  Mutex::ScopedLock lock(mutex_);
326
327
  {
328
5632
    Mutex::ScopedLock list_lock(list_mutex_);
329
330
5632
    had_pending_signal = has_pending_signal_;
331
332
5632
    if (--start_stop_count_ > 0) {
333
182
      has_pending_signal_ = false;
334
182
      return had_pending_signal;
335
    }
336
337
#ifdef __POSIX__
338
    // Set stopping now because it's only protected by list_mutex_.
339
5450
    stopping_ = true;
340
#endif
341
342
5450
    watchdogs_.clear();
343
  }
344
345
#ifdef __POSIX__
346
5450
  if (!has_running_thread_) {
347
5252
    has_pending_signal_ = false;
348
5252
    return had_pending_signal;
349
  }
350
351
  // Wake up the helper thread.
352
198
  uv_sem_post(&sem_);
353
354
  // Wait for the helper thread to finish.
355
198
  CHECK_EQ(0, pthread_join(thread_, nullptr));
356
198
  has_running_thread_ = false;
357
358
198
  RegisterSignalHandler(SIGINT, SignalExit, true);
359
#else
360
  watchdog_disabled_ = true;
361
#endif
362
363
198
  had_pending_signal = has_pending_signal_;
364
198
  has_pending_signal_ = false;
365
366
198
  return had_pending_signal;
367
}
368
369
370
3
bool SigintWatchdogHelper::HasPendingSignal() {
371
3
  Mutex::ScopedLock lock(list_mutex_);
372
373
3
  return has_pending_signal_;
374
}
375
376
194
void SigintWatchdogHelper::Register(SigintWatchdogBase* wd) {
377
388
  Mutex::ScopedLock lock(list_mutex_);
378
379
194
  watchdogs_.push_back(wd);
380
194
}
381
382
191
void SigintWatchdogHelper::Unregister(SigintWatchdogBase* wd) {
383
191
  Mutex::ScopedLock lock(list_mutex_);
384
385
191
  auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd);
386
387
191
  CHECK_NE(it, watchdogs_.end());
388
191
  watchdogs_.erase(it);
389
191
}
390
391
392
5255
SigintWatchdogHelper::SigintWatchdogHelper()
393
    : start_stop_count_(0),
394
5255
      has_pending_signal_(false) {
395
#ifdef __POSIX__
396
5255
  has_running_thread_ = false;
397
5255
  stopping_ = false;
398
5255
  CHECK_EQ(0, uv_sem_init(&sem_, 0));
399
#else
400
  watchdog_disabled_ = false;
401
#endif
402
5255
}
403
404
405
5255
SigintWatchdogHelper::~SigintWatchdogHelper() {
406
5255
  start_stop_count_ = 0;
407
5255
  Stop();
408
409
#ifdef __POSIX__
410
5255
  CHECK_EQ(has_running_thread_, false);
411
5255
  uv_sem_destroy(&sem_);
412
#endif
413
5255
}
414
415
SigintWatchdogHelper SigintWatchdogHelper::instance;
416
417
namespace watchdog {
418
2
static void Initialize(Local<Object> target,
419
                       Local<Value> unused,
420
                       Local<Context> context,
421
                       void* priv) {
422
2
  Environment* env = Environment::GetCurrent(context);
423
2
  TraceSigintWatchdog::Init(env, target);
424
2
}
425
}  // namespace watchdog
426
427
}  // namespace node
428
429
5255
NODE_MODULE_CONTEXT_AWARE_INTERNAL(watchdog, node::watchdog::Initialize)