$this->getCacheKey( $cache ),
$cache::TTL_HOUR,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
- $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+ $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
wfDebug( "User: cache miss for user {$this->mId}\n" );
$this->loadFromDatabase( self::READ_NORMAL );
public static function newFromConfirmationCode( $code, $flags = 0 ) {
$db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
? wfGetDB( DB_MASTER )
- : wfGetDB( DB_SLAVE );
+ : wfGetDB( DB_REPLICA );
$id = $db->selectField(
'user',
$conds[] = 'ug_user > ' . (int)$after;
}
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$ids = $dbr->selectFieldValues(
'user_groups',
'ug_user',
if ( is_null( $this->mGroups ) ) {
$db = ( $this->queryFlagsUsed & self::READ_LATEST )
? wfGetDB( DB_MASTER )
- : wfGetDB( DB_SLAVE );
+ : wfGetDB( DB_REPLICA );
$res = $db->select( 'user_groups',
[ 'ug_group' ],
[ 'ug_user' => $this->mId ],
* protected against race conditions using a compare-and-set (CAS) mechanism
* based on comparing $this->mTouched with the user_touched field.
*
- * @param DatabaseBase $db
- * @param array $conditions WHERE conditions for use with DatabaseBase::update
- * @return array WHERE conditions for use with DatabaseBase::update
+ * @param Database $db
+ * @param array $conditions WHERE conditions for use with Database::update
+ * @return array WHERE conditions for use with Database::update
*/
- protected function makeUpdateConditions( DatabaseBase $db, array $conditions ) {
+ protected function makeUpdateConditions( Database $db, array $conditions ) {
if ( $this->mTouched ) {
// CAS check: only update if the row wasn't changed sicne it was loaded.
$conditions['user_touched'] = $db->timestamp( $this->mTouched );
/**
* Get blocking information
- * @param bool $bFromSlave Whether to check the slave database first.
- * To improve performance, non-critical checks are done against slaves.
+ * @param bool $bFromSlave Whether to check the replica DB first.
+ * To improve performance, non-critical checks are done against replica DBs.
* Check when actually saving should be done against master.
*/
private function getBlockedStatus( $bFromSlave = true ) {
/**
* Check if user is blocked
*
- * @param bool $bFromSlave Whether to check the slave database instead of
+ * @param bool $bFromSlave Whether to check the replica DB instead of
* the master. Hacked from false due to horrible probs on site.
* @return bool True if blocked, false otherwise
*/
/**
* Get the block affecting the user, or null if the user is not blocked
*
- * @param bool $bFromSlave Whether to check the slave database instead of the master
+ * @param bool $bFromSlave Whether to check the replica DB instead of the master
* @return Block|null
*/
public function getBlock( $bFromSlave = true ) {
* Check if user is blocked from editing a particular article
*
* @param Title $title Title to check
- * @param bool $bFromSlave Whether to check the slave database instead of the master
+ * @param bool $bFromSlave Whether to check the replica DB instead of the master
* @return bool
*/
public function isBlockedFrom( $title, $bFromSlave = false ) {
return [];
}
$utp = $this->getTalkPage();
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
// Get the "last viewed rev" timestamp from the oldest message notification
$timestamp = $dbr->selectField( 'user_newtalk',
'MIN(user_last_timestamp)',
* @return bool True if the user has new messages
*/
protected function checkNewtalk( $field, $id ) {
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
function() use ( $cache, $key ) {
$cache->delete( $key );
- }
+ },
+ __METHOD__
);
}
}
if ( is_null( $this->mFormerGroups ) ) {
$db = ( $this->queryFlagsUsed & self::READ_LATEST )
? wfGetDB( DB_MASTER )
- : wfGetDB( DB_SLAVE );
+ : wfGetDB( DB_REPLICA );
$res = $db->select( 'user_former_groups',
[ 'ufg_group' ],
[ 'ufg_user' => $this->mId ],
if ( $this->mEditCount === null ) {
/* Populate the count, if it has not been populated yet */
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
// check if the user_editcount field has been initialized
$count = $dbr->selectField(
'user', 'user_editcount',
// Only update the timestamp if the page is being watched.
// The query to find out if it is watched is cached both in memcached and per-invocation,
- // and when it does have to be executed, it can be on a slave
+ // and when it does have to be executed, it can be on a replica DB
// If this is the user's newtalk page, we always update the timestamp
$force = '';
if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
* @note If the user doesn't have 'editmywatchlist', this will do nothing.
*/
public function clearAllNotifications() {
- if ( wfReadOnly() ) {
- return;
- }
-
+ global $wgUseEnotif, $wgShowUpdatedMarker;
// Do nothing if not allowed to edit the watchlist
- if ( !$this->isAllowed( 'editmywatchlist' ) ) {
+ if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
return;
}
- global $wgUseEnotif, $wgShowUpdatedMarker;
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
$this->setNewtalk( false );
return;
}
+
$id = $this->getId();
- if ( $id != 0 ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'watchlist',
- [ /* SET */ 'wl_notificationtimestamp' => null ],
- [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
- __METHOD__
- );
- // We also need to clear here the "you have new message" notification for the own user_talk page;
- // it's cleared one page view later in WikiPage::doViewUpdates().
+ if ( !$id ) {
+ return;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ $asOfTimes = array_unique( $dbw->selectFieldValues(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+ __METHOD__,
+ [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
+ ) );
+ if ( !$asOfTimes ) {
+ return;
}
+ // Immediately update the most recent touched rows, which hopefully covers what
+ // the user sees on the watchlist page before pressing "mark all pages visited"....
+ $dbw->update(
+ 'watchlist',
+ [ 'wl_notificationtimestamp' => null ],
+ [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
+ __METHOD__
+ );
+ // ...and finish the older ones in a post-send update with lag checks...
+ DeferredUpdates::addUpdate( new AutoCommitUpdate(
+ $dbw,
+ __METHOD__,
+ function () use ( $dbw, $id ) {
+ global $wgUpdateRowsPerQuery;
+
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+ $asOfTimes = array_unique( $dbw->selectFieldValues(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+ __METHOD__
+ ) );
+ foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
+ $dbw->update(
+ 'watchlist',
+ [ 'wl_notificationtimestamp' => null ],
+ [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
+ __METHOD__
+ );
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+ }
+ }
+ ) );
+ // We also need to clear here the "you have new message" notification for the own
+ // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
}
/**
// Get a new user_touched that is higher than the old one.
// This will be used for a CAS check as a last-resort safety
- // check against race conditions and slave lag.
+ // check against race conditions and replica DB lag.
$newTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
// Maybe the problem was a missed cache update; clear it to be safe
$this->clearSharedCache( 'refresh' );
// User was changed in the meantime or loaded with stale data
- $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
+ $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
throw new MWException(
"CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
" the version of the user to be saved is older than the current version."
$db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
? wfGetDB( DB_MASTER )
- : wfGetDB( DB_SLAVE );
+ : wfGetDB( DB_REPLICA );
$options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
? [ 'LOCK IN SHARE MODE' ]
if ( $this->getId() == 0 ) {
return false; // anons
}
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$time = $dbr->selectField( 'revision', 'rev_timestamp',
[ 'rev_user' => $this->getId() ],
__METHOD__,
* Deferred version of incEditCountImmediate()
*/
public function incEditCount() {
- wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
- $this->incEditCountImmediate();
- } );
+ wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
+ function () {
+ $this->incEditCountImmediate();
+ },
+ __METHOD__
+ );
}
/**
// Lazy initialization check...
if ( $dbw->affectedRows() == 0 ) {
// Now here's a goddamn hack...
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
if ( $dbr !== $dbw ) {
- // If we actually have a slave server, the count is
+ // If we actually have a replica DB server, the count is
// at least one behind because the current transaction
// has not been committed and replicated.
$this->mEditCount = $this->initEditCount( 1 );
} else {
- // But if DB_SLAVE is selecting the master, then the
+ // But if DB_REPLICA is selecting the master, then the
// count we just read includes the revision that was
// just added in the working transaction.
$this->mEditCount = $this->initEditCount();
} else {
if ( $this->mEditCount === null ) {
$this->getEditCount();
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
} else {
$this->mEditCount++;
* @return int Number of edits
*/
protected function initEditCount( $add = 0 ) {
- // Pull from a slave to be less cruel to servers
+ // Pull from a replica DB to be less cruel to servers
// Accuracy isn't the point anyway here
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$count = (int)$dbr->selectField(
'revision',
'COUNT(rev_user)',
// Load from database
$dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
? wfGetDB( DB_MASTER )
- : wfGetDB( DB_SLAVE );
+ : wfGetDB( DB_REPLICA );
$res = $dbr->select(
'user_properties',
[ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
// Find prior rows that need to be removed or updated. These rows will
- // all be deleted (the later so that INSERT IGNORE applies the new values).
+ // all be deleted (the latter so that INSERT IGNORE applies the new values).
$keysDelete = [];
foreach ( $res as $row ) {
if ( !isset( $saveOptions[$row->up_property] )
* Get a new instance of this user that was loaded from the master via a locking read
*
* Use this instead of the main context User when updating that user. This avoids races
- * where that user was loaded from a slave or even the master but without proper locks.
+ * where that user was loaded from a replica DB or even the master but without proper locks.
*
* @return User|null Returns null if the user was not found in the DB
* @since 1.27