From 090d0267daa4721ffb154e7e604804201365f9dd Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Sat, 1 Oct 2016 00:07:19 -0700 Subject: [PATCH] Use wikimedia/wait-condition-loop Since the WaitConditionLoop class was first introduced in 1.28 (current master), no back-compat alias is added. Bug: T146256 Depends-On: Ia84774d83da79fea1e167fe065c69549981f753b Change-Id: Ibd4f15c87105b8caccbd1f661b74b6efa012b77f --- RELEASE-NOTES-1.28 | 1 + autoload.php | 1 - composer.json | 1 + includes/libs/WaitConditionLoop.php | 186 ------------------ includes/libs/lockmanager/LockManager.php | 1 + includes/libs/lockmanager/MemcLockManager.php | 1 + includes/libs/objectcache/BagOStuff.php | 1 + includes/libs/rdbms/ChronologyProtector.php | 1 + .../libs/rdbms/database/DatabasePostgres.php | 1 + .../includes/libs/WaitConditionLoopTest.php | 179 ----------------- 10 files changed, 7 insertions(+), 366 deletions(-) delete mode 100644 includes/libs/WaitConditionLoop.php delete mode 100644 tests/phpunit/includes/libs/WaitConditionLoopTest.php diff --git a/RELEASE-NOTES-1.28 b/RELEASE-NOTES-1.28 index 4f687a1199..4051987e87 100644 --- a/RELEASE-NOTES-1.28 +++ b/RELEASE-NOTES-1.28 @@ -68,6 +68,7 @@ production. ==== New external libraries ==== * Added wikimedia/scoped-callback v1.0.0 +* Added wikimedia/wait-condition-loop v1.0.1 ==== Removed and replaced external libraries ==== diff --git a/autoload.php b/autoload.php index 323a01d34c..e614444aeb 100644 --- a/autoload.php +++ b/autoload.php @@ -1516,7 +1516,6 @@ $wgAutoloadLocalClasses = [ 'VirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTService.php', 'VirtualRESTServiceClient' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTServiceClient.php', 'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/WANObjectCache.php', - 'WaitConditionLoop' => __DIR__ . '/includes/libs/WaitConditionLoop.php', 'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php', 'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php', 'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php', diff --git a/composer.json b/composer.json index c827c0f100..4d71c66aa8 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "wikimedia/running-stat": "1.1.0", "wikimedia/scoped-callback": "1.0.0", "wikimedia/utfnormal": "1.0.3", + "wikimedia/wait-condition-loop": "1.0.1", "wikimedia/wrappedstring": "2.2.0", "zordius/lightncandy": "0.23" }, diff --git a/includes/libs/WaitConditionLoop.php b/includes/libs/WaitConditionLoop.php deleted file mode 100644 index 969e86e89c..0000000000 --- a/includes/libs/WaitConditionLoop.php +++ /dev/null @@ -1,186 +0,0 @@ -condition = $condition; - $this->timeout = $timeout; - $this->busyCallbacks =& $busyCallbacks; - - if ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) { - $this->rusageMode = 2; // RUSAGE_THREAD - } elseif ( function_exists( 'getrusage' ) ) { - $this->rusageMode = 0; // RUSAGE_SELF - } - } - - /** - * Invoke the loop and continue until either: - * - a) The condition callback returns neither CONDITION_CONTINUE nor false - * - b) The timeout is reached - * This a condition callback can return true (stop) or false (continue) for convenience. - * In such cases, the halting result of "true" will be converted to CONDITION_REACHED. - * - * If $timeout is 0, then only the condition callback will be called (no busy callbacks), - * and this will immediately return CONDITION_FAILED if the condition was not met. - * - * Exceptions in callbacks will be caught and the callback will be swapped with - * one that simply rethrows that exception back to the caller when invoked. - * - * @return integer WaitConditionLoop::CONDITION_* constant - * @throws Exception Any error from the condition callback - */ - public function invoke() { - $elapsed = 0.0; // seconds - $sleepUs = 0; // microseconds to sleep each time - $lastCheck = false; - $finalResult = self::CONDITION_TIMED_OUT; - do { - $checkStartTime = $this->getWallTime(); - // Check if the condition is met yet - $realStart = $this->getWallTime(); - $cpuStart = $this->getCpuTime(); - $checkResult = call_user_func( $this->condition ); - $cpu = $this->getCpuTime() - $cpuStart; - $real = $this->getWallTime() - $realStart; - // Exit if the condition is reached, and error occurs, or this is non-blocking - if ( $this->timeout <= 0 ) { - $finalResult = $checkResult ? self::CONDITION_REACHED : self::CONDITION_FAILED; - break; - } elseif ( (int)$checkResult !== self::CONDITION_CONTINUE ) { - if ( is_int( $checkResult ) ) { - $finalResult = $checkResult; - } else { - $finalResult = self::CONDITION_REACHED; - } - break; - } elseif ( $lastCheck ) { - break; // timeout reached - } - // Detect if condition callback seems to block or if justs burns CPU - $conditionUsesInterrupts = ( $real > 0.100 && $cpu <= $real * .03 ); - if ( !$this->popAndRunBusyCallback() && !$conditionUsesInterrupts ) { - // 10 queries = 10(10+100)/2 ms = 550ms, 14 queries = 1050ms - $sleepUs = min( $sleepUs + 10 * 1e3, 1e6 ); // stop incrementing at ~1s - $this->usleep( $sleepUs ); - } - $checkEndTime = $this->getWallTime(); - // The max() protects against the clock getting set back - $elapsed += max( $checkEndTime - $checkStartTime, 0.010 ); - // Do not let slow callbacks timeout without checking the condition one more time - $lastCheck = ( $elapsed >= $this->timeout ); - } while ( true ); - - $this->lastWaitTime = $elapsed; - - return $finalResult; - } - - /** - * @return float Seconds - */ - public function getLastWaitTime() { - return $this->lastWaitTime; - } - - /** - * @param integer $microseconds - */ - protected function usleep( $microseconds ) { - usleep( $microseconds ); - } - - /** - * @return float - */ - protected function getWallTime() { - return microtime( true ); - } - - /** - * @return float Returns 0.0 if not supported (Windows on PHP < 7) - */ - protected function getCpuTime() { - if ( $this->rusageMode === null ) { - return microtime( true ); // assume worst case (all time is CPU) - } - - $ru = getrusage( $this->rusageMode ); - $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6; - $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6; - - return $time; - } - - /** - * Run one of the callbacks that does work ahead of time for another caller - * - * @return bool Whether a callback was executed - */ - private function popAndRunBusyCallback() { - if ( $this->busyCallbacks ) { - reset( $this->busyCallbacks ); - $key = key( $this->busyCallbacks ); - /** @var callable $workCallback */ - $workCallback =& $this->busyCallbacks[$key]; - try { - $workCallback(); - } catch ( Exception $e ) { - $workCallback = function () use ( $e ) { - throw $e; - }; - } - unset( $this->busyCallbacks[$key] ); // consume - - return true; - } - - return false; - } -} diff --git a/includes/libs/lockmanager/LockManager.php b/includes/libs/lockmanager/LockManager.php index e89a9c7eac..bee34dcf30 100644 --- a/includes/libs/lockmanager/LockManager.php +++ b/includes/libs/lockmanager/LockManager.php @@ -4,6 +4,7 @@ * @ingroup FileBackend */ use Psr\Log\LoggerInterface; +use Wikimedia\WaitConditionLoop; /** * Resource locking handling. diff --git a/includes/libs/lockmanager/MemcLockManager.php b/includes/libs/lockmanager/MemcLockManager.php index 83334fe099..aecdf60cda 100644 --- a/includes/libs/lockmanager/MemcLockManager.php +++ b/includes/libs/lockmanager/MemcLockManager.php @@ -20,6 +20,7 @@ * @file * @ingroup LockManager */ +use Wikimedia\WaitConditionLoop; /** * Manage locks using memcached servers. diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index cd79b67136..d3deefb6fd 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -29,6 +29,7 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Wikimedia\WaitConditionLoop; /** * interface is intended to be more or less compatible with diff --git a/includes/libs/rdbms/ChronologyProtector.php b/includes/libs/rdbms/ChronologyProtector.php index 1f9aff161e..88af1dbdf8 100644 --- a/includes/libs/rdbms/ChronologyProtector.php +++ b/includes/libs/rdbms/ChronologyProtector.php @@ -22,6 +22,7 @@ */ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; +use Wikimedia\WaitConditionLoop; /** * Class for ensuring a consistent ordering of events as seen by the user, despite replication. diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php index f58628e942..f82d76d4d8 100644 --- a/includes/libs/rdbms/database/DatabasePostgres.php +++ b/includes/libs/rdbms/database/DatabasePostgres.php @@ -20,6 +20,7 @@ * @file * @ingroup Database */ +use Wikimedia\WaitConditionLoop; /** * @ingroup Database diff --git a/tests/phpunit/includes/libs/WaitConditionLoopTest.php b/tests/phpunit/includes/libs/WaitConditionLoopTest.php deleted file mode 100644 index 9ce93d6291..0000000000 --- a/tests/phpunit/includes/libs/WaitConditionLoopTest.php +++ /dev/null @@ -1,179 +0,0 @@ -wallClock += $microseconds / 1e6; - } - - function getCpuTime() { - return 0.0; - } - - function getWallTime() { - return $this->wallClock; - } - - public function setWallClock( &$timestamp ) { - $this->wallClock =& $timestamp; - } -} - -class WaitConditionLoopTest extends PHPUnit_Framework_TestCase { - public function testCallbackReached() { - $wallClock = microtime( true ); - - $count = 0; - $status = new StatusValue(); - $loop = new WaitConditionLoopFakeTime( - function () use ( &$count, $status ) { - ++$count; - $status->value = 'cookie'; - - return WaitConditionLoop::CONDITION_REACHED; - }, - 10.0, - $this->newBusyWork( $x, $y, $z ) - ); - $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() ); - $this->assertEquals( 1, $count ); - $this->assertEquals( 'cookie', $status->value ); - $this->assertEquals( [ 0, 0, 0 ], [ $x, $y, $z ], "No busy work done" ); - - $count = 0; - $loop = new WaitConditionLoopFakeTime( - function () use ( &$count, &$wallClock ) { - $wallClock += 1; - ++$count; - - return $count >= 2 ? WaitConditionLoop::CONDITION_REACHED : false; - }, - 7.0, - $this->newBusyWork( $x, $y, $z, $wallClock ) - ); - $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke(), - "Busy work did not cause timeout" ); - $this->assertEquals( [ 1, 0, 0 ], [ $x, $y, $z ] ); - - $count = 0; - $loop = new WaitConditionLoopFakeTime( - function () use ( &$count, &$wallClock ) { - $wallClock += .1; - ++$count; - - return $count > 80 ? true : false; - }, - 50.0, - $this->newBusyWork( $x, $y, $z, $wallClock, $dontCallMe, $badCalls ) - ); - $this->assertEquals( 0, $badCalls, "Callback exception not yet called" ); - $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() ); - $this->assertEquals( [ 1, 1, 1 ], [ $x, $y, $z ], "Busy work done" ); - $this->assertEquals( 1, $badCalls, "Bad callback ran and was exception caught" ); - - try { - $e = null; - $dontCallMe(); - } catch ( Exception $e ) { - } - - $this->assertInstanceOf( 'RunTimeException', $e ); - $this->assertEquals( 1, $badCalls, "Callback exception cached" ); - } - - public function testCallbackTimeout() { - $count = 0; - $wallClock = microtime( true ); - $loop = new WaitConditionLoopFakeTime( - function () use ( &$count, &$wallClock ) { - $wallClock += 3; - ++$count; - - return $count > 300 ? true : false; - }, - 50.0, - $this->newBusyWork( $x, $y, $z, $wallClock ) - ); - $loop->setWallClock( $wallClock ); - $this->assertEquals( $loop::CONDITION_TIMED_OUT, $loop->invoke() ); - $this->assertEquals( [ 1, 1, 1 ], [ $x, $y, $z ], "Busy work done" ); - - $loop = new WaitConditionLoopFakeTime( - function () use ( &$count, &$wallClock ) { - $wallClock += 3; - ++$count; - - return true; - }, - 0.0, - $this->newBusyWork( $x, $y, $z, $wallClock ) - ); - $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() ); - - $count = 0; - $loop = new WaitConditionLoopFakeTime( - function () use ( &$count, &$wallClock ) { - $wallClock += 3; - ++$count; - - return $count > 10 ? true : false; - }, - 0, - $this->newBusyWork( $x, $y, $z, $wallClock ) - ); - $this->assertEquals( $loop::CONDITION_FAILED, $loop->invoke() ); - } - - public function testCallbackAborted() { - $x = 0; - $wallClock = microtime( true ); - $loop = new WaitConditionLoopFakeTime( - function () use ( &$x, &$wallClock ) { - $wallClock += 2; - ++$x; - - return $x > 2 ? WaitConditionLoop::CONDITION_ABORTED : false; - }, - 10.0, - $this->newBusyWork( $x, $y, $z, $wallClock ) - ); - $loop->setWallClock( $wallClock ); - $this->assertEquals( $loop::CONDITION_ABORTED, $loop->invoke() ); - } - - private function newBusyWork( - &$x, &$y, &$z, &$wallClock = 1, &$dontCallMe = null, &$badCalls = 0 - ) { - $x = $y = $z = 0; - $badCalls = 0; - - $list = []; - $list[] = function () use ( &$x, &$wallClock ) { - $wallClock += 1; - - return ++$x; - }; - $dontCallMe = function () use ( &$badCalls ) { - ++$badCalls; - throw new RuntimeException( "TrollyMcTrollFace" ); - }; - $list[] =& $dontCallMe; - $list[] = function () use ( &$y, &$wallClock ) { - $wallClock += 15; - - return ++$y; - }; - $list[] = function () use ( &$z, &$wallClock ) { - $wallClock += 0.1; - - return ++$z; - }; - - return $list; - } -} -- 2.20.1