r110812 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r110811‎ | r110812 | r110813 >
Date:00:35, 7 February 2012
Author:aaron
Status:ok
Tags:filebackend 
Comment:
r109469: protect FileOpScopedPHPTimeout from abuse by infinite loops. Also commented out the invocation of it for now.
Modified paths:
  • /trunk/phase3/includes/filerepo/backend/FileOp.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/filerepo/backend/FileOp.php
@@ -118,7 +118,8 @@
119119 // Restart PHP's execution timer and set the timeout to safe amount.
120120 // This handles cases where the operations take a long time or where we are
121121 // already running low on time left. The old timeout is restored afterwards.
122 - $scopedTimeLimit = new FileOpScopedPHPTimeout( self::TIME_LIMIT_SEC );
 122+ # @TODO: re-enable this for when the number of batches is high.
 123+ #$scopedTimeLimit = new FileOpScopedPHPTimeout( self::TIME_LIMIT_SEC );
123124
124125 // Attempt each operation...
125126 foreach ( $performOps as $index => $fileOp ) {
@@ -359,28 +360,52 @@
360361 * the original time limit minus the time the object existed.
361362 */
362363 class FileOpScopedPHPTimeout {
363 - protected $startTime; // integer seconds
364 - protected $oldTimeout; // integer seconds
 364+ protected $startTime; // float; seconds
 365+ protected $oldTimeout; // integer; seconds
365366
 367+ protected static $stackDepth = 0; // integer
 368+ protected static $totalCalls = 0; // integer
 369+ protected static $totalElapsed = 0; // float; seconds
 370+
 371+ /* Prevent callers in infinite loops from running forever */
 372+ const MAX_TOTAL_CALLS = 1000000;
 373+ const MAX_TOTAL_TIME = 300; // seconds
 374+
366375 /**
367376 * @param $seconds integer
368377 */
369378 public function __construct( $seconds ) {
370379 if ( ini_get( 'max_execution_time' ) > 0 ) { // CLI uses 0
371 - $this->oldTimeout = ini_set( 'max_execution_time', $seconds );
 380+ if ( self::$totalCalls >= self::MAX_TOTAL_CALLS ) {
 381+ trigger_error( "Maximum invocations of " . __CLASS__ . " exceeded." );
 382+ } elseif ( self::$totalElapsed >= self::MAX_TOTAL_TIME ) {
 383+ trigger_error( "Time limit within invocations of " . __CLASS__ . " exceeded." );
 384+ } elseif ( self::$stackDepth > 0 ) { // recursion guard
 385+ trigger_error( "Resursive invocation of " . __CLASS__ . " attempted." );
 386+ } else {
 387+ $this->oldTimeout = ini_set( 'max_execution_time', $seconds );
 388+ $this->startTime = microtime( true );
 389+ ++self::$stackDepth;
 390+ ++self::$totalCalls; // proof against < 1us scopes
 391+ }
372392 }
373 - $this->startTime = time();
374393 }
375394
376 - /*
 395+ /**
377396 * Restore the original timeout.
378397 * This does not account for the timer value on __construct().
379398 */
380399 public function __destruct() {
381400 if ( $this->oldTimeout ) {
382 - $elapsed = time() - $this->startTime;
 401+ $elapsed = microtime( true ) - $this->startTime;
383402 // Note: a limit of 0 is treated as "forever"
384 - set_time_limit( max( 1, $this->oldTimeout - $elapsed ) );
 403+ set_time_limit( max( 1, $this->oldTimeout - (int)$elapsed ) );
 404+ // If each scoped timeout is for less than one second, we end up
 405+ // restoring the original timeout without any decrease in value.
 406+ // Thus web scripts in an infinite loop can run forever unless we
 407+ // take some measures to prevent this. Track total time and calls.
 408+ self::$totalElapsed += $elapsed;
 409+ --self::$stackDepth;
385410 }
386411 }
387412 }

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r109469In FileBackend/FileOp:...aaron02:07, 19 January 2012

Status & tagging log