MediaWiki  REL1_20
ScopedPHPTimeout.php
Go to the documentation of this file.
00001 <?php
00032 class ScopedPHPTimeout {
00033         protected $startTime; // float; seconds
00034         protected $oldTimeout; // integer; seconds
00035         protected $oldIgnoreAbort; // boolean
00036 
00037         protected static $stackDepth = 0; // integer
00038         protected static $totalCalls = 0; // integer
00039         protected static $totalElapsed = 0; // float; seconds
00040 
00041         /* Prevent callers in infinite loops from running forever */
00042         const MAX_TOTAL_CALLS = 1000000;
00043         const MAX_TOTAL_TIME = 300; // seconds
00044 
00048         public function __construct( $seconds ) {
00049                 if ( ini_get( 'max_execution_time' ) > 0 ) { // CLI uses 0
00050                         if ( self::$totalCalls >= self::MAX_TOTAL_CALLS ) {
00051                                 trigger_error( "Maximum invocations of " . __CLASS__ . " exceeded." );
00052                         } elseif ( self::$totalElapsed >= self::MAX_TOTAL_TIME ) {
00053                                 trigger_error( "Time limit within invocations of " . __CLASS__ . " exceeded." );
00054                         } elseif ( self::$stackDepth > 0 ) { // recursion guard
00055                                 trigger_error( "Resursive invocation of " . __CLASS__ . " attempted." );
00056                         } else {
00057                                 $this->oldIgnoreAbort = ignore_user_abort( true );
00058                                 $this->oldTimeout = ini_set( 'max_execution_time', $seconds );
00059                                 $this->startTime = microtime( true );
00060                                 ++self::$stackDepth;
00061                                 ++self::$totalCalls; // proof against < 1us scopes
00062                         }
00063                 }
00064         }
00065 
00070         public function __destruct() {
00071                 if ( $this->oldTimeout ) {
00072                         $elapsed = microtime( true ) - $this->startTime;
00073                         // Note: a limit of 0 is treated as "forever"
00074                         set_time_limit( max( 1, $this->oldTimeout - (int)$elapsed ) );
00075                         // If each scoped timeout is for less than one second, we end up
00076                         // restoring the original timeout without any decrease in value.
00077                         // Thus web scripts in an infinite loop can run forever unless we
00078                         // take some measures to prevent this. Track total time and calls.
00079                         self::$totalElapsed += $elapsed;
00080                         --self::$stackDepth;
00081                         ignore_user_abort( $this->oldIgnoreAbort );
00082                 }
00083         }
00084 }