Allow REST API POST handlers to opt out of mandatory sqlite locking
This is a follow up to T93097, which worked around a limitation of SQLite 3.8+ which prevents it from upgrading a read transaction to a write transaction. Our heuristic is that HTTP POST requests are going to eventually become DB writes, and so we take the SQLite lock early. However, Parsoid POST requests don't have any side effects on the DB, and taking the write lock causes deadlocks during VE saves: the POST to action=visualeditoredit conflicts with the POST to the recursive request to the Parsoid REST API. We can't use a GET request for these requests without hitting query-length limits. This patch allows REST API calls to set the `X-MediaWiki-Read` header to opt-out of the SQLite obligatory lock. This avoids the deadlock, while still allowing the API call to use a POST and avoid query length limits. Bug: T259685 Change-Id: If37dc890a24a45c3a914e310b5b5bf625965e9e6
This commit is contained in:
parent
631d7e45ef
commit
b75ac3953e
2 changed files with 25 additions and 4 deletions
|
|
@ -26,6 +26,8 @@ class EntryPoint {
|
|||
private $context;
|
||||
/** @var CorsUtils */
|
||||
private $cors;
|
||||
/** @var ?RequestInterface */
|
||||
private static $mainRequest;
|
||||
|
||||
/**
|
||||
* @param IContextSource $context
|
||||
|
|
@ -77,6 +79,19 @@ class EntryPoint {
|
|||
) )->setCors( $cors );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?RequestInterface The RequestInterface object used by this entry point.
|
||||
*/
|
||||
public static function getMainRequest(): ?RequestInterface {
|
||||
if ( self::$mainRequest === null ) {
|
||||
$conf = MediaWikiServices::getInstance()->getMainConfig();
|
||||
self::$mainRequest = new RequestFromGlobals( [
|
||||
'cookiePrefix' => $conf->get( 'CookiePrefix' )
|
||||
] );
|
||||
}
|
||||
return self::$mainRequest;
|
||||
}
|
||||
|
||||
public static function main() {
|
||||
// URL safety checks
|
||||
global $wgRequest;
|
||||
|
|
@ -91,10 +106,6 @@ class EntryPoint {
|
|||
$services = MediaWikiServices::getInstance();
|
||||
$conf = $services->getMainConfig();
|
||||
|
||||
$request = new RequestFromGlobals( [
|
||||
'cookiePrefix' => $conf->get( 'CookiePrefix' )
|
||||
] );
|
||||
|
||||
$responseFactory = new ResponseFactory( self::getTextFormatters( $services ) );
|
||||
|
||||
$cors = new CorsUtils(
|
||||
|
|
@ -105,6 +116,8 @@ class EntryPoint {
|
|||
$context->getUser()
|
||||
);
|
||||
|
||||
$request = self::getMainRequest();
|
||||
|
||||
$router = self::createRouter( $context, $request, $responseFactory, $cors );
|
||||
|
||||
$entryPoint = new self(
|
||||
|
|
|
|||
|
|
@ -182,6 +182,14 @@ abstract class MWLBFactory {
|
|||
// See https://www.sqlite.org/lang_transaction.html
|
||||
// See https://www.sqlite.org/lockingv3.html#shared_lock
|
||||
$isHttpRead = in_array( $httpMethod, [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
|
||||
if ( MW_ENTRY_POINT === 'rest' && !$isHttpRead ) {
|
||||
// Hack to support some re-entrant invocations using sqlite
|
||||
// See: T259685
|
||||
$request = \MediaWiki\Rest\EntryPoint::getMainRequest();
|
||||
if ( $request->hasHeader( 'X-MediaWiki-Read' ) ) {
|
||||
$isHttpRead = true;
|
||||
}
|
||||
}
|
||||
$server += [
|
||||
'dbDirectory' => $options->get( 'SQLiteDataDir' ),
|
||||
'trxMode' => $isHttpRead ? 'DEFERRED' : 'IMMEDIATE'
|
||||
|
|
|
|||
Loading…
Reference in a new issue