Tim Starling has submitted this change and it was merged.

Change subject: (bug 45684) Add support for pausing timers
......................................................................


(bug 45684) Add support for pausing timers

Sometimes a PHP callback will need to do things that shouldn't be
counted against the normal Lua usage timer. Support this by adding
methods pauseUsageTimer() and unpauseUsageTimer() to the LuaSandbox
class.

Note this doesn't actually close bug 45684, but it is necessary for the
change to the Scribunto extension that will close it.

== Timers ==

Pausing affects the "normal" CPU limit and the usage reported by
getCPUUsage() in PHP and os.clock() in Lua. The "emergency" CPU limit is
*not* affected.

To avoid issues with precision when timers are repeatedly stopped and
started, the timer for the "normal" limit is not actually stopped when
the timer is paused. Instead, the pause timestamp is recorded on pause,
and the delta is accumulated on unpause. The accumulated delta is folded
into the usage and the normal limit time remaining when the timers
actually are stopped, when the limit is reset, or when the normal timer
expires.

Since the normal limit timer is not actually stopped, there are three
possibilities when the timer fires:
1. Timers are paused: A flag is set, so the normal limit timer can be
   restarted with the accumulated delta on unpause.
2. There is an accumulated delta: The timer is restarted with that
   delta.
3. Otherwise, the normal "timeout error" hook is hooked.

== PHP interface ==

Inside a PHP callback from Lua, pauseUsageTimer() may be called to pause
the timers as described above, and unpauseUsageTimer() may be called to
unpause them. In addition, the timers will be automatically unpaused
when the PHP callback returns. If the PHP callback having timers paused
itself calls back into Lua, the timers will be unpaused for the duration
of the Lua call and automatically paused again when the Lua call
returns, before control returns to PHP.

pauseUsageTimer() will be disabled whenever a PHP callback not having
timers paused itself calls back into Lua. This prevents offloading of
expensive processing via something like this:

 -- This is cheap, because the underlying PHP call for __index uses
 -- pauseUsageTimer()
 function cheap( frame )
     return frame.args[1]
 end

 -- This would be expensive, because the underlying PHP call does not
 -- use pauseUsageTimer()
 function expensive( frame )
     frame:preprocess( '{{expensive}}' )
 end

 -- Here, the cost of processing '{{expensive}}' is offloaded into
 -- cheap's access of frame.args[1] where it wouldn't count against the
 -- CPU limit.
 -- Disabling pauseUsageTimer() when preprocess calls cheap closes the
 -- hole.
 function cheat( frame )
     frame:preprocess( '{{#invoke:Cheater|cheap| {{expensive}} }}' )
 end

Bug: 45684
Change-Id: I11881232527c341e4329f041247d5b2bbbdee8c7
---
M luasandbox.c
M luasandbox_timer.h
M luasandbox_types.h
M php_luasandbox.h
A tests/timer.phpt
M timer.c
6 files changed, 378 insertions(+), 12 deletions(-)

Approvals:
  Tim Starling: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/luasandbox.c b/luasandbox.c
index 4754154..3d8aba2 100644
--- a/luasandbox.c
+++ b/luasandbox.c
@@ -105,6 +105,12 @@
 ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_getCPUUsage, 0)
 ZEND_END_ARG_INFO()
 
+ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_pauseUsageTimer, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_unpauseUsageTimer, 0)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_enableProfiler, 0)
        ZEND_ARG_INFO(0, period)
 ZEND_END_ARG_INFO()
@@ -156,6 +162,8 @@
        PHP_ME(LuaSandbox, getPeakMemoryUsage, 
arginfo_luasandbox_getPeakMemoryUsage, 0)
        PHP_ME(LuaSandbox, setCPULimit, arginfo_luasandbox_setCPULimit, 0)
        PHP_ME(LuaSandbox, getCPUUsage, arginfo_luasandbox_getCPUUsage, 0)
+       PHP_ME(LuaSandbox, pauseUsageTimer, arginfo_luasandbox_pauseUsageTimer, 
0)
+       PHP_ME(LuaSandbox, unpauseUsageTimer, 
arginfo_luasandbox_unpauseUsageTimer, 0)
        PHP_ME(LuaSandbox, enableProfiler, arginfo_luasandbox_enableProfiler, 0)
        PHP_ME(LuaSandbox, disableProfiler, arginfo_luasandbox_disableProfiler, 
0)
        PHP_ME(LuaSandbox, getProfilerFunctionReport, 
arginfo_luasandbox_getProfilerFunctionReport, 0)
@@ -338,6 +346,7 @@
        memset(sandbox, 0, sizeof(php_luasandbox_obj));
        zend_object_std_init(&sandbox->std, ce TSRMLS_CC);
        sandbox->alloc.memory_limit = (size_t)-1;
+       sandbox->allow_pause = 1;
 
        // Initialise the Lua state
        sandbox->state = luasandbox_newstate(sandbox TSRMLS_CC);
@@ -527,7 +536,8 @@
        lua_State * L;
        int have_mark;
        php_luasandbox_obj * sandbox;
-       
+       int was_paused;
+
        sandbox = (php_luasandbox_obj*) 
                zend_object_store_get_object(this_ptr TSRMLS_CC);
        L = sandbox->state;
@@ -564,8 +574,19 @@
                RETURN_FALSE;
        }
 
+       // Make sure this is counted against the Lua usage time limit
+       was_paused = luasandbox_timer_is_paused(&sandbox->timer);
+       luasandbox_timer_unpause(&sandbox->timer);
+
        // Parse the string into a function on the stack
        status = luaL_loadbuffer(L, code, codeLength, chunkName);
+
+       // If the timers were paused before, re-pause them now
+       if (was_paused) {
+               luasandbox_timer_pause(&sandbox->timer);
+       }
+
+       // Handle any error from luaL_loadbuffer
        if (status != 0) {
                luasandbox_handle_error(sandbox, status TSRMLS_CC);
                return;
@@ -835,6 +856,60 @@
 
        luasandbox_timer_get_usage(&sandbox->timer, &ts);
        RETURN_DOUBLE(ts.tv_sec + 1e-9 * ts.tv_nsec);
+}
+/* }}} */
+
+/** {{{ proto bool LuaSandbox::pauseUsageTimer()
+ *
+ * Pause the CPU usage timer, and the "normal" time limit set by 
LuaSandbox::setCPULimit.
+ * Does not pause the "emergency" time limit set by LuaSandbox::setCPULimit.
+ *
+ * This only has effect when called from within a callback from Lua. When
+ * execution returns to Lua, the timers will be automatically unpaused. If a
+ * new call into Lua is made, the timers will be unpaused for the duration of
+ * that call.
+ *
+ * If a PHP callback calls into Lua again with timers not paused, and then that
+ * Lua function calls into PHP again, the second PHP call will not be able to
+ * pause the timers. The logic is that even though the second PHP call would
+ * avoid counting the CPU usage against the limit, the first call still counts
+ * it.
+ *
+ * Returns true if the timers are now paused, false if not.
+ */
+PHP_METHOD(LuaSandbox, pauseUsageTimer)
+{
+       php_luasandbox_obj * sandbox = (php_luasandbox_obj*)
+               zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       if ( !sandbox->allow_pause || !sandbox->in_lua ) {
+               RETURN_FALSE;
+       }
+
+       luasandbox_timer_pause(&sandbox->timer);
+       RETURN_TRUE;
+}
+/* }}} */
+
+/** {{{ proto void LuaSandbox::unpauseUsageTimer()
+ *
+ * Unpause timers paused by LuaSandbox::pauseUsageTimer.
+ */
+PHP_METHOD(LuaSandbox, unpauseUsageTimer)
+{
+       php_luasandbox_obj * sandbox = (php_luasandbox_obj*)
+               zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       luasandbox_timer_unpause(&sandbox->timer);
+       RETURN_NULL();
 }
 /* }}} */
 
@@ -1191,6 +1266,8 @@
        int timer_started = 0;
        int i, numResults;
        zval * old_zval;
+       int was_paused;
+       int old_allow_pause;
 
        // Check to see if the value is a valid function
        if (lua_type(L, -1) != LUA_TFUNCTION) {
@@ -1237,11 +1314,24 @@
        old_zval = sandbox->current_zval;
        sandbox->current_zval = sandbox_zval;
 
+       // Make sure this is counted against the Lua usage time limit, and set 
the
+       // allow_pause flag.
+       was_paused = luasandbox_timer_is_paused(&sandbox->timer);
+       luasandbox_timer_unpause(&sandbox->timer);
+       old_allow_pause = sandbox->allow_pause;
+       sandbox->allow_pause = ( !sandbox->in_lua || was_paused );
+
        // Call the function
        sandbox->in_lua++;
        status = lua_pcall(L, numArgs, LUA_MULTRET, origTop + 1);
        sandbox->in_lua--;
        sandbox->current_zval = old_zval;
+
+       // Restore pause state
+       sandbox->allow_pause = old_allow_pause;
+       if (was_paused) {
+               luasandbox_timer_pause(&sandbox->timer);
+       }
 
        // Stop the timer
        if (timer_started) {
@@ -1453,6 +1543,7 @@
        zval **pointers;
        zval ***double_pointers;
        int num_results = 0;
+       int status;
        Bucket *bucket; 
        TSRMLS_FETCH();
 
@@ -1487,9 +1578,16 @@
        // zend_fcall_info_argp()
        zend_fcall_info_args_restore(&fci, top, double_pointers);
 
+       // Sanity check, timers should never be paused at this point
+       assert(!luasandbox_timer_is_paused(&intern->timer));
+
        // Call the function
-       if (zend_call_function(&fci, &fcc TSRMLS_CC) == SUCCESS 
-               && fci.retval_ptr_ptr && *fci.retval_ptr_ptr) 
+       status = zend_call_function(&fci, &fcc TSRMLS_CC);
+
+       // Automatically unpause now that PHP has returned
+       luasandbox_timer_unpause(&intern->timer);
+
+       if (status == SUCCESS && fci.retval_ptr_ptr && *fci.retval_ptr_ptr)
        {
                // Push the return values back to Lua
                if (Z_TYPE_PP(fci.retval_ptr_ptr) == IS_NULL) {
diff --git a/luasandbox_timer.h b/luasandbox_timer.h
index e666363..04b30cb 100644
--- a/luasandbox_timer.h
+++ b/luasandbox_timer.h
@@ -35,6 +35,9 @@
 void luasandbox_timer_stop(luasandbox_timer_set * lts);
 void luasandbox_timer_destroy(luasandbox_timer_set * lts);
 void luasandbox_timer_get_usage(luasandbox_timer_set * lts, struct timespec * 
ts);
+void luasandbox_timer_pause(luasandbox_timer_set * lts);
+void luasandbox_timer_unpause(luasandbox_timer_set * lts);
+int luasandbox_timer_is_paused(luasandbox_timer_set * lts);
 void luasandbox_timer_timeout_error(lua_State *L);
 int luasandbox_timer_is_expired(luasandbox_timer_set * lts);
 
diff --git a/luasandbox_types.h b/luasandbox_types.h
index 2c42c86..5806142 100644
--- a/luasandbox_types.h
+++ b/luasandbox_types.h
@@ -28,6 +28,8 @@
        struct timespec normal_limit, normal_remaining;
        struct timespec emergency_limit, emergency_remaining;
        struct timespec usage_start, usage;
+       struct timespec pause_start, pause_delta;
+       struct timespec normal_expired_at;
        struct timespec profiler_period;
        struct _php_luasandbox_obj * sandbox;
        int is_running;
@@ -58,6 +60,7 @@
        struct timespec profiler_period;
        HashTable * function_counts;
        long total_count;
+       int is_paused;
 } luasandbox_timer_set;
 
 #endif /*CLOCK_REALTIME*/
@@ -96,6 +99,7 @@
        luasandbox_timer_set timer;
        int function_index;
        unsigned int random_seed;
+       int allow_pause;
 };
 typedef struct _php_luasandbox_obj php_luasandbox_obj;
 
diff --git a/php_luasandbox.h b/php_luasandbox.h
index d155a09..0af7c25 100644
--- a/php_luasandbox.h
+++ b/php_luasandbox.h
@@ -46,6 +46,8 @@
 PHP_METHOD(LuaSandbox, getPeakMemoryUsage);
 PHP_METHOD(LuaSandbox, setCPULimit);
 PHP_METHOD(LuaSandbox, getCPUUsage);
+PHP_METHOD(LuaSandbox, pauseUsageTimer);
+PHP_METHOD(LuaSandbox, unpauseUsageTimer);
 PHP_METHOD(LuaSandbox, enableProfiler);
 PHP_METHOD(LuaSandbox, disableProfiler);
 PHP_METHOD(LuaSandbox, getProfilerFunctionReport);
diff --git a/tests/timer.phpt b/tests/timer.phpt
new file mode 100644
index 0000000..d61aaa6
--- /dev/null
+++ b/tests/timer.phpt
@@ -0,0 +1,153 @@
+--TEST--
+timer pausing and unpausing
+--FILE--
+<?php
+
+// Note these tests have to waste CPU cycles rather than sleep(), because the
+// timer counts CPU time used and sleep() doesn't use CPU time.
+
+$lua = <<<LUA
+       lua = {}
+
+       function lua.expensive()
+               local t = os.clock() + 0.15
+               while os.clock() < t do end
+       end
+
+       function test_auto_unpause()
+               php.paused();
+               lua.expensive();
+       end
+
+       function test_pause_overrun()
+               php.overrun();
+               local t = os.clock() + 0.15
+               while os.clock() < t do end
+       end
+
+       function lua.call( func, ... )
+               func( ... )
+       end
+LUA;
+
+function expensive() {
+       $t = microtime( 1 ) + 0.15;
+       while ( microtime( 1 ) < $t ) {
+       }
+}
+
+function paused() {
+       global $sandbox;
+       $sandbox->pauseUsageTimer();
+       $t = microtime( 1 ) + 0.15;
+       while ( microtime( 1 ) < $t ) {
+       }
+}
+
+function unpaused() {
+       global $sandbox;
+       $sandbox->pauseUsageTimer();
+       $sandbox->unpauseUsageTimer();
+       $t = microtime( 1 ) + 0.15;
+       while ( microtime( 1 ) < $t ) {
+       }
+}
+
+function overrun() {
+       global $sandbox;
+       $sandbox->pauseUsageTimer();
+       $t = microtime( 1 ) + 0.45;
+       while ( microtime( 1 ) < $t ) {
+       }
+}
+
+function resetLimit() {
+       global $sandbox;
+       $sandbox->pauseUsageTimer();
+       $sandbox->setCPULimit( 0.1 );
+       $t = microtime( 1 ) + 0.15;
+       while ( microtime( 1 ) < $t ) {
+       }
+}
+
+function call() {
+       global $sandbox;
+       $args = func_get_args();
+       call_user_func_array( array( $sandbox, 'callFunction' ), $args );
+}
+
+function pauseCall() {
+       global $sandbox;
+       $sandbox->pauseUsageTimer();
+       $args = func_get_args();
+       call_user_func_array( array( $sandbox, 'callFunction' ), $args );
+}
+
+function doTest( $name ) {
+       global $sandbox, $lua;
+
+       $args = func_get_args();
+       array_shift( $args );
+
+       printf( "%-47s ", "$name:" );
+
+       $sandbox = new LuaSandbox;
+       $sandbox->registerLibrary( 'php', array(
+               'expensive' => 'expensive',
+               'paused' => 'paused',
+               'unpaused' => 'unpaused',
+               'overrun' => 'overrun',
+               'resetLimit' => 'resetLimit',
+               'call' => 'call',
+               'pauseCall' => 'pauseCall',
+       ) );
+       $sandbox->loadString( $lua )->call();
+       $sandbox->setMemoryLimit( 100000 );
+       $sandbox->setCPULimit( 0.1 );
+
+       try {
+               $timeout='no';
+               $t0 = microtime( 1 );
+               $u0 = $sandbox->getCPUUsage();
+               call_user_func_array( array( $sandbox, 'callFunction' ), $args 
);
+       } catch ( LuaSandboxTimeoutError $err ) {
+               $timeout='yes';
+       }
+       $t1 = microtime( 1 ) - $t0;
+       $u1 = $sandbox->getCPUUsage() - $u0;
+       printf( "%3s (%.2fs of %.2fs)\n", $timeout, $u1, $t1 );
+}
+
+doTest( 'Lua usage counted', 'lua.expensive' );
+doTest( 'PHP usage counted', 'php.expensive' );
+doTest( 'Paused PHP usage counted', 'php.paused' );
+doTest( 'Unpause works', 'php.unpaused' );
+doTest( 'Auto-unpause works', 'test_auto_unpause' );
+doTest( 'Reset limit unpauses', 'php.resetLimit' );
+doTest( 'Pause overrun prevented', 'test_pause_overrun' );
+
+doTest( 'PHP to Lua counted', 'php.call', 'lua.expensive' );
+doTest( 'PHP to paused-PHP counted', 'php.call', 'php.paused' );
+doTest( 'PHP to paused-PHP to paused-PHP counted', 'php.call', 
'php.pauseCall', 'php.paused' );
+doTest( 'paused-PHP to Lua counted', 'php.pauseCall', 'lua.expensive' );
+doTest( 'paused-PHP to PHP counted', 'php.pauseCall', 'php.expensive' );
+doTest( 'paused-PHP to paused-PHP counted', 'php.pauseCall', 'php.paused' );
+doTest( 'paused-PHP to paused-PHP to paused-PHP counted', 'php.pauseCall', 
'php.pauseCall', 'php.paused' );
+doTest( 'paused-PHP to PHP to paused-PHP counted', 'php.pauseCall', 
'php.call', 'php.paused' );
+
+--EXPECTF--
+Lua usage counted:                              yes (0.10s of %fs)
+PHP usage counted:                              yes (0.15s of %fs)
+Paused PHP usage counted:                        no (0.00s of %fs)
+Unpause works:                                  yes (0.15s of %fs)
+Auto-unpause works:                             yes (0.10s of %fs)
+Reset limit unpauses:                            no (0.00s of %fs)
+Pause overrun prevented:                        yes (0.10s of %fs)
+PHP to Lua counted:                             yes (0.10s of %fs)
+PHP to paused-PHP counted:                      yes (0.15s of %fs)
+PHP to paused-PHP to paused-PHP counted:        yes (0.15s of %fs)
+paused-PHP to Lua counted:                      yes (0.10s of %fs)
+paused-PHP to PHP counted:                      yes (0.15s of %fs)
+paused-PHP to paused-PHP counted:                no (0.00s of %fs)
+paused-PHP to paused-PHP to paused-PHP counted:  no (0.00s of %fs)
+paused-PHP to PHP to paused-PHP counted:        yes (0.15s of %fs)
diff --git a/timer.c b/timer.c
index 255e75c..8cb3738 100644
--- a/timer.c
+++ b/timer.c
@@ -20,7 +20,9 @@
 void luasandbox_timer_install_handler(struct sigaction * oldact) {}
 void luasandbox_timer_remove_handler(struct sigaction * oldact) {}
 void luasandbox_timer_create(luasandbox_timer_set * lts,
-               php_luasandbox_obj * sandbox) {}
+               php_luasandbox_obj * sandbox) {
+       lts->is_paused = 0;
+}
 void luasandbox_timer_set_limits(luasandbox_timer_set * lts,
                struct timespec * normal_timeout, 
                struct timespec * emergency_timeout) {}
@@ -34,6 +36,17 @@
 void luasandbox_timer_get_usage(luasandbox_timer_set * lts, struct timespec * 
ts) {
        ts->tv_sec = ts->tv_nsec = 0;
 }
+
+void luasandbox_timer_pause(luasandbox_timer_set * lts) {
+       lts->is_paused = 1;
+}
+void luasandbox_timer_unpause(luasandbox_timer_set * lts) {
+       lts->is_paused = 0;
+}
+int luasandbox_timer_is_paused(luasandbox_timer_set * lts) {
+       return lts->is_paused;
+}
+
 void luasandbox_timer_timeout_error(lua_State *L) {}
 int luasandbox_timer_is_expired(luasandbox_timer_set * lts) {
        return 0;
@@ -122,12 +135,12 @@
        data = (luasandbox_timer_callback_data*)info->si_value.sival_ptr;
 
        lua_State * L = data->sandbox->state;
-       data->sandbox->timed_out = 1;
 
        if (data->type == LUASANDBOX_TIMER_EMERGENCY) {
                sigset_t set;
                sigemptyset(&set);
                sigprocmask(SIG_SETMASK, &set, NULL);
+               data->sandbox->timed_out = 1;
                data->sandbox->emergency_timed_out = 1;
                if (data->sandbox->in_php) {
                        // The whole PHP request context is dirty now. We need 
to kill it,
@@ -141,9 +154,22 @@
                        lua_error(L);
                }
        } else {
-               // Set a hook which will terminate the script execution in a 
graceful way
-               lua_sethook(L, luasandbox_timer_timeout_hook,
-                       LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | 
LUA_MASKLINE, 1);
+               luasandbox_timer_set * lts = &data->sandbox->timer;
+               if (luasandbox_timer_is_paused(lts)) {
+                       // The timer is paused. luasandbox_timer_unpause will 
reschedule when unpaused.
+                       clock_gettime(LUASANDBOX_CLOCK_ID, 
&lts->normal_expired_at);
+               } else if (!luasandbox_timer_is_zero(&lts->pause_delta)) {
+                       // The timer is not paused, but we have a pause delta. 
Reschedule.
+                       luasandbox_timer_subtract(&lts->usage, 
&lts->pause_delta);
+                       lts->normal_remaining = lts->pause_delta;
+                       luasandbox_timer_zero(&lts->pause_delta);
+                       luasandbox_timer_set_one_time(&lts->normal_timer, 
&lts->normal_remaining);
+               } else {
+                       // Set a hook which will terminate the script execution 
in a graceful way
+                       data->sandbox->timed_out = 1;
+                       lua_sethook(L, luasandbox_timer_timeout_hook,
+                               LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | 
LUA_MASKLINE, 1);
+               }
        }
 }
 
@@ -356,6 +382,9 @@
        luasandbox_timer_zero(&lts->normal_remaining);
        luasandbox_timer_zero(&lts->emergency_limit);
        luasandbox_timer_zero(&lts->emergency_remaining);
+       luasandbox_timer_zero(&lts->pause_start);
+       luasandbox_timer_zero(&lts->pause_delta);
+       luasandbox_timer_zero(&lts->normal_expired_at);
        luasandbox_timer_zero(&lts->profiler_period);
        lts->is_running = 0;
        lts->normal_running = 0;
@@ -369,14 +398,20 @@
                struct timespec * emergency_timeout)
 {
        int was_running = 0;
+       int was_paused = luasandbox_timer_is_paused(lts);
        if (lts->is_running) {
                was_running = 1;
                luasandbox_timer_stop(lts);
        }
        lts->normal_remaining = lts->normal_limit = *normal_timeout;
        lts->emergency_remaining = lts->emergency_limit = *emergency_timeout;
+       luasandbox_timer_zero(&lts->normal_expired_at);
+
        if (was_running) {
                luasandbox_timer_start(lts);
+       }
+       if (was_paused) {
+               luasandbox_timer_pause(lts);
        }
 }
 
@@ -438,6 +473,10 @@
        struct itimerspec its;
        luasandbox_timer_zero(&its.it_interval);
        its.it_value = *ts;
+       if (luasandbox_timer_is_zero(&its.it_value)) {
+               // Sanity check: make sure there is at least 1 nanosecond on 
the timer.
+               its.it_value.tv_nsec = 1;
+       }
        timer_settime(lt->timer, 0, &its, NULL);
 }
 
@@ -451,13 +490,18 @@
 
 void luasandbox_timer_stop(luasandbox_timer_set * lts)
 {
-       struct timespec usage;
+       struct timespec usage, delta;
 
        if (lts->is_running) {
                lts->is_running = 0;
        } else {
                return;
        }
+
+       // Make sure timers aren't paused, and extract the delta
+       luasandbox_timer_unpause(lts);
+       delta = lts->pause_delta;
+       luasandbox_timer_zero(&lts->pause_delta);
 
        // Stop the interval timers and save the time remaining
        if (lts->emergency_running) {
@@ -467,6 +511,7 @@
        if (lts->normal_running) {
                luasandbox_timer_stop_one(&lts->normal_timer, 
&lts->normal_remaining);
                lts->normal_running = 0;
+               luasandbox_timer_add(&lts->normal_remaining, &delta);
        }
 
        // Update the usage
@@ -474,6 +519,7 @@
        clock_gettime(LUASANDBOX_CLOCK_ID, &usage);
        luasandbox_timer_subtract(&usage, &lts->usage_start);
        luasandbox_timer_add(&lts->usage, &usage);
+       luasandbox_timer_subtract(&lts->usage, &delta);
 }
 
 static void luasandbox_timer_stop_one(luasandbox_timer * lt, struct timespec * 
remaining)
@@ -482,10 +528,13 @@
        struct itimerspec its;
        timer_gettime(lt->timer, &its);
        if (remaining) {
-               remaining->tv_sec = its.it_value.tv_sec;
-               remaining->tv_nsec = its.it_value.tv_nsec;
+               *remaining = its.it_value;
        }
-       luasandbox_timer_set_one_time(lt, &zero);
+
+       its.it_value = zero;
+       its.it_interval = zero;
+       timer_settime(lt->timer, 0, &its, NULL);
+
        if (lt->cbdata.type == LUASANDBOX_TIMER_PROFILER) {
                // Invalidate the cbdata, delete the timer
                lt->cbdata.sandbox = NULL;
@@ -517,10 +566,67 @@
 
 void luasandbox_timer_get_usage(luasandbox_timer_set * lts, struct timespec * 
ts)
 {
+       struct timespec delta;
+
        if (lts->is_running) {
                luasandbox_update_usage(lts);
        }
        *ts = lts->usage;
+       // Subtract the pause delta from the usage
+       luasandbox_timer_subtract(ts, &lts->pause_delta);
+       // If currently paused, subtract the time-since-pause too
+       if (!luasandbox_timer_is_zero(&lts->pause_start)) {
+               clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
+               luasandbox_timer_subtract(&delta, &lts->pause_start);
+               luasandbox_timer_subtract(ts, &delta);
+       }
+}
+
+void luasandbox_timer_pause(luasandbox_timer_set * lts) {
+       if (luasandbox_timer_is_zero(&lts->pause_start)) {
+               clock_gettime(LUASANDBOX_CLOCK_ID, &lts->pause_start);
+       }
+}
+
+void luasandbox_timer_unpause(luasandbox_timer_set * lts) {
+       struct timespec delta;
+
+       if (!luasandbox_timer_is_zero(&lts->pause_start)) {
+               clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
+               luasandbox_timer_subtract(&delta, &lts->pause_start);
+
+               if (luasandbox_timer_is_zero(&lts->normal_expired_at)) {
+                       // Easy case, timer didn't expire while paused. Throw 
the whole delta
+                       // into pause_delta for later timer and usage 
adjustment.
+                       luasandbox_timer_add(&lts->pause_delta, &delta);
+                       luasandbox_timer_zero(&lts->pause_start);
+               } else {
+                       // If the normal limit expired, we need to fold the 
whole
+                       // accumulated delta into usage immediately, and then 
restart the
+                       // timer with the portion before the expiry.
+
+                       // adjust usage
+                       luasandbox_timer_subtract(&lts->usage, &delta);
+                       luasandbox_timer_subtract(&lts->usage, 
&lts->pause_delta);
+
+                       // calculate timer delta
+                       delta = lts->normal_expired_at;
+                       luasandbox_timer_subtract(&delta, &lts->pause_start);
+                       luasandbox_timer_add(&delta, &lts->pause_delta);
+
+                       // Zero out pause vars
+                       luasandbox_timer_zero(&lts->pause_start);
+                       luasandbox_timer_zero(&lts->pause_delta);
+
+                       // Restart timer
+                       lts->normal_remaining = delta;
+                       luasandbox_timer_set_one_time(&lts->normal_timer, 
&lts->normal_remaining);
+               }
+       }
+}
+
+int luasandbox_timer_is_paused(luasandbox_timer_set * lts) {
+       return !luasandbox_timer_is_zero(&lts->pause_start);
 }
 
 int luasandbox_timer_is_expired(luasandbox_timer_set * lts)

-- 
To view, visit https://gerrit.wikimedia.org/r/52568
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I11881232527c341e4329f041247d5b2bbbdee8c7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/php/luasandbox
Gerrit-Branch: master
Gerrit-Owner: Anomie <bjor...@wikimedia.org>
Gerrit-Reviewer: Aaron Schulz <asch...@wikimedia.org>
Gerrit-Reviewer: Demon <ch...@wikimedia.org>
Gerrit-Reviewer: Tim Starling <tstarl...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to