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

Commit

Permalink
Allow to recover from limit his and make V8\Isolate time limit affect…
Browse files Browse the repository at this point in the history
…s js runtime only
  • Loading branch information
pinepain committed Apr 30, 2017
1 parent 04d9e6f commit 22e0f6b
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 20 deletions.
85 changes: 65 additions & 20 deletions src/php_v8_isolate_limits.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@
#define php_v8_debug_execution(format, ...)
#endif

static inline void php_v8_isolate_limits_update_time_point(php_v8_isolate_limits_t *limits) {
php_v8_debug_execution("Updating time limits\n");

std::chrono::milliseconds duration(static_cast<int64_t>(limits->time_limit * 1000));
std::chrono::time_point<std::chrono::high_resolution_clock> from = std::chrono::high_resolution_clock::now();

php_v8_debug_execution(" now: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(from).time_since_epoch().count()/1000.0);
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);

limits->time_point = from + duration;
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);
}

static inline void php_v8_isolate_limits_maybe_terminate_thread(php_v8_isolate_limits_t *limits) {
if (!limits->active && limits->thread) {
limits->thread->join();
delete limits->thread;
limits->thread = NULL;
}
}

static void php_v8_isolate_limits_interrupt_handler(v8::Isolate *isolate, void *data) {
php_v8_isolate_t *php_v8_isolate = static_cast<php_v8_isolate_t *>(data);
php_v8_isolate_limits_t *limits = &php_v8_isolate->limits;
Expand Down Expand Up @@ -81,12 +102,17 @@ void php_v8_isolate_limits_thread(php_v8_isolate_t *php_v8_isolate) {
limits->mutex->lock();

if (limits->active && limits->time_limit > 0) {

now = std::chrono::high_resolution_clock::now();

if (now > limits->time_point) {
php_v8_debug_execution("Time limit reached, terminating\n");
php_v8_debug_execution(" now: %.3f\n", std::chrono::time_point_cast<std::chrono::milliseconds>(now).time_since_epoch().count()/1000.0);
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);

limits->time_limit_hit = true;
limits->active = false;
php_v8_isolate->isolate->TerminateExecution();
limits->time_limit_hit = true;
}
}

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

if (!limits->active) {
php_v8_debug_execution("Exit timer loop: %s, %s\n", has(limits->mutex, "mutex"), has(limits->thread, "thread"));
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);
return;
}

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

php_v8_debug_execution("Maybe start timer: %d, %s, %s\n", limits->depth, has(limits->mutex, "mutex"), has(limits->thread, "thread"));

assert (limits->depth < UINT32_MAX);

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

if (limits->active && !limits->thread) {
php_v8_isolate_limits_update_time_point(limits);

php_v8_debug_execution(" start timer\n");
limits->thread = new std::thread(php_v8_isolate_limits_thread, php_v8_isolate);
}
}


void php_v8_isolate_limits_maybe_stop_timer(php_v8_isolate_t *php_v8_isolate) {
php_v8_isolate_limits_t *limits = &php_v8_isolate->limits;

Expand Down Expand Up @@ -172,6 +206,8 @@ void php_v8_isolate_limits_free(php_v8_isolate_t *php_v8_isolate) {
if (limits->mutex) {
delete limits->mutex;
}

limits->time_point.~time_point();
}

void php_v8_isolate_limits_ctor(php_v8_isolate_t *php_v8_isolate) {
Expand All @@ -180,13 +216,15 @@ void php_v8_isolate_limits_ctor(php_v8_isolate_t *php_v8_isolate) {
limits->thread = NULL;
limits->mutex = NULL;
limits->depth = 0;

new(&limits->time_point) std::chrono::time_point<std::chrono::high_resolution_clock>();
}

void php_v8_isolate_limits_set_time_limit(php_v8_isolate_t *php_v8_isolate, double time_limit_in_seconds) {
PHP_V8_DECLARE_ISOLATE(php_v8_isolate);
PHP_V8_DECLARE_LIMITS(php_v8_isolate);

assert(time_limit_in_seconds >=0);
assert(time_limit_in_seconds >= 0);

v8::Locker locker(isolate);

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

limits->mutex->lock();

std::chrono::milliseconds duration(static_cast<int64_t>(time_limit_in_seconds * 1000));
std::chrono::time_point<std::chrono::high_resolution_clock> from = std::chrono::high_resolution_clock::now();

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));
limits->time_limit = time_limit_in_seconds;
limits->time_point = from + duration;
limits->time_limit_hit = false;
php_v8_isolate_limits_update_time_point(limits);

if (limits->time_limit_hit) {
php_v8_debug_execution(" trying to recover from time limit hit, active: %s\n", is(limits->active));

isolate->CancelTerminateExecution();

php_v8_isolate_limits_maybe_terminate_thread(limits);
limits->time_limit_hit = false;
}

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

if (limits->active && limits->depth && !limits->thread) {
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"));
limits->thread = new std::thread(php_v8_isolate_limits_thread, php_v8_isolate);
}

limits->mutex->unlock();

if (!limits->active && limits->thread) {
limits->thread->join();
delete limits->thread;
limits->thread = NULL;
}
php_v8_isolate_limits_maybe_terminate_thread(limits);
}

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

limits->mutex->lock();

php_v8_debug_execution("Updating memory limits, memory_limit_hit: %s\n", is(limits->memory_limit_hit));
limits->memory_limit = memory_limit_in_bytes;
limits->memory_limit_hit = false;

if (limits->memory_limit_hit) {
php_v8_debug_execution(" trying to recover from memory limit hit, active: %s\n", is(limits->active));

isolate->CancelTerminateExecution();

php_v8_isolate_limits_maybe_terminate_thread(limits);
limits->memory_limit_hit = false;
}

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

if (limits->active && limits->depth && !limits->thread) {
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"));
limits->thread = new std::thread(php_v8_isolate_limits_thread, php_v8_isolate);
}

limits->mutex->unlock();

if (!limits->active && limits->thread) {
limits->thread->join();
delete limits->thread;
limits->thread = NULL;
}
php_v8_isolate_limits_maybe_terminate_thread(limits);
}
1 change: 1 addition & 0 deletions src/php_v8_isolate_limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extern void php_v8_isolate_limits_ctor(php_v8_isolate_t *php_v8_isolate);

extern void php_v8_isolate_limits_set_time_limit(php_v8_isolate_t *php_v8_isolate, double time_limit_in_seconds);
extern void php_v8_isolate_limits_set_memory_limit(php_v8_isolate_t *php_v8_isolate, size_t memory_limit_in_bytes);
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);

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

Expand Down
13 changes: 13 additions & 0 deletions tests/.testsuite.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,19 @@ public function need_more_time() {
// NOTE: this check is a bit fragile but should fits our need
return isset($_ENV['TRAVIS']) && isset($_ENV['TEST_PHP_ARGS']) && $_ENV['TEST_PHP_ARGS'] == '-m';
}

public function is_memory_test() {
// NOTE: this check is a bit fragile but should fits our need
if (!isset($_SERVER['ZEND_DONT_UNLOAD_MODULES']) || !$_SERVER['ZEND_DONT_UNLOAD_MODULES']) {
return false;
}

if (isset($_SERVER['USE_ZEND_ALLOC']) && $_SERVER['USE_ZEND_ALLOC']) {
return false;
}

return isset($_SERVER['LD_PRELOAD']) && false != strpos($_SERVER['LD_PRELOAD'], '/valgrind/');
}
}

interface FilterInterface
Expand Down
91 changes: 91 additions & 0 deletions tests/V8Isolate_limit_time_affects_js_runtime_only.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
--TEST--
V8\Isolate - time limit affects js runtime only
--SKIPIF--
<?php if (!extension_loaded("v8")) print "skip"; ?>
--FILE--
<?php

/** @var \Phpv8Testsuite $helper */
$helper = require '.testsuite.php';

require '.v8-helpers.php';
$v8_helper = new PhpV8Helpers($helper);

// Tests:

$isolate = new V8\Isolate();
$context = new V8\Context($isolate);


$source = '
var i = 0;
while(true) { i++};
';
$file_name = 'test.js';

$script = new V8\Script($context, new \V8\StringValue($isolate, $source), new \V8\ScriptOrigin($file_name));

if ($helper->need_more_time()) {
// On travis when valgrind active it takes more time to complete all operations so we just increase initial limits
$time_limit = 5.0;
$low_range = $time_limit/2;
$high_range = $time_limit*20;
} else {
$time_limit = 1.5;
$low_range = 1.45;
$high_range = 1.65;
}

$helper->assert('Time limit accessor report no hit', false === $isolate->IsTimeLimitHit());
$helper->assert('Get time limit default value is zero', 0.0 === $isolate->GetTimeLimit());
$isolate->SetTimeLimit($time_limit);
$helper->assert('Get time limit returns valid value', $time_limit === $isolate->GetTimeLimit());

$helper->dump($isolate);
$helper->line();

// sleeping **before** running js should not affect js runtime timeout
sleep($time_limit);

$t = microtime(true);
try {
$res = $script->Run($context);
} catch(\V8\Exceptions\TimeLimitException $e) {
$helper->exception_export($e);
echo 'script execution terminated', PHP_EOL;
} finally {
$helper->line();
$t = microtime(true) - $t;
$helper->dump(round($t, 9));
$helper->assert("Script execution time is within specified range ({$low_range}, {$high_range})", $t >= $low_range && $t < $high_range);
}

$helper->assert('Get time limit returns valid value', $time_limit === $isolate->GetTimeLimit());
$helper->assert('Time limit accessor report hit', true === $isolate->IsTimeLimitHit());

$helper->line();
$helper->dump($isolate);

// EXPECTF: ---/float\(.+\)/
// EXPECTF: +++float(%f)

// EXPECTF: ---/range \(.+, .+\)/
// EXPECTF: +++range (%f, %f)
?>
--EXPECTF--
Time limit accessor report no hit: ok
Get time limit default value is zero: ok
Get time limit returns valid value: ok
object(V8\Isolate)#3 (0) {
}

V8\Exceptions\TimeLimitException: Time limit exceeded
script execution terminated

float(%f)
Script execution time is within specified range (%f, %f): ok
Get time limit returns valid value: ok
Time limit accessor report hit: ok

object(V8\Isolate)#3 (0) {
}
Loading

0 comments on commit 22e0f6b

Please sign in to comment.