21use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
59 private const NORMAL_MAX_LAG = 10;
61 private const LAG_WAIT_TIMEOUT = 15;
64 if ( empty( $params[
'pages'] ) && !$page->
canExist() ) {
67 'The given PageIdentity {pageIdentity} does not represent a proper page',
68 [
'pageIdentity' => $page ]
72 parent::__construct(
'refreshLinks', $page, $params );
74 $this->removeDuplicates = (
76 !isset( $params[
'range'] ) &&
78 !( isset( $params[
'pages'] ) && count( $params[
'pages'] ) != 1 )
80 $this->params += [
'causeAction' =>
'RefreshLinksJob',
'causeAgent' =>
'unknown' ];
84 $this->executionFlags |= self::JOB_NO_EXPLICIT_TRX_ROUND;
93 $job =
new self( $page, $params );
94 $job->command =
'refreshLinksPrioritized';
105 $job =
new self( $page, $params );
106 $job->command =
'refreshLinksDynamic';
114 if ( !empty( $this->params[
'recursive'] ) ) {
120 $services = MediaWikiServices::getInstance();
121 if ( !isset( $this->params[
'range'] ) ) {
122 $lbFactory = $services->getDBLoadBalancerFactory();
123 if ( !$lbFactory->waitForReplication( [
124 'domain' => $lbFactory->getLocalDomainID(),
125 'timeout' => self::LAG_WAIT_TIMEOUT
128 $stats = $services->getStatsdDataFactory();
129 $stats->increment(
'refreshlinks_warning.lag_wait_failed' );
134 $extraParams[
'triggeredRecursive'] =
true;
136 $extraParams[
'causeAction'] = $this->params[
'causeAction'];
137 $extraParams[
'causeAgent'] = $this->params[
'causeAgent'];
142 $services->getMainConfig()->get( MainConfigNames::UpdateRowsPerJob ),
144 [
'params' => $extraParams ]
146 $services->getJobQueueGroup()->push( $jobs );
148 } elseif ( isset( $this->params[
'pages'] ) ) {
150 foreach ( $this->params[
'pages'] as [ $ns, $dbKey ] ) {
151 $title = Title::makeTitleSafe( $ns, $dbKey );
173 $services = MediaWikiServices::getInstance();
174 $stats = $services->getStatsdDataFactory();
175 $renderer = $services->getRevisionRenderer();
176 $parserCache = $services->getParserCache();
177 $lbFactory = $services->getDBLoadBalancerFactory();
178 $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
181 $page = $services->getWikiPageFactory()->newFromTitle( $pageIdentity );
186 $logger = LoggerFactory::getInstance(
'RefreshLinksJob' );
188 'The page does not exist. Perhaps it was deleted?',
190 'page_title' => $this->title->getPrefixedDBkey(),
191 'job_params' => $this->getParams(),
192 'job_metadata' => $this->getMetadata()
195 $stats->increment(
'refreshlinks_outcome.bad_page_not_found' );
205 $dbw = $lbFactory->getPrimaryDatabase();
207 $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->
getId(),
'job' );
208 if ( $scopedLock ===
null ) {
210 $this->
setLastError(
'LinksUpdate already running for this page, try again later.' );
211 $stats->increment(
'refreshlinks_outcome.bad_lock_failure' );
217 if ( $this->isAlreadyRefreshed( $page ) ) {
220 $stats->increment(
'refreshlinks_outcome.good_update_superseded' );
227 $lbFactory->flushReplicaSnapshots( __METHOD__ );
229 $lbFactory->beginPrimaryChanges( __METHOD__ );
230 $output = $this->getParserOutput( $renderer, $parserCache, $page, $stats );
231 $options = $this->getDataUpdateOptions();
232 $lbFactory->commitPrimaryChanges( __METHOD__ );
242 $options[
'known-revision-output'] = $output;
245 InfoAction::invalidateCache( $page );
255 $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
263 private function getLagAwareRootTimestamp() {
265 $rootTimestamp = $this->params[
'rootJobTimestamp'] ??
null;
266 if ( $rootTimestamp ===
null ) {
270 if ( !empty( $this->params[
'isOpportunistic'] ) ) {
273 $lagAwareTimestamp = $rootTimestamp;
278 (
int)
wfTimestamp( TS_UNIX, $rootTimestamp ) + self::NORMAL_MAX_LAG
282 return $lagAwareTimestamp;
289 private function isAlreadyRefreshed(
WikiPage $page ) {
290 $lagAwareTimestamp = $this->getLagAwareRootTimestamp();
292 return ( $lagAwareTimestamp !==
null && $page->
getLinksTimestamp() > $lagAwareTimestamp );
304 private function getParserOutput(
308 StatsdDataFactoryInterface $stats
310 $revision = $this->getCurrentRevisionIfUnchanged( $page, $stats );
316 $cachedOutput = $this->getParserOutputFromCache( $parserCache, $page, $revision, $stats );
317 if ( $cachedOutput ) {
318 return $cachedOutput;
321 $causeAction = $this->params[
'causeAction'] ??
'RefreshLinksJob';
326 [
'audience' => $revision::RAW,
'causeAction' => $causeAction ]
330 $output = $renderedRevision->getRevisionParserOutput( [
'generate-html' =>
false ] );
331 $output->setCacheTime( $parseTimestamp );
343 private function getCurrentRevisionIfUnchanged(
345 StatsdDataFactoryInterface $stats
351 $latest =
$title->getLatestRevID( Title::READ_LATEST );
353 $triggeringRevisionId = $this->params[
'triggeringRevisionId'] ??
null;
354 if ( $triggeringRevisionId && $triggeringRevisionId !== $latest ) {
356 $stats->increment(
'refreshlinks_outcome.bad_rev_not_current' );
357 $this->
setLastError(
"Revision $triggeringRevisionId is not current" );
366 $stats->increment(
'refreshlinks_outcome.bad_rev_not_found' );
367 $this->
setLastError(
"Revision not found for {$title->getPrefixedDBkey()}" );
370 } elseif ( $revision->getId() !== $latest || $revision->getPageId() !== $page->
getId() ) {
375 $stats->increment(
'refreshlinks_outcome.bad_rev_not_current' );
376 $this->
setLastError(
"Revision {$revision->getId()} is not current" );
393 private function getParserOutputFromCache(
397 StatsdDataFactoryInterface $stats
399 $cachedOutput =
null;
403 $rootTimestamp = $this->params[
'rootJobTimestamp'] ??
null;
404 if ( $rootTimestamp !==
null ) {
405 $opportunistic = !empty( $this->params[
'isOpportunistic'] );
406 if ( $page->
getTouched() >= $rootTimestamp || $opportunistic ) {
411 $output = $parserCache->
getDirty( $page, $parserOptions );
414 $output->getCacheRevisionId() == $currentRevision->
getId() &&
415 $output->getCacheTime() >= $this->getLagAwareRootTimestamp()
417 $cachedOutput = $output;
422 if ( $cachedOutput ) {
423 $stats->increment(
'refreshlinks.parser_cached' );
425 $stats->increment(
'refreshlinks.parser_uncached' );
428 return $cachedOutput;
434 private function getDataUpdateOptions() {
436 'recursive' => !empty( $this->params[
'useRecursiveLinksUpdate'] ),
438 'causeAction' => $this->params[
'causeAction'],
439 'causeAgent' => $this->params[
'causeAgent']
441 if ( !empty( $this->params[
'triggeringUser'] ) ) {
442 $userInfo = $this->params[
'triggeringUser'];
443 if ( $userInfo[
'userId'] ) {
455 $info = parent::getDeduplicationInfo();
456 unset( $info[
'causeAction'] );
457 unset( $info[
'causeAgent'] );
458 if ( is_array( $info[
'params'] ) ) {
461 if ( isset( $info[
'params'][
'pages'] ) ) {
462 unset( $info[
'namespace'] );
463 unset( $info[
'title'] );
471 if ( !empty( $this->params[
'recursive'] ) ) {
473 } elseif ( isset( $this->params[
'pages'] ) ) {
474 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.
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 rerendered wiki pages.
getDeduplicationInfo()
Subclasses may need to override this to make duplication detection work.
runForTitle(PageIdentity $pageIdentity)
static newDynamic(PageIdentity $page, array $params)
static newPrioritized(PageIdentity $page, array $params)
__construct(PageIdentity $page, array $params)
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.
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
loadPageData( $from='fromdb')
Load the object from a given source by title.
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