4 * Class to expand PHP execution time for a function call.
5 * On construction, set_time_limit() is called and set to $seconds.
6 * When the object goes out of scope, the timer is restarted, with
7 * the original time limit minus the time the object existed.
9 class ScopedPHPTimeout
{
10 protected $startTime; // float; seconds
11 protected $oldTimeout; // integer; seconds
13 protected static $stackDepth = 0; // integer
14 protected static $totalCalls = 0; // integer
15 protected static $totalElapsed = 0; // float; seconds
17 /* Prevent callers in infinite loops from running forever */
18 const MAX_TOTAL_CALLS
= 1000000;
19 const MAX_TOTAL_TIME
= 300; // seconds
22 * @param $seconds integer
24 public function __construct( $seconds ) {
25 if ( ini_get( 'max_execution_time' ) > 0 ) { // CLI uses 0
26 if ( self
::$totalCalls >= self
::MAX_TOTAL_CALLS
) {
27 trigger_error( "Maximum invocations of " . __CLASS__
. " exceeded." );
28 } elseif ( self
::$totalElapsed >= self
::MAX_TOTAL_TIME
) {
29 trigger_error( "Time limit within invocations of " . __CLASS__
. " exceeded." );
30 } elseif ( self
::$stackDepth > 0 ) { // recursion guard
31 trigger_error( "Resursive invocation of " . __CLASS__
. " attempted." );
33 $this->oldTimeout
= ini_set( 'max_execution_time', $seconds );
34 $this->startTime
= microtime( true );
36 ++self
::$totalCalls; // proof against < 1us scopes
42 * Restore the original timeout.
43 * This does not account for the timer value on __construct().
45 public function __destruct() {
46 if ( $this->oldTimeout
) {
47 $elapsed = microtime( true ) - $this->startTime
;
48 // Note: a limit of 0 is treated as "forever"
49 set_time_limit( max( 1, $this->oldTimeout
- (int)$elapsed ) );
50 // If each scoped timeout is for less than one second, we end up
51 // restoring the original timeout without any decrease in value.
52 // Thus web scripts in an infinite loop can run forever unless we
53 // take some measures to prevent this. Track total time and calls.
54 self
::$totalElapsed +
= $elapsed;