Skip to content
This repository was archived by the owner on Mar 29, 2024. It is now read-only.

Commit 22e0f6b

Browse files
committed
Allow to recover from limit his and make V8\Isolate time limit affects js runtime only
1 parent 04d9e6f commit 22e0f6b

5 files changed

+276
-20
lines changed

Diff for: src/php_v8_isolate_limits.cc

+65-20
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
#define php_v8_debug_execution(format, ...)
3333
#endif
3434

35+
static inline void php_v8_isolate_limits_update_time_point(php_v8_isolate_limits_t *limits) {
36+
php_v8_debug_execution("Updating time limits\n");
37+
38+
std::chrono::milliseconds duration(static_cast<int64_t>(limits->time_limit * 1000));
39+
std::chrono::time_point<std::chrono::high_resolution_clock> from = std::chrono::high_resolution_clock::now();
40+
41+
php_v8_debug_execution(" now: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(from).time_since_epoch().count()/1000.0);
42+
php_v8_debug_execution(" old time point: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(limits->time_point).time_since_epoch().count()/1000.0);
43+
44+
limits->time_point = from + duration;
45+
php_v8_debug_execution(" new time point: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(limits->time_point).time_since_epoch().count()/1000.0);
46+
}
47+
48+
static inline void php_v8_isolate_limits_maybe_terminate_thread(php_v8_isolate_limits_t *limits) {
49+
if (!limits->active && limits->thread) {
50+
limits->thread->join();
51+
delete limits->thread;
52+
limits->thread = NULL;
53+
}
54+
}
55+
3556
static void php_v8_isolate_limits_interrupt_handler(v8::Isolate *isolate, void *data) {
3657
php_v8_isolate_t *php_v8_isolate = static_cast<php_v8_isolate_t *>(data);
3758
php_v8_isolate_limits_t *limits = &php_v8_isolate->limits;
@@ -81,12 +102,17 @@ void php_v8_isolate_limits_thread(php_v8_isolate_t *php_v8_isolate) {
81102
limits->mutex->lock();
82103

83104
if (limits->active && limits->time_limit > 0) {
105+
84106
now = std::chrono::high_resolution_clock::now();
85107

86108
if (now > limits->time_point) {
109+
php_v8_debug_execution("Time limit reached, terminating\n");
110+
php_v8_debug_execution(" now: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(now).time_since_epoch().count()/1000.0);
111+
php_v8_debug_execution(" time point: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(limits->time_point).time_since_epoch().count()/1000.0);
112+
113+
limits->time_limit_hit = true;
87114
limits->active = false;
88115
php_v8_isolate->isolate->TerminateExecution();
89-
limits->time_limit_hit = true;
90116
}
91117
}
92118

@@ -103,6 +129,8 @@ void php_v8_isolate_limits_thread(php_v8_isolate_t *php_v8_isolate) {
103129
limits->mutex->unlock();
104130

105131
if (!limits->active) {
132+
php_v8_debug_execution("Exit timer loop: %s, %s\n", has(limits->mutex, "mutex"), has(limits->thread, "thread"));
133+
php_v8_debug_execution(" active: %s, depth: %d, time limit hit: %d, memory limit hit: %d\n", is(limits->active), limits->depth, limits->time_limit_hit, limits->memory_limit_hit);
106134
return;
107135
}
108136

@@ -113,6 +141,8 @@ void php_v8_isolate_limits_thread(php_v8_isolate_t *php_v8_isolate) {
113141
void php_v8_isolate_limits_maybe_start_timer(php_v8_isolate_t *php_v8_isolate) {
114142
php_v8_isolate_limits_t *limits = &php_v8_isolate->limits;
115143

144+
php_v8_debug_execution("Maybe start timer: %d, %s, %s\n", limits->depth, has(limits->mutex, "mutex"), has(limits->thread, "thread"));
145+
116146
assert (limits->depth < UINT32_MAX);
117147

118148
if (!limits->mutex) {
@@ -124,10 +154,14 @@ void php_v8_isolate_limits_maybe_start_timer(php_v8_isolate_t *php_v8_isolate) {
124154
limits->depth++;
125155

126156
if (limits->active && !limits->thread) {
157+
php_v8_isolate_limits_update_time_point(limits);
158+
159+
php_v8_debug_execution(" start timer\n");
127160
limits->thread = new std::thread(php_v8_isolate_limits_thread, php_v8_isolate);
128161
}
129162
}
130163

164+
131165
void php_v8_isolate_limits_maybe_stop_timer(php_v8_isolate_t *php_v8_isolate) {
132166
php_v8_isolate_limits_t *limits = &php_v8_isolate->limits;
133167

@@ -172,6 +206,8 @@ void php_v8_isolate_limits_free(php_v8_isolate_t *php_v8_isolate) {
172206
if (limits->mutex) {
173207
delete limits->mutex;
174208
}
209+
210+
limits->time_point.~time_point();
175211
}
176212

177213
void php_v8_isolate_limits_ctor(php_v8_isolate_t *php_v8_isolate) {
@@ -180,13 +216,15 @@ void php_v8_isolate_limits_ctor(php_v8_isolate_t *php_v8_isolate) {
180216
limits->thread = NULL;
181217
limits->mutex = NULL;
182218
limits->depth = 0;
219+
220+
new(&limits->time_point) std::chrono::time_point<std::chrono::high_resolution_clock>();
183221
}
184222

185223
void php_v8_isolate_limits_set_time_limit(php_v8_isolate_t *php_v8_isolate, double time_limit_in_seconds) {
186224
PHP_V8_DECLARE_ISOLATE(php_v8_isolate);
187225
PHP_V8_DECLARE_LIMITS(php_v8_isolate);
188226

189-
assert(time_limit_in_seconds >=0);
227+
assert(time_limit_in_seconds >= 0);
190228

191229
v8::Locker locker(isolate);
192230

@@ -196,28 +234,30 @@ void php_v8_isolate_limits_set_time_limit(php_v8_isolate_t *php_v8_isolate, doub
196234

197235
limits->mutex->lock();
198236

199-
std::chrono::milliseconds duration(static_cast<int64_t>(time_limit_in_seconds * 1000));
200-
std::chrono::time_point<std::chrono::high_resolution_clock> from = std::chrono::high_resolution_clock::now();
201-
237+
php_v8_debug_execution("Setting time limits, new limit: %f, old limit: %f, time_limit_hit: %s\n", time_limit_in_seconds, limits->time_limit, is(limits->time_limit_hit));
202238
limits->time_limit = time_limit_in_seconds;
203-
limits->time_point = from + duration;
204-
limits->time_limit_hit = false;
239+
php_v8_isolate_limits_update_time_point(limits);
240+
241+
if (limits->time_limit_hit) {
242+
php_v8_debug_execution(" trying to recover from time limit hit, active: %s\n", is(limits->active));
243+
244+
isolate->CancelTerminateExecution();
245+
246+
php_v8_isolate_limits_maybe_terminate_thread(limits);
247+
limits->time_limit_hit = false;
248+
}
205249

206250
limits->active = (limits->time_limit > 0 || limits->memory_limit > 0)
207251
&& !limits->time_limit_hit
208252
&& !limits->memory_limit_hit;
209253

210254
if (limits->active && limits->depth && !limits->thread) {
255+
php_v8_debug_execution("Restart timer: %d, %s, %s\n", limits->depth, has(limits->memory_limit_hit, "memory limit hit"), has(limits->time_limit_hit, "time limit hit"));
211256
limits->thread = new std::thread(php_v8_isolate_limits_thread, php_v8_isolate);
212257
}
213258

214259
limits->mutex->unlock();
215-
216-
if (!limits->active && limits->thread) {
217-
limits->thread->join();
218-
delete limits->thread;
219-
limits->thread = NULL;
220-
}
260+
php_v8_isolate_limits_maybe_terminate_thread(limits);
221261
}
222262

223263
void php_v8_isolate_limits_set_memory_limit(php_v8_isolate_t *php_v8_isolate, size_t memory_limit_in_bytes) {
@@ -232,22 +272,27 @@ void php_v8_isolate_limits_set_memory_limit(php_v8_isolate_t *php_v8_isolate, si
232272

233273
limits->mutex->lock();
234274

275+
php_v8_debug_execution("Updating memory limits, memory_limit_hit: %s\n", is(limits->memory_limit_hit));
235276
limits->memory_limit = memory_limit_in_bytes;
236-
limits->memory_limit_hit = false;
277+
278+
if (limits->memory_limit_hit) {
279+
php_v8_debug_execution(" trying to recover from memory limit hit, active: %s\n", is(limits->active));
280+
281+
isolate->CancelTerminateExecution();
282+
283+
php_v8_isolate_limits_maybe_terminate_thread(limits);
284+
limits->memory_limit_hit = false;
285+
}
237286

238287
limits->active = (limits->time_limit > 0 || limits->memory_limit > 0)
239288
&& !limits->time_limit_hit
240289
&& !limits->memory_limit_hit;
241290

242291
if (limits->active && limits->depth && !limits->thread) {
292+
php_v8_debug_execution("Restart timer: %d, %s, %s\n", limits->depth, has(limits->memory_limit_hit, "memory limit hit"), has(limits->time_limit_hit, "time limit hit"));
243293
limits->thread = new std::thread(php_v8_isolate_limits_thread, php_v8_isolate);
244294
}
245295

246296
limits->mutex->unlock();
247-
248-
if (!limits->active && limits->thread) {
249-
limits->thread->join();
250-
delete limits->thread;
251-
limits->thread = NULL;
252-
}
297+
php_v8_isolate_limits_maybe_terminate_thread(limits);
253298
}

Diff for: src/php_v8_isolate_limits.h

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ extern void php_v8_isolate_limits_ctor(php_v8_isolate_t *php_v8_isolate);
3131

3232
extern void php_v8_isolate_limits_set_time_limit(php_v8_isolate_t *php_v8_isolate, double time_limit_in_seconds);
3333
extern void php_v8_isolate_limits_set_memory_limit(php_v8_isolate_t *php_v8_isolate, size_t memory_limit_in_bytes);
34+
extern void php_v8_isolate_limits_set_limits(php_v8_isolate_t *php_v8_isolate, double time_limit_in_seconds, size_t memory_limit_in_bytes);
3435

3536
#define PHP_V8_DECLARE_ISOLATE_LOCAL_ALIAS(i) v8::Isolate *isolate = (i);
3637

Diff for: tests/.testsuite.php

+13
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,19 @@ public function need_more_time() {
299299
// NOTE: this check is a bit fragile but should fits our need
300300
return isset($_ENV['TRAVIS']) && isset($_ENV['TEST_PHP_ARGS']) && $_ENV['TEST_PHP_ARGS'] == '-m';
301301
}
302+
303+
public function is_memory_test() {
304+
// NOTE: this check is a bit fragile but should fits our need
305+
if (!isset($_SERVER['ZEND_DONT_UNLOAD_MODULES']) || !$_SERVER['ZEND_DONT_UNLOAD_MODULES']) {
306+
return false;
307+
}
308+
309+
if (isset($_SERVER['USE_ZEND_ALLOC']) && $_SERVER['USE_ZEND_ALLOC']) {
310+
return false;
311+
}
312+
313+
return isset($_SERVER['LD_PRELOAD']) && false != strpos($_SERVER['LD_PRELOAD'], '/valgrind/');
314+
}
302315
}
303316

304317
interface FilterInterface
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--TEST--
2+
V8\Isolate - time limit affects js runtime only
3+
--SKIPIF--
4+
<?php if (!extension_loaded("v8")) print "skip"; ?>
5+
--FILE--
6+
<?php
7+
8+
/** @var \Phpv8Testsuite $helper */
9+
$helper = require '.testsuite.php';
10+
11+
require '.v8-helpers.php';
12+
$v8_helper = new PhpV8Helpers($helper);
13+
14+
// Tests:
15+
16+
$isolate = new V8\Isolate();
17+
$context = new V8\Context($isolate);
18+
19+
20+
$source = '
21+
var i = 0;
22+
while(true) { i++};
23+
';
24+
$file_name = 'test.js';
25+
26+
$script = new V8\Script($context, new \V8\StringValue($isolate, $source), new \V8\ScriptOrigin($file_name));
27+
28+
if ($helper->need_more_time()) {
29+
// On travis when valgrind active it takes more time to complete all operations so we just increase initial limits
30+
$time_limit = 5.0;
31+
$low_range = $time_limit/2;
32+
$high_range = $time_limit*20;
33+
} else {
34+
$time_limit = 1.5;
35+
$low_range = 1.45;
36+
$high_range = 1.65;
37+
}
38+
39+
$helper->assert('Time limit accessor report no hit', false === $isolate->IsTimeLimitHit());
40+
$helper->assert('Get time limit default value is zero', 0.0 === $isolate->GetTimeLimit());
41+
$isolate->SetTimeLimit($time_limit);
42+
$helper->assert('Get time limit returns valid value', $time_limit === $isolate->GetTimeLimit());
43+
44+
$helper->dump($isolate);
45+
$helper->line();
46+
47+
// sleeping **before** running js should not affect js runtime timeout
48+
sleep($time_limit);
49+
50+
$t = microtime(true);
51+
try {
52+
$res = $script->Run($context);
53+
} catch(\V8\Exceptions\TimeLimitException $e) {
54+
$helper->exception_export($e);
55+
echo 'script execution terminated', PHP_EOL;
56+
} finally {
57+
$helper->line();
58+
$t = microtime(true) - $t;
59+
$helper->dump(round($t, 9));
60+
$helper->assert("Script execution time is within specified range ({$low_range}, {$high_range})", $t >= $low_range && $t < $high_range);
61+
}
62+
63+
$helper->assert('Get time limit returns valid value', $time_limit === $isolate->GetTimeLimit());
64+
$helper->assert('Time limit accessor report hit', true === $isolate->IsTimeLimitHit());
65+
66+
$helper->line();
67+
$helper->dump($isolate);
68+
69+
// EXPECTF: ---/float\(.+\)/
70+
// EXPECTF: +++float(%f)
71+
72+
// EXPECTF: ---/range \(.+, .+\)/
73+
// EXPECTF: +++range (%f, %f)
74+
?>
75+
--EXPECTF--
76+
Time limit accessor report no hit: ok
77+
Get time limit default value is zero: ok
78+
Get time limit returns valid value: ok
79+
object(V8\Isolate)#3 (0) {
80+
}
81+
82+
V8\Exceptions\TimeLimitException: Time limit exceeded
83+
script execution terminated
84+
85+
float(%f)
86+
Script execution time is within specified range (%f, %f): ok
87+
Get time limit returns valid value: ok
88+
Time limit accessor report hit: ok
89+
90+
object(V8\Isolate)#3 (0) {
91+
}

0 commit comments

Comments
 (0)