'writes' => 0,
'readQueryTime' => 5
],
+ // Deferred updates that run after HTTP response is sent
+ 'PostSend' => [
+ 'readQueryTime' => 5,
+ 'writeQueryTime' => 1,
+ 'maxAffected' => 500
+ ],
// Background job runner
'JobRunner' => [
'readQueryTime' => 30,
// Assure deferred updates are not in the main transaction
wfGetLBFactory()->commitMasterChanges( __METHOD__ );
- // Ignore things like master queries/connections on GET requests
- // as long as they are in deferred updates (which catch errors).
- Profiler::instance()->getTransactionProfiler()->resetExpectations();
+ // Loosen DB query expectations since the HTTP client is unblocked
+ $trxProfiler = Profiler::instance()->getTransactionProfiler();
+ $trxProfiler->resetExpectations();
+ $trxProfiler->setExpectations(
+ $this->config->get( 'TrxProfilerLimits' )['PostSend'],
+ __METHOD__
+ );
// Do any deferred jobs
DeferredUpdates::doUpdates( 'enqueue' );
'SiteStore' => function( MediaWikiServices $services ) {
$rawSiteStore = new DBSiteStore( $services->getDBLoadBalancer() );
- $rawSiteStore->setLanguageCodeMapping(
- $services->getMainConfig()->get( 'DummyLanguageCodes' ) ?: []
- );
// TODO: replace wfGetCache with a CacheFactory service.
// TODO: replace wfIsHHVM with a capabilities service.
const ERROR_UNCACHEABLE = 'uncacheable';
const PRESUME_FRESH_TTL_SEC = 30;
+ const MAX_CACHE_TTL = 300; // 5 minutes
public function execute() {
$user = $this->getUser();
return $editInfo;
}
- $dbr = wfGetDB( DB_SLAVE );
+ $stats->increment( 'editstash.cache_misses.proven_stale' );
+ $logger->info( "Stale cache for key '$key'; old key with outside edits. (age: $age sec)" );
- $templates = []; // conditions to find changes/creations
- $templateUses = 0; // expected existing templates
- foreach ( $editInfo->output->getTemplateIds() as $ns => $stuff ) {
- foreach ( $stuff as $dbkey => $revId ) {
- $templates[(string)$ns][$dbkey] = (int)$revId;
- ++$templateUses;
- }
- }
- // Check that no templates used in the output changed...
- if ( count( $templates ) ) {
- $res = $dbr->select(
- 'page',
- [ 'ns' => 'page_namespace', 'dbk' => 'page_title', 'page_latest' ],
- $dbr->makeWhereFrom2d( $templates, 'page_namespace', 'page_title' ),
- __METHOD__
- );
- $changed = false;
- foreach ( $res as $row ) {
- $changed = $changed || ( $row->page_latest != $templates[$row->ns][$row->dbk] );
- }
-
- if ( $changed || $res->numRows() != $templateUses ) {
- $stats->increment( 'editstash.cache_misses.proven_stale' );
- $logger->info( "Stale cache for key '$key'; template changed. (age: $age sec)" );
- return false;
- }
- }
-
- $files = []; // conditions to find changes/creations
- foreach ( $editInfo->output->getFileSearchOptions() as $name => $options ) {
- $files[$name] = (string)$options['sha1'];
- }
- // Check that no files used in the output changed...
- if ( count( $files ) ) {
- $res = $dbr->select(
- 'image',
- [ 'name' => 'img_name', 'img_sha1' ],
- [ 'img_name' => array_keys( $files ) ],
- __METHOD__
- );
- $changed = false;
- foreach ( $res as $row ) {
- $changed = $changed || ( $row->img_sha1 != $files[$row->name] );
- }
-
- if ( $changed || $res->numRows() != count( $files ) ) {
- $stats->increment( 'editstash.cache_misses.proven_stale' );
- $logger->info( "Stale cache for key '$key'; file changed. (age: $age sec)" );
- return false;
- }
- }
-
- $stats->increment( 'editstash.cache_hits.proven_fresh' );
- $logger->debug( "Verified cache hit for key '$key' (age: $age sec)." );
-
- return $editInfo;
+ return false;
}
/**
// If an item is renewed, mind the cache TTL determined by config and parser functions.
// Put an upper limit on the TTL for sanity to avoid extreme template/file staleness.
$since = time() - wfTimestamp( TS_UNIX, $parserOutput->getTimestamp() );
- $ttl = min( $parserOutput->getCacheExpiry() - $since, 5 * 60 );
+ $ttl = min( $parserOutput->getCacheExpiry() - $since, self::MAX_CACHE_TTL );
if ( $ttl > 0 && !$parserOutput->getFlag( 'vary-revision' ) ) {
// Only store what is actually needed
const PARSE_THRESHOLD_SEC = 1.0;
/** @var integer Lag safety margin when comparing root job times to last-refresh times */
const CLOCK_FUDGE = 10;
+ /** @var integer How many seconds to wait for slaves to catch up */
+ const LAG_WAIT_TIMEOUT = 15;
function __construct( Title $title, array $params ) {
parent::__construct( 'refreshLinks', $title, $params );
// Avoid the overhead of de-duplication when it would be pointless
$this->removeDuplicates = (
- // Master positions won't match
- !isset( $params['masterPos'] ) &&
// Ranges rarely will line up
!isset( $params['range'] ) &&
// Multiple pages per job make matches unlikely
// Job to update all (or a range of) backlink pages for a page
if ( !empty( $this->params['recursive'] ) ) {
+ // When the base job branches, wait for the slaves to catch up to the master.
+ // From then on, we know that any template changes at the time the base job was
+ // enqueued will be reflected in backlink page parses when the leaf jobs run.
+ if ( !isset( $params['range'] ) ) {
+ try {
+ wfGetLBFactory()->waitForReplication( [
+ 'wiki' => wfWikiID(),
+ 'timeout' => self::LAG_WAIT_TIMEOUT
+ ] );
+ } catch ( DBReplicationWaitError $e ) { // only try so hard
+ $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+ $stats->increment( 'refreshlinks.lag_wait_failed' );
+ }
+ }
// Carry over information for de-duplication
$extraParams = $this->getRootJobParams();
- // Avoid slave lag when fetching templates.
- // When the outermost job is run, we know that the caller that enqueued it must have
- // committed the relevant changes to the DB by now. At that point, record the master
- // position and pass it along as the job recursively breaks into smaller range jobs.
- // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
- if ( isset( $this->params['masterPos'] ) ) {
- $extraParams['masterPos'] = $this->params['masterPos'];
- } elseif ( wfGetLB()->getServerCount() > 1 ) {
- $extraParams['masterPos'] = wfGetLB()->getMasterPos();
- } else {
- $extraParams['masterPos'] = false;
- }
$extraParams['triggeredRecursive'] = true;
// Convert this into no more than $wgUpdateRowsPerJob RefreshLinks per-title
// jobs and possibly a recursive RefreshLinks job for the rest of the backlinks
JobQueueGroup::singleton()->push( $jobs );
// Job to update link tables for a set of titles
} elseif ( isset( $this->params['pages'] ) ) {
- $this->waitForMasterPosition();
foreach ( $this->params['pages'] as $pageId => $nsAndKey ) {
list( $ns, $dbKey ) = $nsAndKey;
$this->runForTitle( Title::makeTitleSafe( $ns, $dbKey ) );
}
// Job to update link tables for a given title
} else {
- $this->waitForMasterPosition();
$this->runForTitle( $this->title );
}
return true;
}
- protected function waitForMasterPosition() {
- if ( !empty( $this->params['masterPos'] ) && wfGetLB()->getServerCount() > 1 ) {
- // Wait for the current/next slave DB handle to catch up to the master.
- // This way, we get the correct page_latest for templates or files that just
- // changed milliseconds ago, having triggered this job to begin with.
- wfGetLB()->waitFor( $this->params['masterPos'] );
- }
- }
-
/**
* @param Title $title
* @return bool
*/
private $dbLoadBalancer;
- /**
- * @var string[]
- */
- private $languageCodeMapping = [];
-
/**
* @since 1.27
*
);
foreach ( $res as $row ) {
- $languageCode = $row->site_language === '' ? null : $row->site_language;
- if ( isset( $this->languageCodeMapping[ $languageCode ] ) ) {
- $languageCode = $this->languageCodeMapping[ $languageCode ];
- }
-
$site = Site::newForType( $row->site_type );
$site->setGlobalId( $row->site_global_key );
$site->setInternalId( (int)$row->site_id );
$site->setForward( (bool)$row->site_forward );
$site->setGroup( $row->site_group );
- $site->setLanguageCode( $languageCode );
+ $site->setLanguageCode( $row->site_language === ''
+ ? null
+ : $row->site_language
+ );
$site->setSource( $row->site_source );
$site->setExtraData( unserialize( $row->site_data ) );
$site->setExtraConfig( unserialize( $row->site_config ) );
return $ok;
}
- /**
- * Provide an array that maps language codes
- *
- * @param string[] $newMapping
- */
- public function setLanguageCodeMapping( array $newMapping ) {
- $this->languageCodeMapping = $newMapping;
- }
-
}
*/
private $cacheFile;
- /**
- * @var string[]
- */
- private $languageCodeMapping = [];
-
/**
* @param string $cacheFile
*/
* @return Site
*/
private function newSiteFromArray( array $data ) {
- $languageCode = $data['language'];
- if ( isset( $this->languageCodeMapping[ $languageCode ] ) ) {
- $languageCode = $this->languageCodeMapping[ $languageCode ];
- }
-
$siteType = array_key_exists( 'type', $data ) ? $data['type'] : Site::TYPE_UNKNOWN;
$site = Site::newForType( $siteType );
$site->setGlobalId( $data['globalid'] );
$site->setForward( $data['forward'] );
$site->setGroup( $data['group'] );
- $site->setLanguageCode( $languageCode );
+ $site->setLanguageCode( $data['language'] );
$site->setSource( $data['source'] );
$site->setExtraData( $data['data'] );
$site->setExtraConfig( $data['config'] );
return $site;
}
- /**
- * Provide an array that maps language codes
- *
- * @param string[] $newMapping
- */
- public function setLanguageCodeMapping( array $newMapping ) {
- $this->languageCodeMapping = $newMapping;
- }
-
}
}
protected function logAuthResult( $success, $status = null ) {
- LoggerFactory::getInstance( 'authmanager-stats' )->info( 'Account creation attempt', [
+ LoggerFactory::getInstance( 'authevents' )->info( 'Account creation attempt', [
'event' => 'accountcreation',
'successful' => $success,
'status' => $status,
}
protected function logAuthResult( $success, $status = null ) {
- LoggerFactory::getInstance( 'authmanager-stats' )->info( 'Login attempt', [
+ LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
'event' => 'login',
'successful' => $success,
'status' => $status,
.mw-ui-input-inline {
display: inline-block;
width: auto;
+ // Make sure we limit `width` to parent element because
+ // in case of text `input` fields, `width: auto;` equals `size` attribute.
+ max-width: 100%;
}
// mw-ui-input-large
}
}
- /**
- * @covers DBSiteStore::getSites
- * @covers DBSiteStore::setLanguageCodeMapping
- */
- public function testLanguageCodeMapping() {
- TestSites::insertIntoDb();
-
- $store = $this->newDBSiteStore();
- $store->setLanguageCodeMapping( [ 'no' => 'nb' ] );
-
- $site = $store->getSite( 'nowiki' );
- $this->assertEquals( $site->getLanguageCode(), 'nb' );
- }
-
/**
* @covers DBSiteStore::saveSites
*/
return tempnam( sys_get_temp_dir(), 'mw-test-sitelist' );
}
- public function testLanguageCodeMapping() {
- $sites = $this->getSites();
- $cacheBuilder = $this->newSitesCacheFileBuilder( $sites );
- $cacheBuilder->build();
-
- $cache = new FileBasedSiteLookup( $this->cacheFile );
- $cache->setLanguageCodeMapping( [ 'en' => 'fa' ] );
-
- $this->assertEquals( $cache->getSite( 'enwiktionary' )->getLanguageCode(), 'fa' );
- }
-
}