-
Notifications
You must be signed in to change notification settings - Fork 18
/
tripwire_win.cc
153 lines (126 loc) · 5.34 KB
/
tripwire_win.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <process.h>
#include <node.h>
#include <v8.h>
#include <nan.h>
HANDLE scriptThread;
HANDLE tripwireThread;
HANDLE event;
extern unsigned int tripwireThreshold;
extern int terminated;
extern v8::Isolate* isolate;
#if (NODE_MODULE_VERSION >= NODE_0_12_MODULE_VERSION)
extern void interruptCallback(v8::Isolate *isolate, void *data);
#endif
void tripwireWorker(void* data)
{
BOOL skipTimeCapture = FALSE;
ULARGE_INTEGER su, sk, eu, ek;
FILETIME tmp;
// This thread monitors the elapsed CPU utilization time of the node.js thread and forces V8 to terminate
// execution if it exceeds the preconfigured tripwireThreshold.
while (1)
{
// Unless the threshold validation logic requested to keep the current thread time utilization values,
// capture the current user mode and kernel mode CPU utilization time of the thread on which node.js executes
// application code.
if (skipTimeCapture)
skipTimeCapture = FALSE;
else
GetThreadTimes(scriptThread, &tmp, &tmp, (LPFILETIME)&sk.u, (LPFILETIME)&su.u);
// Wait on the auto reset event. The event will be signalled in one of two cases:
// 1. When the timeout value equal to tripwireThreshold elapses, or
// 2. When the event is explicitly signalled from resetTripwire.
// A tripwireThreshold value of 0 indicates the tripwire mechanism is turned off, in which case
// an inifite wait is initiated on the event (which will only be terminated with an explicit signal
// during subsequent call to resetThreashold).
if (WAIT_TIMEOUT == WaitForSingleObject(event, 0 == tripwireThreshold ? INFINITE : tripwireThreshold))
{
// If the wait result on the event is WAIT_TIMEOUT, it means resetThreshold
// was called in the tripwireThreshold period since the last call to resetThreshold. This indicates
// a possibility that the node.js thread is blocked.
// If tripwireThreshold is 0 at this point, however, it means a call to clearTripwire was made
// since the last call to resetThreshold. In this case we just skip tripwire enforcement and
// proceed to wait for a subsequent event.
if (0 < tripwireThreshold)
{
// Take a snapshot of the current kernel and user mode CPU utilization time of the node.js thread
// to determine if the elapsed CPU utilization time exceeded the preconfigured tripwireThreshold.
// Despite the fact this code only ever executes after the auto reset event has already timeout out
// after the tripwireThreshold amount of time without hearing from the node.js thread, it need not
// necessarily mean that the node.js thread exceeded that execution time threshold. It might not
// have been running at all in that period, subject to OS scheduling.
GetThreadTimes(scriptThread, &tmp, &tmp, (LPFILETIME)&ek.u, (LPFILETIME)&eu.u);
ULONGLONG elapsed100Ns = ek.QuadPart - sk.QuadPart + eu.QuadPart - su.QuadPart;
// Thread execution times are reported in 100ns units. Convert to milliseconds.
DWORD elapsedMs = elapsed100Ns / 10000;
// If the actual CPU execution time of the node.js thread exceeded the threshold, terminate
// the V8 process. Otherwise wait again while maintaining the current snapshot of the initial
// time utilization. This mechanism results in termination of a runaway thread some time in the
// (tripwireThreshold, 2 * tripwireThreshold) range of CPU utilization.
if (elapsedMs >= tripwireThreshold)
{
terminated = 1;
v8::V8::TerminateExecution(isolate);
#if (NODE_MODULE_VERSION >= NODE_0_12_MODULE_VERSION)
isolate->RequestInterrupt(interruptCallback, NULL);
#endif
}
else
{
skipTimeCapture = 1;
}
}
}
}
}
v8::Local<v8::Value> resetTripwireCore()
{
Nan::EscapableHandleScope scope;
if (NULL == tripwireThread)
{
// This is the first call to resetTripwire. Perform lazy initialization.
// Create the auto reset event that will be used for signalling future changes
// of the tripwireThreshold value to the worker thread.
if (NULL == (event = CreateEvent(NULL, FALSE, FALSE, NULL)))
{
Nan::ThrowError("Unable to initialize a tripwire thread.");
}
// Capture the current thread handle as the thread on which node.js executes user code. The
// worker process measures the CPU utilization of this thread to determine if the execution time
// threshold has been exceeded.
if (!DuplicateHandle(
GetCurrentProcess(),
GetCurrentThread(),
GetCurrentProcess(),
&scriptThread,
0,
FALSE,
DUPLICATE_SAME_ACCESS))
{
CloseHandle(event);
event = NULL;
Nan::ThrowError("Unable to duplicate handle of the script thread.");
}
// Create the worker thread.
if (NULL == (tripwireThread = (HANDLE)_beginthread(tripwireWorker, 4096, NULL)))
{
CloseHandle(event);
event = NULL;
CloseHandle(scriptThread);
scriptThread = 0;
Nan::ThrowError("Unable to initialize a tripwire thread.");
}
}
else
{
// Signal the already existing worker thread using the auto reset event.
// This will cause the worker thread to
// reset the elapsed time timer and pick up the new tripwireThreshold value.
SetEvent(event);
}
return scope.Escape(Nan::Undefined());
}
void initCore()
{
tripwireThread = scriptThread = event = NULL;
}