Файловый менеджер - Редактировать - /var/www/html/poolcounter.zip
Ðазад
PK ! m�� � PoolCounterClient.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use MediaWiki\Status\Status; /** * @since 1.16 */ class PoolCounterClient extends PoolCounter { /** * @var ?resource the socket connection to the poolcounterd. Closing this * releases all locks acquired. */ private $conn; /** * @var string The server host name */ private $hostName; /** * @var PoolCounterConnectionManager */ private $manager; /** * @param PoolCounterConnectionManager $manager */ public function setManager( PoolCounterConnectionManager $manager ): void { $this->manager = $manager; } /** * @return Status */ public function getConn() { if ( !isset( $this->conn ) ) { $status = $this->manager->get( $this->key ); if ( !$status->isOK() ) { return $status; } // @phan-suppress-next-line PhanTypeArraySuspiciousNullable $this->conn = $status->value['conn']; // @phan-suppress-next-line PhanTypeArraySuspiciousNullable $this->hostName = $status->value['hostName']; // Set the read timeout to be 1.5 times the pool timeout. // This allows the server to time out gracefully before we give up on it. stream_set_timeout( $this->conn, 0, (int)( $this->timeout * 1e6 * 1.5 ) ); } // TODO: Convert from Status to StatusValue return Status::newGood( $this->conn ); } /** * @param string|int|float ...$args * @return Status */ public function sendCommand( ...$args ) { $args = str_replace( ' ', '%20', $args ); $cmd = implode( ' ', $args ); $status = $this->getConn(); if ( !$status->isOK() ) { return $status; } $conn = $status->value; $this->logger->debug( "Sending pool counter command: $cmd" ); if ( fwrite( $conn, "$cmd\n" ) === false ) { return Status::newFatal( 'poolcounter-write-error', $this->hostName ); } $response = fgets( $conn ); if ( $response === false ) { return Status::newFatal( 'poolcounter-read-error', $this->hostName ); } $response = rtrim( $response, "\r\n" ); $this->logger->debug( "Got pool counter response: $response" ); $parts = explode( ' ', $response, 2 ); $responseType = $parts[0]; switch ( $responseType ) { case 'LOCKED': $this->onAcquire(); break; case 'RELEASED': $this->onRelease(); break; case 'DONE': case 'NOT_LOCKED': case 'QUEUE_FULL': case 'TIMEOUT': case 'LOCK_HELD': break; case 'ERROR': default: $parts = explode( ' ', $parts[1], 2 ); $errorMsg = $parts[1] ?? '(no message given)'; return Status::newFatal( 'poolcounter-remote-error', $errorMsg, $this->hostName ); } return Status::newGood( constant( "PoolCounter::$responseType" ) ); } /** * @param int|null $timeout * @return Status */ public function acquireForMe( $timeout = null ) { $status = $this->precheckAcquire(); if ( !$status->isGood() ) { return $status; } return $this->sendCommand( 'ACQ4ME', $this->key, $this->workers, $this->maxqueue, $timeout ?? $this->timeout ); } /** * @param int|null $timeout * @return Status */ public function acquireForAnyone( $timeout = null ) { $status = $this->precheckAcquire(); if ( !$status->isGood() ) { return $status; } return $this->sendCommand( 'ACQ4ANY', $this->key, $this->workers, $this->maxqueue, $timeout ?? $this->timeout ); } /** * @return Status */ public function release() { $status = $this->sendCommand( 'RELEASE' ); if ( $this->conn ) { $this->manager->close( $this->conn ); $this->conn = null; } return $status; } } PK ! �c� � PoolWorkArticleView.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use MediaWiki\Logger\Spi as LoggerSpi; use MediaWiki\MediaWikiServices; use MediaWiki\Page\ParserOutputAccess; use MediaWiki\Parser\ParserOptions; use MediaWiki\Parser\ParserOutput; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Revision\RevisionRenderer; use MediaWiki\Revision\SlotRecord; use MediaWiki\Status\Status; use MediaWiki\WikiMap\WikiMap; /** * PoolCounter protected work wrapping RenderedRevision->getRevisionParserOutput. * Caching behavior may be defined by subclasses. * * @note No audience checks are applied. * * @internal */ class PoolWorkArticleView extends PoolCounterWork { /** @var ParserOptions */ protected $parserOptions; /** @var RevisionRecord */ protected $revision; /** @var RevisionRenderer */ private $renderer; /** @var LoggerSpi */ protected $loggerSpi; /** * @param string $workKey * @param RevisionRecord $revision Revision to render * @param ParserOptions $parserOptions ParserOptions to use for the parse * @param RevisionRenderer $revisionRenderer * @param LoggerSpi $loggerSpi */ public function __construct( string $workKey, RevisionRecord $revision, ParserOptions $parserOptions, RevisionRenderer $revisionRenderer, LoggerSpi $loggerSpi ) { parent::__construct( 'ArticleView', $workKey ); $this->revision = $revision; $this->parserOptions = $parserOptions; $this->renderer = $revisionRenderer; $this->loggerSpi = $loggerSpi; } /** * @return Status */ public function doWork() { return $this->renderRevision(); } /** * Render the given revision. * * @see ParserOutputAccess::renderRevision * * @param ?ParserOutput $previousOutput previously-cached output for this * page (used by Parsoid for selective updates) * @param bool $doSample Whether to collect statistics on this render * @param string $sourceLabel the source label to use on the statistics * @return Status with the value being a ParserOutput or null */ public function renderRevision( ?ParserOutput $previousOutput = null, bool $doSample = false, string $sourceLabel = '' ): Status { $renderedRevision = $this->renderer->getRenderedRevision( $this->revision, $this->parserOptions, null, [ 'audience' => RevisionRecord::RAW, 'previous-output' => $previousOutput, ] ); $parserOutput = $renderedRevision->getRevisionParserOutput(); if ( $doSample ) { $stats = MediaWikiServices::getInstance()->getStatsFactory(); $content = $this->revision->getContent( SlotRecord::MAIN ); $labels = [ 'source' => $sourceLabel, 'type' => $previousOutput === null ? 'full' : 'selective', 'reason' => $this->parserOptions->getRenderReason(), 'parser' => $this->parserOptions->getUseParsoid() ? 'parsoid' : 'legacy', 'opportunistic' => 'false', 'wiki' => WikiMap::getCurrentWikiId(), 'model' => $content ? $content->getModel() : 'unknown', ]; $stats ->getCounter( 'ParserCache_selective_total' ) ->setLabels( $labels ) ->increment(); $stats ->getCounter( 'ParserCache_selective_cpu_seconds' ) ->setLabels( $labels ) ->incrementBy( $parserOutput->getTimeProfile( 'cpu' ) ); } return Status::newGood( $parserOutput ); } /** * @param Status $status * @return Status */ public function error( $status ) { return $status; } } /** @deprecated class alias since 1.42 */ class_alias( PoolWorkArticleView::class, 'PoolWorkArticleView' ); PK ! �\T� � PoolCounterNull.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use MediaWiki\Status\Status; /** * A default PoolCounter, which provides no locking. * * @internal * @since 1.33 */ class PoolCounterNull extends PoolCounter { public function __construct() { // No parameters needed } public function acquireForMe( $timeout = null ) { return Status::newGood( PoolCounter::LOCKED ); } public function acquireForAnyone( $timeout = null ) { return Status::newGood( PoolCounter::LOCKED ); } public function release() { return Status::newGood( PoolCounter::RELEASED ); } } /** @deprecated class alias since 1.42 */ class_alias( PoolCounterNull::class, 'PoolCounterNull' ); PK ! �g g PoolCounter.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use MediaWiki\Status\Status; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; /** * Semaphore semantics to restrict how many workers may concurrently perform a task. * * When you have many workers (threads/servers) in service, and a * cached item expensive to produce expires, you may get several workers * computing the same expensive item at the same time. * * Given enough incoming requests and the item expiring quickly (non-cacheable, * or lots of edits or other invalidation events) that single task can end up * unfairly using most (or all) of the CPUs of the server cluster. * This is also known as "Michael Jackson effect", as this scenario happened on * the English Wikipedia in 2009 on the day Michael Jackson died. * See also <https://wikitech.wikimedia.org/wiki/Michael_Jackson_effect>. * * PoolCounter was created to provide semaphore semantics to restrict the number * of workers that may be concurrently performing a given task. Only one key * can be locked by any PoolCounter instance of a process, except for keys * that start with "nowait:". However, only non-blocking requests (timeout=0) * may be used with a "nowait:" key. * * By default PoolCounterNull is used, which provides no locking. * Install the poolcounterd service from * <https://gerrit.wikimedia.org/g/mediawiki/services/poolcounter> to * enable this feature. * * @since 1.16 * @stable to extend */ abstract class PoolCounter implements LoggerAwareInterface { /* Return codes */ public const LOCKED = 1; /* Lock acquired */ public const RELEASED = 2; /* Lock released */ public const DONE = 3; /* Another worker did the work for you */ public const ERROR = -1; /* Indeterminate error */ public const NOT_LOCKED = -2; /* Called release() with no lock held */ public const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */ public const TIMEOUT = -4; /* Timeout exceeded */ public const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */ /** @var string All workers with the same key share the lock */ protected $key; /** @var int Maximum number of workers working on tasks with the same key simultaneously */ protected $workers; /** * Maximum number of workers working on this task type, regardless of key. * 0 means unlimited. Max allowed value is 65536. * The way the slot limit is enforced is overzealous - this option should be used with caution. * @var int */ protected $slots = 0; /** @var int If this number of workers are already working/waiting, fail instead of wait */ protected $maxqueue; /** @var int Maximum time in seconds to wait for the lock */ protected $timeout; protected LoggerInterface $logger; /** * @var bool Whether the key is a "might wait" key */ private $isMightWaitKey; /** * @var int Whether this process holds a "might wait" lock key */ private static $acquiredMightWaitKey = 0; /** * @var bool Enable fast stale mode (T250248). This may be overridden by the work class. */ private $fastStale; /** * @param array $conf * @param string $type The class of actions to limit concurrency for (task type) * @param string $key */ public function __construct( array $conf, string $type, string $key ) { $this->workers = $conf['workers']; $this->maxqueue = $conf['maxqueue']; $this->timeout = $conf['timeout']; if ( isset( $conf['slots'] ) ) { $this->slots = $conf['slots']; } $this->fastStale = $conf['fastStale'] ?? false; $this->logger = new NullLogger(); if ( $this->slots ) { $key = $this->hashKeyIntoSlots( $type, $key, $this->slots ); } $this->key = $key; $this->isMightWaitKey = !preg_match( '/^nowait:/', $this->key ); } /** * @return string */ public function getKey() { return $this->key; } /** * I want to do this task and I need to do it myself. * * @param int|null $timeout Wait timeout, or null to use value passed to * the constructor * @return Status Value is one of Locked/Error */ abstract public function acquireForMe( $timeout = null ); /** * I want to do this task, but if anyone else does it * instead, it's also fine for me. I will read its cached data. * * @param int|null $timeout Wait timeout, or null to use value passed to * the constructor * @return Status Value is one of Locked/Done/Error */ abstract public function acquireForAnyone( $timeout = null ); /** * I have successfully finished my task. * Lets another one grab the lock, and returns the workers * waiting on acquireForAnyone() * * @return Status Value is one of Released/NotLocked/Error */ abstract public function release(); /** * Checks that the lock request is sensible. * @return Status good for sensible requests, fatal for the not so sensible * @since 1.25 */ final protected function precheckAcquire() { if ( $this->isMightWaitKey ) { if ( self::$acquiredMightWaitKey ) { /* * The poolcounter itself is quite happy to allow you to wait * on another lock while you have a lock you waited on already * but we think that it is unlikely to be a good idea. So we * made it an error. If you are _really_ _really_ sure it is a * good idea then feel free to implement an unsafe flag or * something. */ return Status::newFatal( 'poolcounter-usage-error', 'You may only aquire a single non-nowait lock.' ); } } elseif ( $this->timeout !== 0 ) { return Status::newFatal( 'poolcounter-usage-error', 'Locks starting in nowait: must have 0 timeout.' ); } return Status::newGood(); } /** * Update any lock tracking information when the lock is acquired * @since 1.25 */ final protected function onAcquire() { self::$acquiredMightWaitKey |= $this->isMightWaitKey; } /** * Update any lock tracking information when the lock is released * @since 1.25 */ final protected function onRelease() { self::$acquiredMightWaitKey &= !$this->isMightWaitKey; } /** * Given a key (any string) and the number of lots, returns a slot key (a prefix with a suffix * integer from the [0..($slots-1)] range). This is used for a global limit on the number of * instances of a given type that can acquire a lock. The hashing is deterministic so that * PoolCounter::$workers is always an upper limit of how many instances with the same key * can acquire a lock. * * @param string $type The class of actions to limit concurrency for (task type) * @param string $key PoolCounter instance key (any string) * @param int $slots The number of slots (max allowed value is 65536) * @return string Slot key with the type and slot number */ protected function hashKeyIntoSlots( $type, $key, $slots ) { return $type . ':' . ( hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots ); } /** * Is fast stale mode (T250248) enabled? This may be overridden by the * PoolCounterWork subclass. * * @return bool */ public function isFastStaleEnabled() { return $this->fastStale; } /** * @since 1.42 * @param LoggerInterface $logger * @return void */ public function setLogger( LoggerInterface $logger ) { $this->logger = $logger; } /** * @internal For use in PoolCounterWork only * @return LoggerInterface */ public function getLogger(): LoggerInterface { return $this->logger; } } /** @deprecated class alias since 1.42 */ class_alias( PoolCounter::class, 'PoolCounter' ); PK ! xo�� � PoolWorkArticleViewOld.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use MediaWiki\Logger\Spi as LoggerSpi; use MediaWiki\MainConfigNames; use MediaWiki\MediaWikiServices; use MediaWiki\Parser\ParserOptions; use MediaWiki\Parser\ParserOutput; use MediaWiki\Parser\RevisionOutputCache; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Revision\RevisionRenderer; use MediaWiki\Status\Status; /** * PoolWorkArticleView for an old revision of a page, using a simple cache. * * @internal */ class PoolWorkArticleViewOld extends PoolWorkArticleView { /** @var RevisionOutputCache */ private $cache; /** * @param string $workKey PoolCounter key. * @param RevisionOutputCache $cache The cache to store ParserOutput in. * @param RevisionRecord $revision Revision to render * @param ParserOptions $parserOptions ParserOptions to use for the parse * @param RevisionRenderer $revisionRenderer * @param LoggerSpi $loggerSpi */ public function __construct( string $workKey, RevisionOutputCache $cache, RevisionRecord $revision, ParserOptions $parserOptions, RevisionRenderer $revisionRenderer, LoggerSpi $loggerSpi ) { parent::__construct( $workKey, $revision, $parserOptions, $revisionRenderer, $loggerSpi ); $this->cache = $cache; $this->cacheable = true; } /** * @return Status */ public function doWork() { // T371713: Temporary statistics collection code to determine // feasibility of Parsoid selective update $sampleRate = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ParsoidSelectiveUpdateSampleRate ); $doSample = ( $sampleRate && mt_rand( 1, $sampleRate ) === 1 ); // Reduce effects of race conditions for slow parses (T48014) $cacheTime = wfTimestampNow(); $status = $this->renderRevision( null, /* don't attempt Parsoid selective updates on this path */ $doSample, 'PoolWorkArticleViewOld' ); /** @var ParserOutput|null $output */ $output = $status->getValue(); if ( $output && $output->isCacheable() ) { $this->cache->save( $output, $this->revision, $this->parserOptions, $cacheTime ); } return $status; } /** * @return Status|false */ public function getCachedWork() { $parserOutput = $this->cache->get( $this->revision, $this->parserOptions ); return $parserOutput ? Status::newGood( $parserOutput ) : false; } } /** @deprecated class alias since 1.42 */ class_alias( PoolWorkArticleViewOld::class, 'PoolWorkArticleViewOld' ); PK ! v� Q�; �; PoolCounterRedis.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use ArrayUtils; use Exception; use HashRing; use MediaWiki\Status\Status; use RedisException; use Wikimedia\ObjectCache\RedisConnectionPool; use Wikimedia\ObjectCache\RedisConnRef; /** * Version of PoolCounter that uses Redis * * There are four main redis keys used to track each pool counter key: * - poolcounter:l-slots-* : A list of available slot IDs for a pool. * - poolcounter:z-renewtime-* : A sorted set of (slot ID, UNIX timestamp as score) * used for tracking the next time a slot should be * released. This is -1 when a slot is created, and is * set when released (expired), locked, and unlocked. * - poolcounter:z-wait-* : A sorted set of (slot ID, UNIX timestamp as score) * used for tracking waiting processes (and wait time). * - poolcounter:l-wakeup-* : A list pushed to for the sake of waking up processes * when a any process in the pool finishes (lasts for 1ms). * * For a given pool key, all the redis keys start off non-existing and are deleted if not * used for a while to prevent garbage from building up on the server. They are atomically * re-initialized as needed. The "z-renewtime" key is used for detecting sessions which got * slots but then disappeared. Stale entries from there have their timestamp updated and the * corresponding slots freed up. The "z-wait" key is used for detecting processes registered * as waiting but that disappeared. Stale entries from there are deleted and the corresponding * slots are freed up. The worker count is included in all the redis key names as it does not * vary within each $wgPoolCounterConf type and doing so handles configuration changes. * * This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations. * Also this should be on a server plenty of RAM for the working set to avoid evictions. * Evictions could temporarily allow wait queues to double in size or temporarily cause * pools to appear as full when they are not. Using volatile-ttl and bumping memory-samples * in redis.conf can be helpful otherwise. * * @since 1.23 */ class PoolCounterRedis extends PoolCounter { /** @var HashRing */ protected $ring; /** @var RedisConnectionPool */ protected $pool; /** @var array (server label => host) map */ protected $serversByLabel; /** @var string SHA-1 of the key */ protected $keySha1; /** @var int TTL for locks to expire (work should finish in this time) */ protected $lockTTL; /** @var RedisConnRef */ protected $conn; /** @var string|null Pool slot value */ protected $slot; /** @var int|null AWAKE_* constant */ protected $onRelease; /** @var string Unique string to identify this process */ protected $session; /** @var float|null UNIX timestamp */ protected $slotTime; private const AWAKE_ONE = 1; // wake-up if when a slot can be taken from an existing process private const AWAKE_ALL = 2; // wake-up if an existing process finishes and wake up such others /** @var PoolCounterRedis[] List of active PoolCounterRedis objects in this script */ protected static $active = null; public function __construct( $conf, $type, $key ) { parent::__construct( $conf, $type, $key ); $this->serversByLabel = $conf['servers']; $serverLabels = array_keys( $conf['servers'] ); $this->ring = new HashRing( array_fill_keys( $serverLabels, 10 ) ); $conf['redisConfig']['serializer'] = 'none'; // for use with Lua $this->pool = RedisConnectionPool::singleton( $conf['redisConfig'] ); $this->keySha1 = sha1( $this->key ); $met = ini_get( 'max_execution_time' ); // usually 0 in CLI mode $this->lockTTL = $met ? 2 * (int)$met : 3600; if ( self::$active === null ) { self::$active = []; register_shutdown_function( [ __CLASS__, 'releaseAll' ] ); } } /** * @return Status Uses RediConnRef as value on success */ protected function getConnection() { if ( !isset( $this->conn ) ) { $conn = false; $servers = $this->ring->getLocations( $this->key, 3 ); ArrayUtils::consistentHashSort( $servers, $this->key ); foreach ( $servers as $server ) { $conn = $this->pool->getConnection( $this->serversByLabel[$server], $this->logger ); if ( $conn ) { break; } } if ( !$conn ) { return Status::newFatal( 'pool-servererror', implode( ', ', $servers ) ); } $this->conn = $conn; } return Status::newGood( $this->conn ); } public function acquireForMe( $timeout = null ) { $status = $this->precheckAcquire(); if ( !$status->isGood() ) { return $status; } return $this->waitForSlotOrNotif( self::AWAKE_ONE, $timeout ); } public function acquireForAnyone( $timeout = null ) { $status = $this->precheckAcquire(); if ( !$status->isGood() ) { return $status; } return $this->waitForSlotOrNotif( self::AWAKE_ALL, $timeout ); } public function release() { if ( $this->slot === null ) { return Status::newGood( PoolCounter::NOT_LOCKED ); // not locked } $status = $this->getConnection(); if ( !$status->isOK() ) { return $status; } /** @var RedisConnRef $conn */ $conn = $status->value; '@phan-var RedisConnRef $conn'; // phpcs:disable Generic.Files.LineLength static $script = /** @lang Lua */ <<<LUA local kSlots,kSlotsNextRelease,kWakeup,kWaiting = unpack(KEYS) local rMaxWorkers,rExpiry,rSlot,rSlotTime,rAwakeAll,rTime = unpack(ARGV) -- Add the slots back to the list (if rSlot is "w" then it is not a slot). -- Treat the list as expired if the "next release" time sorted-set is missing. if rSlot ~= 'w' and redis.call('exists',kSlotsNextRelease) == 1 then if 1*redis.call('zScore',kSlotsNextRelease,rSlot) ~= (rSlotTime + rExpiry) then -- Slot lock expired and was released already elseif redis.call('lLen',kSlots) >= 1*rMaxWorkers then -- Slots somehow got out of sync; reset the list redis.call('del',kSlots,kSlotsNextRelease) elseif redis.call('lLen',kSlots) == (1*rMaxWorkers - 1) and redis.call('zCard',kWaiting) == 0 then -- Slot list will be made full; clear it to save space (it re-inits as needed) -- since nothing is waiting on being unblocked by a push to the list redis.call('del',kSlots,kSlotsNextRelease) else -- Add slot back to pool and update the "next release" time redis.call('rPush',kSlots,rSlot) redis.call('zAdd',kSlotsNextRelease,rTime + 30,rSlot) -- Always keep renewing the expiry on use redis.call('expireAt',kSlots,math.ceil(rTime + rExpiry)) redis.call('expireAt',kSlotsNextRelease,math.ceil(rTime + rExpiry)) end end -- Update an ephemeral list to wake up other clients that can -- reuse any cached work from this process. Only do this if no -- slots are currently free (e.g. clients could be waiting). if 1*rAwakeAll == 1 then local count = redis.call('zCard',kWaiting) for i = 1,count do redis.call('rPush',kWakeup,'w') end redis.call('pexpire',kWakeup,1) end return 1 LUA; // phpcs:enable try { $conn->luaEval( $script, [ $this->getSlotListKey(), $this->getSlotRTimeSetKey(), $this->getWakeupListKey(), $this->getWaitSetKey(), $this->workers, $this->lockTTL, $this->slot, $this->slotTime, // used for CAS-style check ( $this->onRelease === self::AWAKE_ALL ) ? 1 : 0, microtime( true ), ], 4 # number of first argument(s) that are keys ); } catch ( RedisException $e ) { return Status::newFatal( 'pool-error-unknown', $e->getMessage() ); } $this->slot = null; $this->slotTime = null; $this->onRelease = null; unset( self::$active[$this->session] ); $this->onRelease(); return Status::newGood( PoolCounter::RELEASED ); } /** * @param int $doWakeup AWAKE_* constant * @param int|float|null $timeout * @return Status */ protected function waitForSlotOrNotif( $doWakeup, $timeout = null ) { if ( $this->slot !== null ) { return Status::newGood( PoolCounter::LOCK_HELD ); // already acquired } $status = $this->getConnection(); if ( !$status->isOK() ) { return $status; } /** @var RedisConnRef $conn */ $conn = $status->value; '@phan-var RedisConnRef $conn'; $now = microtime( true ); $timeout ??= $this->timeout; try { $slot = $this->initAndPopPoolSlotList( $conn, $now ); if ( ctype_digit( $slot ) ) { // Pool slot acquired by this process $slotTime = $now; } elseif ( $slot === 'QUEUE_FULL' ) { // Too many processes are waiting for pooled processes to finish return Status::newGood( PoolCounter::QUEUE_FULL ); } elseif ( $slot === 'QUEUE_WAIT' ) { // This process is now registered as waiting $keys = ( $doWakeup == self::AWAKE_ALL ) // Wait for an open slot or wake-up signal (preferring the latter) ? [ $this->getWakeupListKey(), $this->getSlotListKey() ] // Just wait for an actual pool slot : [ $this->getSlotListKey() ]; $res = $conn->blPop( $keys, $timeout ); if ( $res === [] ) { $conn->zRem( $this->getWaitSetKey(), $this->session ); // no longer waiting return Status::newGood( PoolCounter::TIMEOUT ); } $slot = $res[1]; // pool slot or "w" for wake-up notifications $slotTime = microtime( true ); // last microtime() was a few RTTs ago // Unregister this process as waiting and bump slot "next release" time $this->registerAcquisitionTime( $conn, $slot, $slotTime ); } else { return Status::newFatal( 'pool-error-unknown', "Server gave slot '$slot'." ); } } catch ( RedisException $e ) { return Status::newFatal( 'pool-error-unknown', $e->getMessage() ); } if ( $slot !== 'w' ) { $this->slot = $slot; $this->slotTime = $slotTime; $this->onRelease = $doWakeup; self::$active[$this->session] = $this; } $this->onAcquire(); return Status::newGood( $slot === 'w' ? PoolCounter::DONE : PoolCounter::LOCKED ); } /** * @param RedisConnRef $conn * @param float $now UNIX timestamp * @return string|bool False on failure */ protected function initAndPopPoolSlotList( RedisConnRef $conn, $now ) { static $script = /** @lang Lua */ <<<LUA local kSlots,kSlotsNextRelease,kSlotWaits = unpack(KEYS) local rMaxWorkers,rMaxQueue,rTimeout,rExpiry,rSess,rTime = unpack(ARGV) -- Initialize if the "next release" time sorted-set is empty. The slot key -- itself is empty if all slots are busy or when nothing is initialized. -- If the list is empty but the set is not, then it is the latter case. -- If the list exists but not the set, then reset everything. if redis.call('exists',kSlotsNextRelease) == 0 then redis.call('del',kSlots) for i = 1,1*rMaxWorkers do redis.call('rPush',kSlots,i) redis.call('zAdd',kSlotsNextRelease,-1,i) end -- Otherwise do maintenance to clean up after network partitions else -- Find stale slot locks and add free them (avoid duplicates) local staleLocks = redis.call('zRangeByScore',kSlotsNextRelease,0,rTime) for k,slot in ipairs(staleLocks) do redis.call('lRem',kSlots,0,slot) redis.call('rPush',kSlots,slot) redis.call('zAdd',kSlotsNextRelease,rTime + 30,slot) end -- Find stale wait slot entries and remove them redis.call('zRemRangeByScore',kSlotWaits,0,rTime - 2*rTimeout) end local slot -- Try to acquire a slot if possible now if redis.call('lLen',kSlots) > 0 then slot = redis.call('lPop',kSlots) -- Update the slot "next release" time redis.call('zAdd',kSlotsNextRelease,rTime + rExpiry,slot) elseif redis.call('zCard',kSlotWaits) >= 1*rMaxQueue then slot = 'QUEUE_FULL' else slot = 'QUEUE_WAIT' -- Register this process as waiting redis.call('zAdd',kSlotWaits,rTime,rSess) redis.call('expireAt',kSlotWaits,math.ceil(rTime + 2*rTimeout)) end -- Always keep renewing the expiry on use redis.call('expireAt',kSlots,math.ceil(rTime + rExpiry)) redis.call('expireAt',kSlotsNextRelease,math.ceil(rTime + rExpiry)) return slot LUA; return $conn->luaEval( $script, [ $this->getSlotListKey(), $this->getSlotRTimeSetKey(), $this->getWaitSetKey(), $this->workers, $this->maxqueue, $this->timeout, $this->lockTTL, $this->session, $now, ], 3 # number of first argument(s) that are keys ); } /** * @param RedisConnRef $conn * @param string $slot * @param float $now * @return int|bool False on failure */ protected function registerAcquisitionTime( RedisConnRef $conn, $slot, $now ) { static $script = /** @lang Lua */ <<<LUA local kSlots,kSlotsNextRelease,kSlotWaits = unpack(KEYS) local rSlot,rExpiry,rSess,rTime = unpack(ARGV) -- If rSlot is 'w' then the client was told to wake up but got no slot if rSlot ~= 'w' then -- Update the slot "next release" time redis.call('zAdd',kSlotsNextRelease,rTime + rExpiry,rSlot) -- Always keep renewing the expiry on use redis.call('expireAt',kSlots,math.ceil(rTime + rExpiry)) redis.call('expireAt',kSlotsNextRelease,math.ceil(rTime + rExpiry)) end -- Unregister this process as waiting redis.call('zRem',kSlotWaits,rSess) return 1 LUA; return $conn->luaEval( $script, [ $this->getSlotListKey(), $this->getSlotRTimeSetKey(), $this->getWaitSetKey(), $slot, $this->lockTTL, $this->session, $now, ], 3 # number of first argument(s) that are keys ); } /** * @return string */ protected function getSlotListKey() { return "poolcounter:l-slots-{$this->keySha1}-{$this->workers}"; } /** * @return string */ protected function getSlotRTimeSetKey() { return "poolcounter:z-renewtime-{$this->keySha1}-{$this->workers}"; } /** * @return string */ protected function getWaitSetKey() { return "poolcounter:z-wait-{$this->keySha1}-{$this->workers}"; } /** * @return string */ protected function getWakeupListKey() { return "poolcounter:l-wakeup-{$this->keySha1}-{$this->workers}"; } /** * Try to make sure that locks get released (even with exceptions and fatals) */ public static function releaseAll() { $e = null; foreach ( self::$active as $poolCounter ) { try { if ( $poolCounter->slot !== null ) { $poolCounter->release(); } } catch ( Exception $e ) { } } if ( $e ) { throw $e; } } } /** @deprecated class alias since 1.42 */ class_alias( PoolCounterRedis::class, 'PoolCounterRedis' ); PK ! M3�� � PoolWorkArticleViewCurrent.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use InvalidArgumentException; use MediaWiki\Logger\Spi as LoggerSpi; use MediaWiki\MainConfigNames; use MediaWiki\MediaWikiServices; use MediaWiki\Page\PageRecord; use MediaWiki\Page\WikiPageFactory; use MediaWiki\Parser\ParserCache; use MediaWiki\Parser\ParserOptions; use MediaWiki\Parser\ParserOutput; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Revision\RevisionRenderer; use MediaWiki\Status\Status; use MediaWiki\Utils\MWTimestamp; use Wikimedia\Rdbms\ChronologyProtector; use Wikimedia\Rdbms\ILBFactory; /** * PoolWorkArticleView for the current revision of a page, using ParserCache. * * @internal */ class PoolWorkArticleViewCurrent extends PoolWorkArticleView { /** @var string */ private $workKey; /** @var PageRecord */ private $page; /** @var ParserCache */ private $parserCache; /** @var ILBFactory */ private $lbFactory; /** @var WikiPageFactory */ private $wikiPageFactory; /** @var bool Whether it should trigger an opportunistic LinksUpdate or not */ private bool $triggerLinksUpdate; private ChronologyProtector $chronologyProtector; /** * @param string $workKey * @param PageRecord $page * @param RevisionRecord $revision Revision to render * @param ParserOptions $parserOptions ParserOptions to use for the parse * @param RevisionRenderer $revisionRenderer * @param ParserCache $parserCache * @param ILBFactory $lbFactory * @param ChronologyProtector $chronologyProtector * @param LoggerSpi $loggerSpi * @param WikiPageFactory $wikiPageFactory * @param bool $cacheable Whether it should store the result in cache or not * @param bool $triggerLinksUpdate Whether it should trigger an opportunistic LinksUpdate or not */ public function __construct( string $workKey, PageRecord $page, RevisionRecord $revision, ParserOptions $parserOptions, RevisionRenderer $revisionRenderer, ParserCache $parserCache, ILBFactory $lbFactory, ChronologyProtector $chronologyProtector, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, bool $cacheable = true, bool $triggerLinksUpdate = false ) { // TODO: Remove support for partially initialized RevisionRecord instances once // Article no longer uses fake revisions. if ( $revision->getPageId() && $revision->getPageId() !== $page->getId() ) { throw new InvalidArgumentException( '$page parameter mismatches $revision parameter' ); } parent::__construct( $workKey, $revision, $parserOptions, $revisionRenderer, $loggerSpi ); $this->workKey = $workKey; $this->page = $page; $this->parserCache = $parserCache; $this->lbFactory = $lbFactory; $this->chronologyProtector = $chronologyProtector; $this->wikiPageFactory = $wikiPageFactory; $this->cacheable = $cacheable; $this->triggerLinksUpdate = $triggerLinksUpdate; } /** * @return Status */ public function doWork() { // T371713: Temporary statistics collection code to determine // feasibility of Parsoid selective update $sampleRate = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ParsoidSelectiveUpdateSampleRate ); $doSample = ( $sampleRate && mt_rand( 1, $sampleRate ) === 1 ); $previousOutput = null; if ( $this->parserOptions->getUseParsoid() || $doSample ) { // Parsoid can do selective updates, so it is worth checking the // cache for an existing entry. Not worth it for the legacy // parser, though. $previousOutput = $this->parserCache->getDirty( $this->page, $this->parserOptions ) ?: null; } $status = $this->renderRevision( $previousOutput, $doSample, 'PoolWorkArticleViewCurrent' ); /** @var ParserOutput|null $output */ $output = $status->getValue(); if ( $output ) { if ( $this->cacheable && $output->isCacheable() ) { $this->parserCache->save( $output, $this->page, $this->parserOptions ); } if ( $this->triggerLinksUpdate ) { $this->wikiPageFactory->newFromTitle( $this->page )->triggerOpportunisticLinksUpdate( $output ); } } return $status; } /** * @return Status|false */ public function getCachedWork() { $parserOutput = $this->parserCache->get( $this->page, $this->parserOptions ); $logger = $this->loggerSpi->getLogger( 'PoolWorkArticleView' ); $logger->debug( $parserOutput ? 'parser cache hit' : 'parser cache miss' ); return $parserOutput ? Status::newGood( $parserOutput ) : false; } /** * @param bool $fast Fast stale request * @return Status|false */ public function fallback( $fast ) { $parserOutput = $this->parserCache->getDirty( $this->page, $this->parserOptions ); $logger = $this->loggerSpi->getLogger( 'dirty' ); if ( !$parserOutput ) { $logger->info( 'dirty missing' ); return false; } if ( $fast ) { /* Check if the stale response is from before the last write to the * DB by this user. Declining to return a stale response in this * case ensures that the user will see their own edit after page * save. * * Note that the CP touch time is the timestamp of the shutdown of * the save request, so there is a bias towards avoiding fast stale * responses of potentially several seconds. */ $lastWriteTime = $this->chronologyProtector->getTouched( $this->lbFactory->getMainLB() ); $cacheTime = MWTimestamp::convert( TS_UNIX, $parserOutput->getCacheTime() ); if ( $lastWriteTime && $cacheTime <= $lastWriteTime ) { $logger->info( 'declining to send dirty output since cache time ' . '{cacheTime} is before last write time {lastWriteTime}', [ 'workKey' => $this->workKey, 'cacheTime' => $cacheTime, 'lastWriteTime' => $lastWriteTime, ] ); // Forget this ParserOutput -- we will request it again if // necessary in slow mode. There might be a newer entry // available by that time. return false; } } $logger->info( $fast ? 'fast dirty output' : 'dirty output', [ 'workKey' => $this->workKey ] ); $status = Status::newGood( $parserOutput ); $status->warning( 'view-pool-dirty-output' ); $status->warning( $fast ? 'view-pool-contention' : 'view-pool-overload' ); return $status; } } /** @deprecated class alias since 1.42 */ class_alias( PoolWorkArticleViewCurrent::class, 'PoolWorkArticleViewCurrent' ); PK ! 1���Y Y PoolCounterWork.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use MediaWiki\MediaWikiServices; use MediaWiki\Status\Status; /** * Class for dealing with PoolCounters using class members */ abstract class PoolCounterWork { /** @var string */ protected $type = 'generic'; /** @var bool */ protected $cacheable = false; // does this override getCachedWork() ? /** @var PoolCounter */ private $poolCounter; /** * @param string $type The class of actions to limit concurrency for (task type) * @param string $key Key that identifies the queue this work is placed on * @param PoolCounter|null $poolCounter */ public function __construct( string $type, string $key, ?PoolCounter $poolCounter = null ) { $this->type = $type; // MW >= 1.35 $this->poolCounter = $poolCounter ?? MediaWikiServices::getInstance()->getPoolCounterFactory()->create( $type, $key ); } /** * Actually perform the work, caching it if needed * * @return mixed|false Work result or false */ abstract public function doWork(); /** * Retrieve the work from cache * * @return mixed|false Work result or false */ public function getCachedWork() { return false; } /** * A work not so good (eg. expired one) but better than an error * message. * * @param bool $fast True if PoolCounter is requesting a fast stale response (pre-wait) * @return mixed|false Work result or false */ public function fallback( $fast ) { return false; } /** * Do something with the error, like showing it to the user. * * @param Status $status * @return mixed|false */ public function error( $status ) { return false; } /** * Should fast stale mode be used? * * @return bool */ protected function isFastStaleEnabled() { return $this->poolCounter->isFastStaleEnabled(); } /** * Log an error * * @param Status $status * @return void */ public function logError( $status ) { $key = $this->poolCounter->getKey(); $this->poolCounter->getLogger()->info( "Pool key '$key' ({$this->type}): " . $status->getMessage()->inLanguage( 'en' )->useDatabase( false )->text() ); } /** * Get the result of the work (whatever it is), or the result of the error() function. * * This returns the result of the one of the following methods: * * - doWork(): Applies if the work is exclusive or no other process * is doing it, and on the condition that either this process * successfully entered the pool or the pool counter is down. * - doCachedWork(): Applies if the work is cacheable and this blocked on another * process which finished the work. * - fallback(): Applies for all remaining cases. * * If these all return false, then the result of error() is returned. * * In slow-stale mode, these three methods are called in the sequence given above, and * the first non-false response is used. This means in case of concurrent cache-miss requests * for the same revision, later ones will load on DBs and other backend services, and wait for * earlier requests to succeed and then read out their saved result. * * In fast-stale mode, if other requests hold doWork lock already, we call fallback() first * to let it try to find an acceptable return value. If fallback() returns false, then we * will wait for the doWork lock, as for slow stale mode, including potentially calling * fallback() a second time. * * @param bool $skipcache * @return mixed */ public function execute( $skipcache = false ) { if ( !$this->cacheable || $skipcache ) { $status = $this->poolCounter->acquireForMe(); } else { if ( $this->isFastStaleEnabled() ) { // In fast stale mode, check for existing locks by acquiring lock with 0 timeout $status = $this->poolCounter->acquireForAnyone( 0 ); if ( $status->isOK() && $status->value === PoolCounter::TIMEOUT ) { // Lock acquisition would block: try fallback $staleResult = $this->fallback( true ); if ( $staleResult !== false ) { return $staleResult; } // No fallback available, so wait for the lock $status = $this->poolCounter->acquireForAnyone(); } // else behave as if $status were returned in slow mode } else { $status = $this->poolCounter->acquireForAnyone(); } } if ( !$status->isOK() ) { // Respond gracefully to complete server breakage: just log it and do the work $this->logError( $status ); return $this->doWork(); } switch ( $status->value ) { case PoolCounter::LOCK_HELD: // Better to ignore nesting pool counter limits than to fail. // Assume that the outer pool limiting is reasonable enough. /* no break */ case PoolCounter::LOCKED: try { return $this->doWork(); } finally { $this->poolCounter->release(); } // no fall-through, because try returns or throws case PoolCounter::DONE: $result = $this->getCachedWork(); if ( $result === false ) { /* That someone else work didn't serve us. * Acquire the lock for me */ return $this->execute( true ); } return $result; case PoolCounter::QUEUE_FULL: case PoolCounter::TIMEOUT: $result = $this->fallback( false ); if ( $result !== false ) { return $result; } /* no break */ /* These two cases should never be hit... */ case PoolCounter::ERROR: default: $errors = [ PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout', ]; $status = Status::newFatal( $errors[$status->value] ?? 'pool-errorunknown' ); $this->logError( $status ); return $this->error( $status ); } } } /** @deprecated class alias since 1.42 */ class_alias( PoolCounterWork::class, 'PoolCounterWork' ); PK ! 2W PoolCounterConnectionManager.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use InvalidArgumentException; use MediaWiki\Status\Status; use Wikimedia\IPUtils; /** * Helper for \MediaWiki\PoolCounter\PoolCounterClient. * * @internal * @since 1.16 */ class PoolCounterConnectionManager { /** @var string[] */ public $hostNames; /** @var array */ public $conns = []; /** @var array */ public $refCounts = []; /** @var float */ public $timeout; /** @var int */ public $connect_timeout; /** * @internal Public for testing only * @var string */ public $host; /** * @internal Public for testing only * @var int */ public $port; /** * @param array $conf */ public function __construct( $conf ) { if ( !count( $conf['servers'] ) ) { throw new InvalidArgumentException( __METHOD__ . ': no servers configured' ); } $this->hostNames = $conf['servers']; $this->timeout = $conf['timeout'] ?? 0.1; $this->connect_timeout = $conf['connect_timeout'] ?? 0; } /** * @param string $key * @return Status */ public function get( $key ) { $hashes = []; foreach ( $this->hostNames as $hostName ) { $hashes[$hostName] = md5( $hostName . $key ); } asort( $hashes ); $errno = 0; $errstr = ''; $hostName = ''; $conn = null; foreach ( $hashes as $hostName => $hash ) { if ( isset( $this->conns[$hostName] ) ) { $this->refCounts[$hostName]++; return Status::newGood( [ 'conn' => $this->conns[$hostName], 'hostName' => $hostName ] ); } $parts = IPUtils::splitHostAndPort( $hostName ); if ( $parts === false ) { $errstr = '\'servers\' config incorrectly configured.'; return Status::newFatal( 'poolcounter-connection-error', $errstr, $hostName ); } // IPV6 addresses need to be in brackets otherwise it fails. $this->host = IPUtils::isValidIPv6( $parts[0] ) ? '[' . $parts[0] . ']' : $parts[0]; $this->port = $parts[1] ?: 7531; // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged $conn = @$this->open( $this->host, $this->port, $errno, $errstr ); if ( $conn ) { break; } } if ( !$conn ) { return Status::newFatal( 'poolcounter-connection-error', $errstr, $hostName ); } // TODO: Inject PSR Logger from ServiceWiring wfDebug( "Connected to pool counter server: $hostName\n" ); $this->conns[$hostName] = $conn; $this->refCounts[$hostName] = 1; return Status::newGood( [ 'conn' => $conn, 'hostName' => $hostName ] ); } /** * Open a socket. Just a wrapper for fsockopen() * @param string $host * @param int $port * @param int &$errno * @param string &$errstr * @return null|resource */ private function open( $host, $port, &$errno, &$errstr ) { // If connect_timeout is set, we try to open the socket twice. // You usually want to set the connection timeout to a very // small value so that in case of failure of a server the // connection to poolcounter is not a SPOF. if ( $this->connect_timeout > 0 ) { $tries = 2; $timeout = $this->connect_timeout; } else { $tries = 1; $timeout = $this->timeout; } $fp = null; while ( true ) { $fp = fsockopen( $host, $port, $errno, $errstr, $timeout ); if ( $fp !== false || --$tries < 1 ) { break; } usleep( 1000 ); } return $fp; } /** * @param resource $conn */ public function close( $conn ) { foreach ( $this->conns as $hostName => $otherConn ) { if ( $conn === $otherConn ) { if ( $this->refCounts[$hostName] ) { $this->refCounts[$hostName]--; } if ( !$this->refCounts[$hostName] ) { fclose( $conn ); unset( $this->conns[$hostName] ); } } } } } PK ! q�5$ $ PoolCounterWorkViaCallback.phpnu �Iw�� <?php /** * 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 */ namespace MediaWiki\PoolCounter; use InvalidArgumentException; /** * Convenience class for dealing with PoolCounter using callbacks * @since 1.22 * @newable * @note marked as newable in 1.35 for lack of a better alternative, * but should use a factory in the future. */ class PoolCounterWorkViaCallback extends PoolCounterWork { /** @var callable */ protected $doWork; /** @var callable|null */ protected $doCachedWork; /** @var callable|null */ protected $fallback; /** @var callable|null */ protected $error; /** * Build a PoolCounterWork class from a type, key, and callback map. * * The callback map must at least have a callback for the 'doWork' method. * Additionally, callbacks can be provided for the 'doCachedWork', 'fallback', * and 'error' methods. Methods without callbacks will be no-ops that return false. * If a 'doCachedWork' callback is provided, then execute() may wait for any prior * process in the pool to finish and reuse its cached result. * * @stable to call * @param string $type The class of actions to limit concurrency for * @param string $key * @param array $callbacks Map of callbacks */ public function __construct( $type, $key, array $callbacks ) { parent::__construct( $type, $key ); foreach ( [ 'doWork', 'doCachedWork', 'fallback', 'error' ] as $name ) { if ( isset( $callbacks[$name] ) ) { if ( !is_callable( $callbacks[$name] ) ) { throw new InvalidArgumentException( "Invalid callback provided for '$name' function." ); } $this->$name = $callbacks[$name]; } } if ( !isset( $this->doWork ) ) { throw new InvalidArgumentException( "No callback provided for 'doWork' function." ); } $this->cacheable = isset( $this->doCachedWork ); } public function doWork() { return ( $this->doWork )(); } public function getCachedWork() { if ( $this->doCachedWork ) { return ( $this->doCachedWork )(); } return false; } public function fallback( $fast ) { if ( $this->fallback ) { return ( $this->fallback )( $fast ); } return false; } public function error( $status ) { if ( $this->error ) { return ( $this->error )( $status ); } return false; } } /** @deprecated class alias since 1.42 */ class_alias( PoolCounterWorkViaCallback::class, 'PoolCounterWorkViaCallback' ); PK ! !��? ? PoolCounterFactory.phpnu �Iw�� <?php namespace MediaWiki\PoolCounter; use Psr\Log\LoggerInterface; /** * @since 1.40 */ class PoolCounterFactory { private ?PoolCounterConnectionManager $manager = null; private ?array $typeConfigs; private array $clientConf; private LoggerInterface $logger; /** * @internal For use by ServiceWiring * @param array|null $typeConfigs See $wgPoolCounterConf * @param array $clientConf See $wgPoolCountClientConf * @param LoggerInterface $logger */ public function __construct( ?array $typeConfigs, array $clientConf, LoggerInterface $logger ) { $this->typeConfigs = $typeConfigs; $this->clientConf = $clientConf; $this->logger = $logger; } private function getClientManager(): PoolCounterConnectionManager { $this->manager ??= new PoolCounterConnectionManager( $this->clientConf ); return $this->manager; } /** * Get a PoolCounter. * * @internal This should only be called from PoolCounterWork * @param string $type The class of actions to limit concurrency for (task type) * @param string $key * @return PoolCounter */ public function create( string $type, string $key ): PoolCounter { $conf = $this->typeConfigs[$type] ?? null; if ( $conf === null ) { return new PoolCounterNull(); } $class = $conf['class'] ?? null; if ( $class === 'PoolCounter_Client' ) { // Since 1.16: Introduce PoolCounter_Client in PoolCounter extension. // Since 1.40: Move to core as symbolic name, discourage use of class name. $class = PoolCounterClient::class; } /** @var PoolCounter $poolCounter */ $poolCounter = new $class( $conf, $type, $key ); $poolCounter->setLogger( $this->logger ); // Support subclass for back-compat with the extension if ( $poolCounter instanceof PoolCounterClient ) { $poolCounter->setManager( $this->getClientManager() ); } return $poolCounter; } } PK ! m�� � PoolCounterClient.phpnu �Iw�� PK ! �c� � � PoolWorkArticleView.phpnu �Iw�� PK ! �\T� � �! PoolCounterNull.phpnu �Iw�� PK ! �g g �'