use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
/**
* @author Addshore
*/
class NameTableStore {
- /** @var LoadBalancer */
+ /** @var ILoadBalancer */
private $loadBalancer;
/** @var WANObjectCache */
private $insertCallback = null;
/**
- * @param LoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
- * @param WANObjectCache $cache A cache manager for caching data
+ * @param ILoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
+ * @param WANObjectCache $cache A cache manager for caching data. This can be the local
+ * wiki's default instance even if $wikiId refers to a different wiki, since
+ * makeGlobalKey() is used to constructed a key that allows cached names from
+ * the same database to be re-used between wikis. For example, enwiki and frwiki will
+ * use the same cache keys for names from the wikidatawiki database, regardless
+ * of the cache's default key space.
* @param LoggerInterface $logger
* @param string $table
* @param string $idField
* This parameter was introduced in 1.32
*/
public function __construct(
- LoadBalancer $dbLoadBalancer,
+ ILoadBalancer $dbLoadBalancer,
WANObjectCache $cache,
LoggerInterface $logger,
$table,
if ( $searchResult === false ) {
$id = $this->store( $name );
if ( $id === null ) {
- // RACE: $name was already in the db, probably just inserted, so load from master
- // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs
- $table = $this->loadTable(
- $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTOCOMMIT )
- );
+ // RACE: $name was already in the db, probably just inserted, so load from master.
+ // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs.
+ // ...but not during unit tests, because we need the fake DB tables of the default
+ // connection.
+ $connFlags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+ $table = $this->reloadMap( $connFlags );
+
$searchResult = array_search( $name, $table, true );
if ( $searchResult === false ) {
// Insert failed due to IGNORE flag, but DB_MASTER didn't give us the data
$this->logger->error( $m );
throw new NameTableAccessException( $m );
}
- $this->purgeWANCache(
- function () {
- $this->cache->reap( $this->getCacheKey(), INF );
- }
- );
+ } elseif ( isset( $table[$id] ) ) {
+ throw new NameTableAccessException(
+ "Expected unused ID from database insert for '$name' "
+ . " into '{$this->table}', but ID $id is already associated with"
+ . " the name '{$table[$id]}'! This may indicate database corruption!" );
} else {
$table[$id] = $name;
$searchResult = $id;
+
// As store returned an ID we know we inserted so delete from WAN cache
$this->purgeWANCache(
function () {
return $searchResult;
}
+ /**
+ * Reloads the name table from the master database, and purges the WAN cache entry.
+ *
+ * @note This should only be called in situations where the local cache has been detected
+ * to be out of sync with the database. There should be no reason to call this method
+ * from outside the NameTabelStore during normal operation. This method may however be
+ * useful in unit tests.
+ *
+ * @param int $connFlags ILoadBalancer::CONN_XXX flags. Optional.
+ *
+ * @return \string[] The freshly reloaded name map
+ */
+ public function reloadMap( $connFlags = 0 ) {
+ $this->tableCache = $this->loadTable(
+ $this->getDBConnection( DB_MASTER, $connFlags )
+ );
+ $this->purgeWANCache(
+ function () {
+ $this->cache->reap( $this->getCacheKey(), INF );
+ }
+ );
+
+ return $this->tableCache;
+ }
+
/**
* Get the id of the given name.
* If the name doesn't exist this will throw.
if ( array_key_exists( $id, $table ) ) {
return $table[$id];
}
+ $fname = __METHOD__;
$table = $this->cache->getWithSetCallback(
$this->getCacheKey(),
$this->cacheTTL,
- function ( $oldValue, &$ttl, &$setOpts ) use ( $id ) {
+ function ( $oldValue, &$ttl, &$setOpts ) use ( $id, $fname ) {
// Check if cached value is up-to-date enough to have $id
if ( is_array( $oldValue ) && array_key_exists( $id, $oldValue ) ) {
// Completely leave the cache key alone
// Log a fallback to master
if ( $source === DB_MASTER ) {
$this->logger->info(
- __METHOD__ . 'falling back to master select from ' .
+ $fname . ' falling back to master select from ' .
$this->table . ' with id ' . $id
);
}