23 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
60 private const NORMAL_MAX_LAG = 10;
62 private const LAG_WAIT_TIMEOUT = 15;
68 'The given PageIdentity {pageIdentity} does not represent a proper page',
69 [
'pageIdentity' => $page ]
73 parent::__construct(
'refreshLinks', $page,
$params );
75 $this->removeDuplicates = (
81 $this->params += [
'causeAction' =>
'unknown',
'causeAgent' =>
'unknown' ];
85 $this->executionFlags |= self::JOB_NO_EXPLICIT_TRX_ROUND;
95 $job->command =
'refreshLinksPrioritized';
107 $job->command =
'refreshLinksDynamic';
115 if ( !empty( $this->params[
'recursive'] ) ) {
121 $services = MediaWikiServices::getInstance();
122 if ( !isset( $this->params[
'range'] ) ) {
123 $lbFactory = $services->getDBLoadBalancerFactory();
124 if ( !$lbFactory->waitForReplication( [
125 'domain' => $lbFactory->getLocalDomainID(),
126 'timeout' => self::LAG_WAIT_TIMEOUT
129 $stats = $services->getStatsdDataFactory();
130 $stats->increment(
'refreshlinks_warning.lag_wait_failed' );
135 $extraParams[
'triggeredRecursive'] =
true;
137 $extraParams[
'causeAction'] = $this->params[
'causeAction'];
138 $extraParams[
'causeAgent'] = $this->params[
'causeAgent'];
143 $services->getMainConfig()->get( MainConfigNames::UpdateRowsPerJob ),
145 [
'params' => $extraParams ]
147 $services->getJobQueueGroup()->push( $jobs );
149 } elseif ( isset( $this->params[
'pages'] ) ) {
151 foreach ( $this->params[
'pages'] as list( $ns, $dbKey ) ) {
174 $services = MediaWikiServices::getInstance();
175 $stats = $services->getStatsdDataFactory();
176 $renderer = $services->getRevisionRenderer();
177 $parserCache = $services->getParserCache();
178 $lbFactory = $services->getDBLoadBalancerFactory();
179 $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
182 $page = $services->getWikiPageFactory()->newFromTitle( $pageIdentity );
183 $page->loadPageData( WikiPage::READ_LATEST );
185 if ( !$page->exists() ) {
187 $logger = LoggerFactory::getInstance(
'RefreshLinksJob' );
189 'The page does not exist. Perhaps it was deleted?',
191 'page_title' => $this->title->getPrefixedDBkey(),
192 'job_params' => $this->getParams(),
193 'job_metadata' => $this->getMetadata()
196 $stats->increment(
'refreshlinks_outcome.bad_page_not_found' );
206 $dbw = $lbFactory->getMainLB()->getConnectionRef(
DB_PRIMARY );
208 $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(),
'job' );
209 if ( $scopedLock ===
null ) {
211 $this->
setLastError(
'LinksUpdate already running for this page, try again later.' );
212 $stats->increment(
'refreshlinks_outcome.bad_lock_failure' );
221 $stats->increment(
'refreshlinks_outcome.good_update_superseded' );
227 $lbFactory->beginPrimaryChanges( __METHOD__ );
228 $output = $this->
getParserOutput( $renderer, $parserCache, $page, $stats );
230 $lbFactory->commitPrimaryChanges( __METHOD__ );
240 $options[
'known-revision-output'] = $output;
242 $page->doSecondaryDataUpdates( $options );
247 $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
257 $rootTimestamp = $this->params[
'rootJobTimestamp'] ??
null;
258 if ( $rootTimestamp ===
null ) {
262 if ( !empty( $this->params[
'isOpportunistic'] ) ) {
265 $lagAwareTimestamp = $rootTimestamp;
270 (
int)
wfTimestamp( TS_UNIX, $rootTimestamp ) + self::NORMAL_MAX_LAG
274 return $lagAwareTimestamp;
284 return ( $lagAwareTimestamp !==
null && $page->
getLinksTimestamp() > $lagAwareTimestamp );
300 StatsdDataFactoryInterface $stats
309 if ( $cachedOutput ) {
310 return $cachedOutput;
317 [
'audience' => $revision::RAW ]
321 $output = $renderedRevision->getRevisionParserOutput( [
'generate-html' =>
false ] );
322 $output->setCacheTime( $parseTimestamp );
336 StatsdDataFactoryInterface $stats
344 $triggeringRevisionId = $this->params[
'triggeringRevisionId'] ??
null;
345 if ( $triggeringRevisionId && $triggeringRevisionId !== $latest ) {
347 $stats->increment(
'refreshlinks_outcome.bad_rev_not_current' );
348 $this->
setLastError(
"Revision $triggeringRevisionId is not current" );
357 $stats->increment(
'refreshlinks_outcome.bad_rev_not_found' );
358 $this->
setLastError(
"Revision not found for {$title->getPrefixedDBkey()}" );
361 } elseif ( $revision->getId() !== $latest || $revision->getPageId() !== $page->
getId() ) {
366 $stats->increment(
'refreshlinks_outcome.bad_rev_not_current' );
367 $this->
setLastError(
"Revision {$revision->getId()} is not current" );
388 StatsdDataFactoryInterface $stats
390 $cachedOutput =
null;
394 $rootTimestamp = $this->params[
'rootJobTimestamp'] ??
null;
395 if ( $rootTimestamp !==
null ) {
396 $opportunistic = !empty( $this->params[
'isOpportunistic'] );
397 if ( $page->
getTouched() >= $rootTimestamp || $opportunistic ) {
402 $output = $parserCache->
getDirty( $page, $parserOptions );
405 $output->getCacheRevisionId() == $currentRevision->
getId() &&
406 $output->getCacheTime() >= $this->getLagAwareRootTimestamp()
408 $cachedOutput = $output;
413 if ( $cachedOutput ) {
414 $stats->increment(
'refreshlinks.parser_cached' );
416 $stats->increment(
'refreshlinks.parser_uncached' );
419 return $cachedOutput;
427 'recursive' => !empty( $this->params[
'useRecursiveLinksUpdate'] ),
429 'causeAction' => $this->params[
'causeAction'],
430 'causeAgent' => $this->params[
'causeAgent']
432 if ( !empty( $this->params[
'triggeringUser'] ) ) {
433 $userInfo = $this->params[
'triggeringUser'];
434 if ( $userInfo[
'userId'] ) {
446 $info = parent::getDeduplicationInfo();
447 unset( $info[
'causeAction'] );
448 unset( $info[
'causeAgent'] );
449 if ( is_array( $info[
'params'] ) ) {
452 if ( isset( $info[
'params'][
'pages'] ) ) {
453 unset( $info[
'namespace'] );
454 unset( $info[
'title'] );
462 if ( !empty( $this->params[
'recursive'] ) ) {
464 } elseif ( isset( $this->params[
'pages'] ) ) {
465 return count( $this->params[
'pages'] );
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static partitionBacklinkJob(Job $job, $bSize, $cSize, $opts=[])
Break down $job into approximately ($bSize/$cSize) leaf jobs and a single partition job that covers t...
static invalidateCache(PageIdentity $page, $revid=null)
Clear the info cache for a given Title.
Class to both describe a background job and handle jobs.
array $params
Array of job parameters.
A class containing constants representing the names of configuration variables.
Exception if a PageIdentity is an invalid argument.
Cache for ParserOutput objects corresponding to the latest page revisions.
getDirty(PageRecord $page, $popts)
Retrieve the ParserOutput from ParserCache, even if it's outdated.
Job to update link tables for pages.
getDeduplicationInfo()
Subclasses may need to override this to make duplication detection work.
getParserOutput(RevisionRenderer $renderer, ParserCache $parserCache, WikiPage $page, StatsdDataFactoryInterface $stats)
Get the parser output if the page is unchanged from what was loaded in $page.
runForTitle(PageIdentity $pageIdentity)
static newDynamic(PageIdentity $page, array $params)
getLagAwareRootTimestamp()
static newPrioritized(PageIdentity $page, array $params)
__construct(PageIdentity $page, array $params)
getCurrentRevisionIfUnchanged(WikiPage $page, StatsdDataFactoryInterface $stats)
Get the current revision record if it is unchanged from what was loaded in $page.
isAlreadyRefreshed(WikiPage $page)
getParserOutputFromCache(ParserCache $parserCache, WikiPage $page, RevisionRecord $currentRevision, StatsdDataFactoryInterface $stats)
Get the parser output from cache if it reflects the change that triggered this job.
canExist()
Can this title represent a page in the wiki's database?
getLatestRevID( $flags=0)
What is the page_latest field for this page?
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
static newFromName( $name, $validate='valid')
static newFromId( $id)
Static factory method for creation from a given user ID.
Base representation for an editable wiki page.
getLinksTimestamp()
Get the page_links_updated field.
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
getId( $wikiId=self::LOCAL)
getTitle()
Get the title object of the article.
getRevisionRecord()
Get the latest revision.
getTouched()
Get the page_touched field.
Interface for objects (potentially) representing an editable wiki page.
canExist()
Checks whether this PageIdentity represents a "proper" page, meaning that it could exist as an editab...
if(count( $args)< 1) $job