X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FBagOStuff.php;h=d0b68bcbd7b26a84ce9115e5479bf4444861f804;hb=61898ad28ed69c5b391eb43e0db9386279b9612c;hp=5472e837d4c0c67fffba10e943d7c53fce69260d;hpb=2e7f4e48735b0e916336d9166cb1ab1756e0fa9f;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index 5472e837d4..d0b68bcbd7 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -29,6 +29,8 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Wikimedia\ScopedCallback; +use Wikimedia\WaitConditionLoop; /** * interface is intended to be more or less compatible with @@ -45,31 +47,29 @@ use Psr\Log\NullLogger; abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { /** @var array[] Lock tracking */ protected $locks = []; - /** @var integer ERR_* class constant */ protected $lastError = self::ERR_NONE; - /** @var string */ protected $keyspace = 'local'; - /** @var LoggerInterface */ protected $logger; - /** @var callback|null */ protected $asyncHandler; + /** @var integer Seconds */ + protected $syncTimeout; /** @var bool */ private $debugMode = false; - /** @var array */ private $duplicateKeyLookups = []; - /** @var bool */ private $reportDupes = false; - /** @var bool */ private $dupeTrackScheduled = false; + /** @var callable[] */ + protected $busyCallbacks = []; + /** @var integer[] Map of (ATTR_* class constant => QOS_* class constant) */ protected $attrMap = []; @@ -94,6 +94,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * In CLI mode, it should run the task immediately. * - reportDupes: Whether to emit warning log messages for all keys that were * requested more than once (requires an asyncHandler). + * - syncTimeout: How long to wait with WRITE_SYNC in seconds. * @param array $params */ public function __construct( array $params = [] ) { @@ -114,6 +115,8 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { if ( !empty( $params['reportDupes'] ) && is_callable( $this->asyncHandler ) ) { $this->reportDupes = true; } + + $this->syncTimeout = isset( $params['syncTimeout'] ) ? $params['syncTimeout'] : 3; } /** @@ -409,35 +412,21 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { } $expiry = min( $expiry ?: INF, self::TTL_DAY ); - - $this->clearLastError(); - $timestamp = microtime( true ); // starting UNIX timestamp - if ( $this->add( "{$key}:lock", 1, $expiry ) ) { - $locked = true; - } elseif ( $this->getLastError() || $timeout <= 0 ) { - $locked = false; // network partition or non-blocking - } else { - // Estimate the RTT (us); use 1ms minimum for sanity - $uRTT = max( 1e3, ceil( 1e6 * ( microtime( true ) - $timestamp ) ) ); - $sleep = 2 * $uRTT; // rough time to do get()+set() - - $attempts = 0; // failed attempts - do { - if ( ++$attempts >= 3 && $sleep <= 5e5 ) { - // Exponentially back off after failed attempts to avoid network spam. - // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts. - $sleep *= 2; - } - usleep( $sleep ); // back off + $loop = new WaitConditionLoop( + function () use ( $key, $timeout, $expiry ) { $this->clearLastError(); - $locked = $this->add( "{$key}:lock", 1, $expiry ); - if ( $this->getLastError() ) { - $locked = false; // network partition - break; + if ( $this->add( "{$key}:lock", 1, $expiry ) ) { + return true; // locked! + } elseif ( $this->getLastError() ) { + return WaitConditionLoop::CONDITION_ABORTED; // network partition? } - } while ( !$locked && ( microtime( true ) - $timestamp ) < $timeout ); - } + return WaitConditionLoop::CONDITION_CONTINUE; + }, + $timeout + ); + + $locked = ( $loop->invoke() === $loop::CONDITION_REACHED ); if ( $locked ) { $this->locks[$key] = [ 'class' => $rclass, 'depth' => 1 ]; } @@ -642,6 +631,30 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { $this->lastError = $err; } + /** + * Let a callback be run to avoid wasting time on special blocking calls + * + * The callbacks may or may not be called ever, in any particular order. + * They are likely to be invoked when something WRITE_SYNC is used used. + * They should follow a caching pattern as shown below, so that any code + * using the word will get it's result no matter what happens. + * @code + * $result = null; + * $workCallback = function () use ( &$result ) { + * if ( !$result ) { + * $result = .... + * } + * return $result; + * } + * @endcode + * + * @param callable $workCallback + * @since 1.28 + */ + public function addBusyCallback( callable $workCallback ) { + $this->busyCallbacks[] = $workCallback; + } + /** * Modify a cache update operation array for EventRelayer::notify() *