GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_watchdog.cc Lines: 167 198 84.3 %
Date: 2022-12-07 04:23:16 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::Isolate;
38
using v8::Local;
39
using v8::Object;
40
using v8::Value;
41
42
18
Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out)
43
18
    : isolate_(isolate), timed_out_(timed_out) {
44
45
  int rc;
46
18
  rc = uv_loop_init(&loop_);
47
18
  if (rc != 0) {
48
    FatalError("node::Watchdog::Watchdog()",
49
               "Failed to initialize uv loop.");
50
  }
51
52
18
  rc = uv_async_init(&loop_, &async_, [](uv_async_t* signal) {
53
5
    Watchdog* w = ContainerOf(&Watchdog::async_, signal);
54
5
    uv_stop(&w->loop_);
55
5
  });
56
57
18
  CHECK_EQ(0, rc);
58
59
18
  rc = uv_timer_init(&loop_, &timer_);
60
18
  CHECK_EQ(0, rc);
61
62
18
  rc = uv_timer_start(&timer_, &Watchdog::Timer, ms, 0);
63
18
  CHECK_EQ(0, rc);
64
65
18
  rc = uv_thread_create(&thread_, &Watchdog::Run, this);
66
18
  CHECK_EQ(0, rc);
67
18
}
68
69
70
36
Watchdog::~Watchdog() {
71
18
  uv_async_send(&async_);
72
18
  uv_thread_join(&thread_);
73
74
18
  uv_close(reinterpret_cast<uv_handle_t*>(&async_), nullptr);
75
76
  // UV_RUN_DEFAULT so that libuv has a chance to clean up.
77
18
  uv_run(&loop_, UV_RUN_DEFAULT);
78
79
18
  CheckedUvLoopClose(&loop_);
80
18
}
81
82
83
18
void Watchdog::Run(void* arg) {
84
18
  Watchdog* wd = static_cast<Watchdog*>(arg);
85
86
  // UV_RUN_DEFAULT the loop will be stopped either by the async or the
87
  // timer handle.
88
18
  uv_run(&wd->loop_, UV_RUN_DEFAULT);
89
90
  // Loop ref count reaches zero when both handles are closed.
91
  // Close the timer handle on this side and let ~Watchdog() close async_
92
18
  uv_close(reinterpret_cast<uv_handle_t*>(&wd->timer_), nullptr);
93
18
}
94
95
13
void Watchdog::Timer(uv_timer_t* timer) {
96
13
  Watchdog* w = ContainerOf(&Watchdog::timer_, timer);
97
13
  *w->timed_out_ = true;
98
13
  w->isolate()->TerminateExecution();
99
13
  uv_stop(&w->loop_);
100
13
}
101
102
103
1201
SigintWatchdog::SigintWatchdog(
104
1201
  v8::Isolate* isolate, bool* received_signal)
105
1201
    : isolate_(isolate), received_signal_(received_signal) {
106
2402
  Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
107
  // Register this watchdog with the global SIGINT/Ctrl+C listener.
108
1201
  SigintWatchdogHelper::GetInstance()->Register(this);
109
  // Start the helper thread, if that has not already happened.
110
1201
  SigintWatchdogHelper::GetInstance()->Start();
111
1201
}
112
113
114
2396
SigintWatchdog::~SigintWatchdog() {
115
4792
  Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
116
2396
  SigintWatchdogHelper::GetInstance()->Unregister(this);
117
2396
  SigintWatchdogHelper::GetInstance()->Stop();
118
}
119
120
11
SignalPropagation SigintWatchdog::HandleSigint() {
121
11
  *received_signal_ = true;
122
11
  isolate_->TerminateExecution();
123
11
  return SignalPropagation::kStopPropagation;
124
}
125
126
2
void TraceSigintWatchdog::Init(Environment* env, Local<Object> target) {
127
2
  Isolate* isolate = env->isolate();
128
2
  Local<FunctionTemplate> constructor = NewFunctionTemplate(isolate, New);
129
4
  constructor->InstanceTemplate()->SetInternalFieldCount(
130
      TraceSigintWatchdog::kInternalFieldCount);
131
2
  constructor->Inherit(HandleWrap::GetConstructorTemplate(env));
132
133
2
  SetProtoMethod(isolate, constructor, "start", Start);
134
2
  SetProtoMethod(isolate, constructor, "stop", Stop);
135
136
2
  SetConstructorFunction(
137
      env->context(), target, "TraceSigintWatchdog", constructor);
138
2
}
139
140
1
void TraceSigintWatchdog::New(const FunctionCallbackInfo<Value>& args) {
141
  // This constructor should not be exposed to public javascript.
142
  // Therefore we assert that we are not trying to call this as a
143
  // normal function.
144
1
  CHECK(args.IsConstructCall());
145
1
  Environment* env = Environment::GetCurrent(args);
146
1
  new TraceSigintWatchdog(env, args.This());
147
1
}
148
149
2
void TraceSigintWatchdog::Start(const FunctionCallbackInfo<Value>& args) {
150
  TraceSigintWatchdog* watchdog;
151
2
  ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
152
4
  Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
153
  // Register this watchdog with the global SIGINT/Ctrl+C listener.
154
2
  SigintWatchdogHelper::GetInstance()->Register(watchdog);
155
  // Start the helper thread, if that has not already happened.
156
2
  int r = SigintWatchdogHelper::GetInstance()->Start();
157
2
  CHECK_EQ(r, 0);
158
}
159
160
2
void TraceSigintWatchdog::Stop(const FunctionCallbackInfo<Value>& args) {
161
  TraceSigintWatchdog* watchdog;
162
2
  ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
163
4
  Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
164
2
  SigintWatchdogHelper::GetInstance()->Unregister(watchdog);
165
2
  SigintWatchdogHelper::GetInstance()->Stop();
166
}
167
168
1
TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local<Object> object)
169
    : HandleWrap(env,
170
                 object,
171
1
                 reinterpret_cast<uv_handle_t*>(&handle_),
172
1
                 AsyncWrap::PROVIDER_SIGINTWATCHDOG) {
173
1
  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
1
  });
179
1
  CHECK_EQ(r, 0);
180
1
  uv_unref(reinterpret_cast<uv_handle_t*>(&handle_));
181
1
}
182
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
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
  Mutex::ScopedLock lock(SigintWatchdogHelper::GetInstanceActionMutex());
226
  SigintWatchdogHelper::GetInstance()->Unregister(this);
227
  SigintWatchdogHelper::GetInstance()->Stop();
228
  raise(SIGINT);
229
}
230
231
#ifdef __POSIX__
232
516
void* SigintWatchdogHelper::RunSigintWatchdog(void* arg) {
233
  // Inside the helper thread.
234
  bool is_stopping;
235
236
14
  do {
237
516
    uv_sem_wait(&instance.sem_);
238
516
    is_stopping = InformWatchdogsAboutSignal();
239
516
  } while (!is_stopping);
240
241
502
  return nullptr;
242
}
243
244
14
void SigintWatchdogHelper::HandleSignal(int signum,
245
                                        siginfo_t* info,
246
                                        void* ucontext) {
247
14
  uv_sem_post(&instance.sem_);
248
14
}
249
250
#else
251
252
// Windows starts a separate thread for executing the handler, so no extra
253
// helper thread is required.
254
BOOL WINAPI SigintWatchdogHelper::WinCtrlCHandlerRoutine(DWORD dwCtrlType) {
255
  if (!instance.watchdog_disabled_ &&
256
      (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT)) {
257
    InformWatchdogsAboutSignal();
258
259
    // Return true because the signal has been handled.
260
    return TRUE;
261
  } else {
262
    return FALSE;
263
  }
264
}
265
#endif
266
267
268
516
bool SigintWatchdogHelper::InformWatchdogsAboutSignal() {
269
516
  Mutex::ScopedLock list_lock(instance.list_mutex_);
270
271
516
  bool is_stopping = false;
272
#ifdef __POSIX__
273
516
  is_stopping = instance.stopping_;
274
#endif
275
276
  // If there are no listeners and the helper thread has been awoken by a signal
277
  // (= not when stopping it), indicate that by setting has_pending_signal_.
278

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