Allow setting max execution time to several special pages

These special pages and their counter-part APIs are:
 - Special:RecentChanges
 - Special:Watchlist
 - Special:Log
 - Special:Contributions

This also changes the way MAX_EXECUTION_TIME works from taking the value
as milliseconds, it takes seconds which is more intuitive for users.

Bug: T297708
Depends-On: I126e7181422d8da1a63afc3717faa4f72a687dd9
Change-Id: I3ff78751c3df3b6342f1865d35c2075f4415185d
This commit is contained in:
Amir Sarabadani 2021-12-14 10:08:51 +01:00
parent 72b63c75dc
commit 627c1c2c30
12 changed files with 52 additions and 4 deletions

View file

@ -2370,6 +2370,13 @@ $wgDatabaseReplicaLagWarning = 10;
*/
$wgDatabaseReplicaLagCritical = 30;
/**
* Max execution time for queries of several expensive special pages such as RecentChanges
* in milliseconds.
* @since 1.38
*/
$wgMaxExecutionTimeForExpensiveQueries = 0;
/**
* RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
* Use the SCHEMA_COMPAT_XXX flags. Supported values:

View file

@ -1877,7 +1877,8 @@ return [
$services->getCommentStore(),
$services->getWatchedItemStore(),
$services->getHookContainer(),
$services->getMainConfig()->get( 'WatchlistExpiry' )
$services->getMainConfig()->get( 'WatchlistExpiry' ),
$services->getMainConfig()->get( 'MaxExecutionTimeForExpensiveQueries' )
);
},

View file

@ -259,6 +259,11 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addOption( 'STRAIGHT_JOIN' );
}
$this->addOption(
'MAX_EXECUTION_TIME',
$this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' )
);
$count = 0;
$res = $this->select( __METHOD__ );

View file

@ -416,6 +416,10 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$this->addOption(
'MAX_EXECUTION_TIME',
$this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' )
);
$hookData = [];
$count = 0;

View file

@ -486,6 +486,10 @@ class ApiQueryUserContribs extends ApiQueryBase {
$this->addWhere( '1=0' );
}
}
$this->addOption(
'MAX_EXECUTION_TIME',
$this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' )
);
}
/**

View file

@ -388,6 +388,17 @@ class SelectQueryBuilder extends JoinGroupBase {
return $this;
}
/**
* Set MAX_EXECUTION_TIME for queries.
*
* @param int $time maximum allowed time in milliseconds
* @return $this
*/
public function setMaxExecutionTime( int $time ) {
$this->options['MAX_EXECUTION_TIME'] = $time;
return $this;
}
/**
* Add a GROUP BY clause. May be either an SQL fragment string naming a
* field or expression to group by, or an array of such SQL fragments.

View file

@ -380,6 +380,8 @@ class LogPager extends ReverseChronologicalPager {
$options[] = 'STRAIGHT_JOIN';
}
$options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
$info = [
'tables' => $tables,
'fields' => $fields,

View file

@ -398,6 +398,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
// MediaWiki 1.26 this used to use the plus operator instead, which meant
// that extensions weren't able to change these conditions
$query_options = array_merge( $orderByAndLimit, $query_options );
$query_options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
$rows = $dbr->select(
$tables,
$fields,

View file

@ -249,7 +249,6 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
' ORDER BY rc_timestamp DESC';
$sql = $dbr->limitResult( $sql, $limit, false );
}
return $dbr->query( $sql, __METHOD__ );
}

View file

@ -459,6 +459,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
// array_merge() is used intentionally here so that hooks can, should
// they so desire, override the ORDER BY / LIMIT condition(s)
$query_options = array_merge( $orderByAndLimit, $query_options );
$query_options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
return $dbr->select(
$tables,

View file

@ -276,6 +276,7 @@ class ContribsPager extends RangeChronologicalPager {
$order
);
$options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
/*
* This hook will allow extensions to add in additional queries, so they can get their data
* in My Contributions as well. Extensions should append their results to the $data array.

View file

@ -78,18 +78,25 @@ class WatchedItemQueryService {
*/
private $expiryEnabled;
/**
* @var int Max query execution time
*/
private $maxQueryExecutionTime;
public function __construct(
ILoadBalancer $loadBalancer,
CommentStore $commentStore,
WatchedItemStoreInterface $watchedItemStore,
HookContainer $hookContainer,
bool $expiryEnabled = false
bool $expiryEnabled = false,
int $maxQueryExecutionTime = 0
) {
$this->loadBalancer = $loadBalancer;
$this->commentStore = $commentStore;
$this->watchedItemStore = $watchedItemStore;
$this->hookRunner = new HookRunner( $hookContainer );
$this->expiryEnabled = $expiryEnabled;
$this->maxQueryExecutionTime = $maxQueryExecutionTime;
}
/**
@ -712,7 +719,9 @@ class WatchedItemQueryService {
if ( array_key_exists( 'limit', $options ) ) {
$dbOptions['LIMIT'] = (int)$options['limit'] + 1;
}
if ( $this->maxQueryExecutionTime ) {
$dbOptions['MAX_EXECUTION_TIME'] = $this->maxQueryExecutionTime;
}
return $dbOptions;
}
@ -730,6 +739,9 @@ class WatchedItemQueryService {
if ( array_key_exists( 'limit', $options ) ) {
$dbOptions['LIMIT'] = (int)$options['limit'];
}
if ( $this->maxQueryExecutionTime ) {
$dbOptions['MAX_EXECUTION_TIME'] = $this->maxQueryExecutionTime;
}
return $dbOptions;
}