From bfa2cf424672c3084f2d4c4771dd56cc38167ef7 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Thu, 12 Oct 2017 01:01:58 +0100 Subject: [PATCH] SpecialRunJobs: Use MediaWiki's built-in async/post-send mode Disabling OutputPage is fine, but disabling MediaWiki's own output handling from MediaWiki.php as well (with ignore_user_abort and ob_flush) made this code incompatible with becoming an API module. Make use of DeferredUpdates instead, MediaWiki's built-in post-send mechanism. Bug: T175146 Change-Id: Ia131341d447fd6501a070da89cd3a2af470e0f7d --- includes/specials/SpecialRunJobs.php | 63 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/includes/specials/SpecialRunJobs.php b/includes/specials/SpecialRunJobs.php index 73fe70f07b..375694be08 100644 --- a/includes/specials/SpecialRunJobs.php +++ b/includes/specials/SpecialRunJobs.php @@ -39,17 +39,21 @@ class SpecialRunJobs extends UnlistedSpecialPage { public function execute( $par = '' ) { $this->getOutput()->disable(); + if ( wfReadOnly() ) { wfHttpError( 423, 'Locked', 'Wiki is in read-only mode.' ); return; - } elseif ( !$this->getRequest()->wasPosted() ) { + } + + // Validate request method + if ( !$this->getRequest()->wasPosted() ) { wfHttpError( 400, 'Bad Request', 'Request must be POSTed.' ); return; } + // Validate request parameters $optional = [ 'maxjobs' => 0, 'maxtime' => 30, 'type' => false, 'async' => true ]; $required = array_flip( [ 'title', 'tasks', 'signature', 'sigexpiry' ] ); - $params = array_intersect_key( $this->getRequest()->getValues(), $required + $optional ); $missing = array_diff_key( $required, $params ); if ( count( $missing ) ) { @@ -59,11 +63,11 @@ class SpecialRunJobs extends UnlistedSpecialPage { return; } + // Validate request signature $squery = $params; unset( $squery['signature'] ); $correctSignature = self::getQuerySignature( $squery, $this->getConfig()->get( 'SecretKey' ) ); $providedSignature = $params['signature']; - $verified = is_string( $providedSignature ) && hash_equals( $correctSignature, $providedSignature ); if ( !$verified || $params['sigexpiry'] < time() ) { @@ -75,39 +79,34 @@ class SpecialRunJobs extends UnlistedSpecialPage { $params += $optional; if ( $params['async'] ) { - // Client will usually disconnect before checking the response, - // but it needs to know when it is safe to disconnect. Until this - // reaches ignore_user_abort(), it is not safe as the jobs won't run. - ignore_user_abort( true ); // jobs may take a bit of time // HTTP 202 Accepted HttpStatus::header( 202 ); - ob_flush(); - flush(); - // Once the client receives this response, it can disconnect - set_error_handler( function ( $errno, $errstr ) { - if ( strpos( $errstr, 'Cannot modify header information' ) !== false ) { - return true; // bug T115413 - } - // Delegate unhandled errors to the default MediaWiki handler - // so that fatal errors get proper logging (T89169) - return call_user_func_array( - 'MWExceptionHandler::handleError', func_get_args() - ); - } ); + // Clients are meant to disconnect without waiting for the full response. + // Let the page output happen before the jobs start, so that clients know it's + // safe to disconnect. MediaWiki::preOutputCommit() calls ignore_user_abort() + // or similar to make sure we stay alive to run the deferred update. + DeferredUpdates::addUpdate( + new TransactionRoundDefiningUpdate( + function () use ( $params ) { + $this->doRun( $params ); + }, + __METHOD__ + ), + DeferredUpdates::POSTSEND + ); + } else { + $this->doRun( $params ); + print "Done\n"; } + } - // Do all of the specified tasks... - if ( in_array( 'jobs', explode( '|', $params['tasks'] ) ) ) { - $runner = new JobRunner( LoggerFactory::getInstance( 'runJobs' ) ); - $runner->run( [ - 'type' => $params['type'], - 'maxJobs' => $params['maxjobs'] ? $params['maxjobs'] : 1, - 'maxTime' => $params['maxtime'] ? $params['maxjobs'] : 30 - ] ); - if ( !$params['async'] ) { - print "Done\n"; - } - } + protected function doRun( array $params ) { + $runner = new JobRunner( LoggerFactory::getInstance( 'runJobs' ) ); + $runner->run( [ + 'type' => $params['type'], + 'maxJobs' => $params['maxjobs'] ?: 1, + 'maxTime' => $params['maxtime'] ?: 30 + ] ); } /** -- 2.20.1