%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/vendor/wikimedia/wait-condition-loop/src/ |
| Current File : //www/varak.net/wiki.varak.net/vendor/wikimedia/wait-condition-loop/src/WaitConditionLoop.php |
<?php
/**
* Wait loop that reaches a condition or times out.
*
* Copyright (C) 2016 Aaron Schulz <aschulz@wikimedia.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Aaron Schulz
*/
namespace Wikimedia;
/**
* Wait loop that reaches a condition or times out
*/
class WaitConditionLoop {
/** @var callable */
private $condition;
/** @var callable[] */
private $busyCallbacks = [];
/** @var float Seconds */
private $timeout;
/** @var float Seconds */
private $lastWaitTime;
/** @var integer|null */
private $rusageMode;
const CONDITION_REACHED = 1;
const CONDITION_CONTINUE = 0; // evaluates as falsey
const CONDITION_FAILED = -1;
const CONDITION_TIMED_OUT = -2;
const CONDITION_ABORTED = -3;
/**
* @param callable $condition Callback that returns a WaitConditionLoop::CONDITION_ constant
* @param float $timeout Timeout in seconds
* @param array &$busyCallbacks List of callbacks to do useful work (by reference)
*/
public function __construct( callable $condition, $timeout = 5.0, &$busyCallbacks = [] ) {
$this->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, an 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;
}
}