X-Git-Url: https://git.cyclocoop.org/%242?a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Flbfactory%2FLBFactory.php;h=f5d57c4c0d70c8ca9d9de3f22ba22c8255a6f9c9;hb=bcd377cd008f8a8628ab727c3fa90090725d6c10;hp=d75ba931ee96f930f3c978dee1def1b027ab2d68;hpb=ae4a435159c3f234028c27a7247b407b9f37ad13;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/rdbms/lbfactory/LBFactory.php b/includes/libs/rdbms/lbfactory/LBFactory.php index d75ba931ee..f5d57c4c0d 100644 --- a/includes/libs/rdbms/lbfactory/LBFactory.php +++ b/includes/libs/rdbms/lbfactory/LBFactory.php @@ -27,7 +27,7 @@ use Psr\Log\LoggerInterface; * An interface for generating database load balancers * @ingroup Database */ -abstract class LBFactory { +abstract class LBFactory implements ILBFactory { /** @var ChronologyProtector */ protected $chronProt; /** @var object|string Class name or object With profileIn/profileOut methods */ @@ -72,35 +72,9 @@ abstract class LBFactory { /** @var string Agent name for query profiling */ protected $agent; - const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all - const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs - const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs - private static $loggerFields = [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ]; - /** - * Construct a manager of ILoadBalancer objects - * - * Sub-classes will extend the required keys in $conf with additional parameters - * - * @param $conf $params Array with keys: - * - localDomain: A DatabaseDomain or domain ID string. - * - readOnlyReason : Reason the master DB is read-only if so [optional] - * - srvCache : BagOStuff object for server cache [optional] - * - memCache : BagOStuff object for cluster memory cache [optional] - * - wanCache : WANObjectCache object [optional] - * - hostname : The name of the current server [optional] - * - cliMode: Whether the execution context is a CLI script. [optional] - * - profiler : Class name or instance with profileIn()/profileOut() methods. [optional] - * - trxProfiler: TransactionProfiler instance. [optional] - * - replLogger: PSR-3 logger instance. [optional] - * - connLogger: PSR-3 logger instance. [optional] - * - queryLogger: PSR-3 logger instance. [optional] - * - perfLogger: PSR-3 logger instance. [optional] - * - errorLogger : Callback that takes an Exception and logs it. [optional] - * @throws InvalidArgumentException - */ public function __construct( array $conf ) { $this->localDomain = isset( $conf['localDomain'] ) ? DatabaseDomain::newFromId( $conf['localDomain'] ) @@ -143,81 +117,54 @@ abstract class LBFactory { $this->ticket = mt_rand(); } - /** - * Disables all load balancers. All connections are closed, and any attempt to - * open a new connection will result in a DBAccessError. - * @see ILoadBalancer::disable() - */ public function destroy() { $this->shutdown( self::SHUTDOWN_NO_CHRONPROT ); $this->forEachLBCallMethod( 'disable' ); } + public function shutdown( + $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null + ) { + $chronProt = $this->getChronologyProtector(); + if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) { + $this->shutdownChronologyProtector( $chronProt, $workCallback, 'sync' ); + } elseif ( $mode === self::SHUTDOWN_CHRONPROT_ASYNC ) { + $this->shutdownChronologyProtector( $chronProt, null, 'async' ); + } + + $this->commitMasterChanges( __METHOD__ ); // sanity + } + /** - * Create a new load balancer object. The resulting object will be untracked, - * not chronology-protected, and the caller is responsible for cleaning it up. - * - * @param bool|string $domain Domain ID, or false for the current domain - * @return ILoadBalancer + * @see ILBFactory::newMainLB() + * @param bool $domain + * @return LoadBalancer */ abstract public function newMainLB( $domain = false ); /** - * Get a cached (tracked) load balancer object. - * - * @param bool|string $domain Domain ID, or false for the current domain - * @return ILoadBalancer + * @see ILBFactory::getMainLB() + * @param bool $domain + * @return mixed */ abstract public function getMainLB( $domain = false ); /** - * Create a new load balancer for external storage. The resulting object will be - * untracked, not chronology-protected, and the caller is responsible for - * cleaning it up. - * - * @param string $cluster External storage cluster, or false for core - * @param bool|string $domain Domain ID, or false for the current domain - * @return ILoadBalancer + * @see ILBFactory::newExternalLB() + * @param string $cluster + * @param bool $domain + * @return LoadBalancer */ - abstract protected function newExternalLB( $cluster, $domain = false ); + abstract public function newExternalLB( $cluster, $domain = false ); /** - * Get a cached (tracked) load balancer for external storage - * - * @param string $cluster External storage cluster, or false for core - * @param bool|string $domain Domain ID, or false for the current domain - * @return ILoadBalancer + * @see ILBFactory::getExternalLB() + * @param string $cluster + * @param bool $domain + * @return mixed */ abstract public function getExternalLB( $cluster, $domain = false ); - /** - * Execute a function for each tracked load balancer - * The callback is called with the load balancer as the first parameter, - * and $params passed as the subsequent parameters. - * - * @param callable $callback - * @param array $params - */ - abstract public function forEachLB( $callback, array $params = [] ); - - /** - * Prepare all tracked load balancers for shutdown - * @param integer $mode One of the class SHUTDOWN_* constants - * @param callable|null $workCallback Work to mask ChronologyProtector writes - */ - public function shutdown( - $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null - ) { - $chronProt = $this->getChronologyProtector(); - if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) { - $this->shutdownChronologyProtector( $chronProt, $workCallback, 'sync' ); - } elseif ( $mode === self::SHUTDOWN_CHRONPROT_ASYNC ) { - $this->shutdownChronologyProtector( $chronProt, null, 'async' ); - } - - $this->commitMasterChanges( __METHOD__ ); // sanity - } - /** * Call a method of each tracked load balancer * @@ -233,43 +180,15 @@ abstract class LBFactory { ); } - /** - * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot - * - * @param string $fname Caller name - * @since 1.28 - */ public function flushReplicaSnapshots( $fname = __METHOD__ ) { $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] ); } - /** - * Commit on all connections. Done for two reasons: - * 1. To commit changes to the masters. - * 2. To release the snapshot on all connections, master and replica DB. - * @param string $fname Caller name - * @param array $options Options map: - * - maxWriteDuration: abort if more than this much time was spent in write queries - */ public function commitAll( $fname = __METHOD__, array $options = [] ) { $this->commitMasterChanges( $fname, $options ); $this->forEachLBCallMethod( 'commitAll', [ $fname ] ); } - /** - * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set) - * - * The DBO_TRX setting will be reverted to the default in each of these methods: - * - commitMasterChanges() - * - rollbackMasterChanges() - * - commitAll() - * - * This allows for custom transaction rounds from any outer transaction scope. - * - * @param string $fname - * @throws DBTransactionError - * @since 1.28 - */ public function beginMasterChanges( $fname = __METHOD__ ) { if ( $this->trxRoundId !== false ) { throw new DBTransactionError( @@ -282,13 +201,6 @@ abstract class LBFactory { $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] ); } - /** - * Commit changes on all master connections - * @param string $fname Caller name - * @param array $options Options map: - * - maxWriteDuration: abort if more than this much time was spent in write queries - * @throws Exception - */ public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) { if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) { throw new DBTransactionError( @@ -296,6 +208,8 @@ abstract class LBFactory { "$fname: transaction round '{$this->trxRoundId}' still running." ); } + /** @noinspection PhpUnusedLocalVariableInspection */ + $scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure $this->forEachLBCallMethod( 'finalizeMasterChanges' ); $this->trxRoundId = false; @@ -320,11 +234,6 @@ abstract class LBFactory { } } - /** - * Rollback changes on all master connections - * @param string $fname Caller name - * @since 1.23 - */ public function rollbackMasterChanges( $fname = __METHOD__ ) { $this->trxRoundId = false; $this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' ); @@ -358,11 +267,6 @@ abstract class LBFactory { } } - /** - * Determine if any master connection has pending changes - * @return bool - * @since 1.23 - */ public function hasMasterChanges() { $ret = false; $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) { @@ -372,11 +276,6 @@ abstract class LBFactory { return $ret; } - /** - * Detemine if any lagged replica DB connection was used - * @return bool - * @since 1.28 - */ public function laggedReplicaUsed() { $ret = false; $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) { @@ -386,12 +285,6 @@ abstract class LBFactory { return $ret; } - /** - * Determine if any master connection has pending/written changes from this request - * @param float $age How many seconds ago is "recent" [defaults to LB lag wait timeout] - * @return bool - * @since 1.27 - */ public function hasOrMadeRecentMasterChanges( $age = null ) { $ret = false; $this->forEachLB( function ( ILoadBalancer $lb ) use ( $age, &$ret ) { @@ -400,30 +293,6 @@ abstract class LBFactory { return $ret; } - /** - * Waits for the replica DBs to catch up to the current master position - * - * Use this when updating very large numbers of rows, as in maintenance scripts, - * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs. - * - * By default this waits on all DB clusters actually used in this request. - * This makes sense when lag being waiting on is caused by the code that does this check. - * In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters - * that were not changed since the last wait check. To forcefully wait on a specific cluster - * for a given domain, use the 'domain' parameter. To forcefully wait on an "external" cluster, - * use the "cluster" parameter. - * - * Never call this function after a large DB write that is *still* in a transaction. - * It only makes sense to call this after the possible lag inducing changes were committed. - * - * @param array $opts Optional fields that include: - * - domain : wait on the load balancer DBs that handles the given domain ID - * - cluster : wait on the given external load balancer DBs - * - timeout : Max wait time. Default: ~60 seconds - * - ifWritesSince: Only wait if writes were done since this UNIX timestamp - * @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster - * @since 1.27 - */ public function waitForReplication( array $opts = [] ) { $opts += [ 'domain' => false, @@ -493,15 +362,6 @@ abstract class LBFactory { } } - /** - * Add a callback to be run in every call to waitForReplication() before waiting - * - * Callbacks must clear any transactions that they start - * - * @param string $name Callback name - * @param callable|null $callback Use null to unset a callback - * @since 1.28 - */ public function setWaitForReplicationListener( $name, callable $callback = null ) { if ( $callback ) { $this->replicationWaitCallbacks[$name] = $callback; @@ -510,36 +370,22 @@ abstract class LBFactory { } } - /** - * Get a token asserting that no transaction writes are active - * - * @param string $fname Caller name (e.g. __METHOD__) - * @return mixed A value to pass to commitAndWaitForReplication() - * @since 1.28 - */ public function getEmptyTransactionTicket( $fname ) { if ( $this->hasMasterChanges() ) { - $this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope." ); + $this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" . + ( new RuntimeException() )->getTraceAsString() ); + return null; } return $this->ticket; } - /** - * Convenience method for safely running commitMasterChanges()/waitForReplication() - * - * This will commit and wait unless $ticket indicates it is unsafe to do so - * - * @param string $fname Caller name (e.g. __METHOD__) - * @param mixed $ticket Result of getEmptyTransactionTicket() - * @param array $opts Options to waitForReplication() - * @throws DBReplicationWaitError - * @since 1.28 - */ public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) { if ( $ticket !== $this->ticket ) { - $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope." ); + $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" . + ( new RuntimeException() )->getTraceAsString() ); + return; } @@ -561,22 +407,10 @@ abstract class LBFactory { } } - /** - * @param string $dbName DB master name (e.g. "db1052") - * @return float|bool UNIX timestamp when client last touched the DB or false if not recent - * @since 1.28 - */ public function getChronologyProtectorTouched( $dbName ) { return $this->getChronologyProtector()->getTouched( $dbName ); } - /** - * Disable the ChronologyProtector for all load balancers - * - * This can be called at the start of special API entry points - * - * @since 1.27 - */ public function disableChronologyProtection() { $this->getChronologyProtector()->setEnabled( false ); } @@ -677,12 +511,6 @@ abstract class LBFactory { } } - /** - * Set a new table prefix for the existing local domain ID for testing - * - * @param string $prefix - * @since 1.28 - */ public function setDomainPrefix( $prefix ) { $this->localDomain = new DatabaseDomain( $this->localDomain->getDatabase(), @@ -695,32 +523,14 @@ abstract class LBFactory { } ); } - /** - * Close all open database connections on all open load balancers. - * @since 1.28 - */ public function closeAll() { $this->forEachLBCallMethod( 'closeAll', [] ); } - /** - * @param string $agent Agent name for query profiling - * @since 1.28 - */ public function setAgentName( $agent ) { $this->agent = $agent; } - /** - * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed - * - * Note that unlike cookies, this works accross domains - * - * @param string $url - * @param float $time UNIX timestamp just before shutdown() was called - * @return string - * @since 1.28 - */ public function appendPreShutdownTimeAsQuery( $url, $time ) { $usedCluster = 0; $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) { @@ -734,17 +544,27 @@ abstract class LBFactory { return strpos( $url, '?' ) === false ? "$url?cpPosTime=$time" : "$url&cpPosTime=$time"; } - /** - * @param array $info Map of fields, including: - * - IPAddress : IP address - * - UserAgent : User-Agent HTTP header - * - ChronologyProtection : cookie/header value specifying ChronologyProtector usage - * @since 1.28 - */ public function setRequestInfo( array $info ) { $this->requestInfo = $info + $this->requestInfo; } + /** + * Make PHP ignore user aborts/disconnects until the returned + * value leaves scope. This returns null and does nothing in CLI mode. + * + * @return ScopedCallback|null + */ + final protected function getScopedPHPBehaviorForCommit() { + if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540 + $old = ignore_user_abort( true ); // avoid half-finished operations + return new ScopedCallback( function () use ( $old ) { + ignore_user_abort( $old ); + } ); + } + + return null; + } + function __destruct() { $this->destroy(); }