23use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
45 private const NORMAL_MAX_LAG = 10;
47 private const LAG_WAIT_TIMEOUT = 15;
50 parent::__construct(
'refreshLinks', $page,
$params );
52 $this->removeDuplicates = (
58 $this->params += [
'causeAction' =>
'unknown',
'causeAgent' =>
'unknown' ];
62 $this->executionFlags |= self::JOB_NO_EXPLICIT_TRX_ROUND;
72 $job->command =
'refreshLinksPrioritized';
84 $job->command =
'refreshLinksDynamic';
89 public function run() {
93 if ( !empty( $this->params[
'recursive'] ) ) {
94 $services = MediaWikiServices::getInstance();
98 if ( !isset( $this->params[
'range'] ) ) {
99 $lbFactory = $services->getDBLoadBalancerFactory();
100 if ( !$lbFactory->waitForReplication( [
101 'domain' => $lbFactory->getLocalDomainID(),
102 'timeout' => self::LAG_WAIT_TIMEOUT
104 $stats = $services->getStatsdDataFactory();
105 $stats->increment(
'refreshlinks.lag_wait_failed' );
110 $extraParams[
'triggeredRecursive'] =
true;
112 $extraParams[
'causeAction'] = $this->params[
'causeAction'];
113 $extraParams[
'causeAgent'] = $this->params[
'causeAgent'];
118 $services->getMainConfig()->get(
'UpdateRowsPerJob' ),
120 [
'params' => $extraParams ]
122 JobQueueGroup::singleton()->push( $jobs );
124 } elseif ( isset( $this->params[
'pages'] ) ) {
125 foreach ( $this->params[
'pages'] as list( $ns, $dbKey ) ) {
126 $title = Title::makeTitleSafe( $ns, $dbKey );
147 $services = MediaWikiServices::getInstance();
148 $stats = $services->getStatsdDataFactory();
149 $renderer = $services->getRevisionRenderer();
150 $parserCache = $services->getParserCache();
151 $lbFactory = $services->getDBLoadBalancerFactory();
152 $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
155 $page = $services->getWikiPageFactory()->newFromTitle( $pageIdentity );
156 $page->loadPageData( WikiPage::READ_LATEST );
158 if ( !$page->exists() ) {
160 $logger = LoggerFactory::getInstance(
'RefreshLinksJob' );
162 'The page does not exist. Perhaps it was deleted?',
164 'page_title' => $this->title->getPrefixedDBkey(),
165 'job_params' => $this->getParams(),
166 'job_metadata' => $this->getMetadata()
171 $stats->increment(
'refreshlinks.rev_not_found' );
179 $dbw = $lbFactory->getMainLB()->getConnectionRef(
DB_PRIMARY );
181 $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(),
'job' );
182 if ( $scopedLock ===
null ) {
184 $this->
setLastError(
'LinksUpdate already running for this page, try again later.' );
185 $stats->increment(
'refreshlinks.lock_failure' );
191 $stats->increment(
'refreshlinks.update_skipped' );
197 $lbFactory->beginPrimaryChanges( __METHOD__ );
198 $output = $this->
getParserOutput( $renderer, $parserCache, $page, $stats );
200 $lbFactory->commitPrimaryChanges( __METHOD__ );
207 $options[
'known-revision-output'] = $output;
209 $page->doSecondaryDataUpdates( $options );
210 InfoAction::invalidateCache( $page );
214 $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
225 $rootTimestamp = $this->params[
'rootJobTimestamp'] ??
null;
226 if ( $rootTimestamp ===
null ) {
230 if ( !empty( $this->params[
'isOpportunistic'] ) ) {
233 $lagAwareTimestamp = $rootTimestamp;
238 wfTimestamp( TS_UNIX, $rootTimestamp ) + self::NORMAL_MAX_LAG
258 StatsdDataFactoryInterface $stats
266 if ( $cachedOutput ) {
267 return $cachedOutput;
274 [
'audience' => $revision::RAW ]
278 $output = $renderedRevision->getRevisionParserOutput( [
'generate-html' =>
false ] );
279 $output->setCacheTime( $parseTimestamp );
293 StatsdDataFactoryInterface $stats
301 $triggeringRevisionId = $this->params[
'triggeringRevisionId'] ??
null;
302 if ( $triggeringRevisionId && $triggeringRevisionId !== $latest ) {
304 $stats->increment(
'refreshlinks.rev_not_current' );
305 $this->
setLastError(
"Revision $triggeringRevisionId is not current" );
314 $stats->increment(
'refreshlinks.rev_not_found' );
315 $this->
setLastError(
"Revision not found for {$title->getPrefixedDBkey()}" );
318 } elseif ( $revision->getId() !== $latest || $revision->getPageId() !== $page->
getId() ) {
323 $stats->increment(
'refreshlinks.rev_not_current' );
324 $this->
setLastError(
"Revision {$revision->getId()} is not current" );
345 StatsdDataFactoryInterface $stats
347 $cachedOutput =
null;
351 $rootTimestamp = $this->params[
'rootJobTimestamp'] ??
null;
352 if ( $rootTimestamp !==
null ) {
353 $opportunistic = !empty( $this->params[
'isOpportunistic'] );
354 if ( $opportunistic ) {
357 $lagAwareTimestamp = $rootTimestamp;
362 wfTimestamp( TS_UNIX, $rootTimestamp ) + self::NORMAL_MAX_LAG
366 if ( $page->
getTouched() >= $rootTimestamp || $opportunistic ) {
371 $output = $parserCache->
getDirty( $page, $parserOptions );
374 $output->getCacheRevisionId() == $currentRevision->
getId() &&
375 $output->getCacheTime() >= $lagAwareTimestamp
377 $cachedOutput = $output;
382 if ( $cachedOutput ) {
383 $stats->increment(
'refreshlinks.parser_cached' );
385 $stats->increment(
'refreshlinks.parser_uncached' );
388 return $cachedOutput;
396 'recursive' => !empty( $this->params[
'useRecursiveLinksUpdate'] ),
398 'causeAction' => $this->params[
'causeAction'],
399 'causeAgent' => $this->params[
'causeAgent']
401 if ( !empty( $this->params[
'triggeringUser'] ) ) {
402 $userInfo = $this->params[
'triggeringUser'];
403 if ( $userInfo[
'userId'] ) {
415 $info = parent::getDeduplicationInfo();
416 unset( $info[
'causeAction'] );
417 unset( $info[
'causeAgent'] );
418 if ( is_array( $info[
'params'] ) ) {
421 if ( isset( $info[
'params'][
'pages'] ) ) {
422 unset( $info[
'namespace'] );
423 unset( $info[
'title'] );
431 if ( !empty( $this->params[
'recursive'] ) ) {
433 } elseif ( isset( $this->params[
'pages'] ) ) {
434 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...
Class to both describe a background job and handle jobs.
array $params
Array of job parameters.
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)
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.
getLatestRevID( $flags=0)
What is the page_latest field for this page?
static newFromName( $name, $validate='valid')
static newFromId( $id)
Static factory method for creation from a given user ID.
Class representing a MediaWiki article and history.
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.
if(count( $args)< 1) $job