From 3638695419bd7f3d82a78eff888a1353601b08ab Mon Sep 17 00:00:00 2001 From: Matthew Flaschen Date: Mon, 18 Sep 2017 07:38:59 -0400 Subject: [PATCH] Detect query timeouts and throw a specific exception Throw DBQueryTimeoutError if a database query error is detected to be a timeout. Only DatabaseMysqlBase has been updated here. This is a subclass of DBQueryError, so existing catch'es will work. Bug: T175775 Change-Id: I4749dc33ad530d9b22504f02106b1ca49e8eb167 --- autoload.php | 1 + includes/libs/rdbms/database/Database.php | 20 +++++++++- .../libs/rdbms/database/DatabaseMysqlBase.php | 4 ++ .../libs/rdbms/exception/DBQueryError.php | 27 +++++++------ .../rdbms/exception/DBQueryTimeoutError.php | 38 +++++++++++++++++++ 5 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 includes/libs/rdbms/exception/DBQueryTimeoutError.php diff --git a/autoload.php b/autoload.php index 61fd192f0d..4dd5f12876 100644 --- a/autoload.php +++ b/autoload.php @@ -1636,6 +1636,7 @@ $wgAutoloadLocalClasses = [ 'Wikimedia\\Rdbms\\DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBExpectedError.php', 'Wikimedia\\Rdbms\\DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php', 'Wikimedia\\Rdbms\\DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryError.php', + 'Wikimedia\\Rdbms\\DBQueryTimeoutError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryTimeoutError.php', 'Wikimedia\\Rdbms\\DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBReadOnlyError.php', 'Wikimedia\\Rdbms\\DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBReplicationWaitError.php', 'Wikimedia\\Rdbms\\DBTransactionError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionError.php', diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index c9040928b0..e7417eb84a 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -1130,6 +1130,19 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } } + /** + * Checks whether the cause of the error is detected to be a timeout. + * + * It returns false by default, and not all engines support detecting this yet. + * If this returns false, it will be treated as a generic query error. + * + * @param string $error Error text + * @param int $errno Error number + */ + protected function wasQueryTimeout( $error, $errno ) { + return false; + } + public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { if ( $this->ignoreErrors() || $tempIgnore ) { $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" ); @@ -1146,7 +1159,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware ] ) ); $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" ); - throw new DBQueryError( $this, $error, $errno, $sql, $fname ); + $wasQueryTimeout = $this->wasQueryTimeout( $error, $errno ); + if ( $wasQueryTimeout ) { + throw new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname ); + } else { + throw new DBQueryError( $this, $error, $errno, $sql, $fname ); + } } } diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php index 3c4cda5552..5312a3d032 100644 --- a/includes/libs/rdbms/database/DatabaseMysqlBase.php +++ b/includes/libs/rdbms/database/DatabaseMysqlBase.php @@ -486,6 +486,10 @@ abstract class DatabaseMysqlBase extends Database { */ abstract protected function mysqlError( $conn = null ); + protected function wasQueryTimeout( $error, $errno ) { + return $errno == 2062; + } + /** * @param string $table * @param array $uniqueIndexes diff --git a/includes/libs/rdbms/exception/DBQueryError.php b/includes/libs/rdbms/exception/DBQueryError.php index a8ea3ade42..e6870a7e2e 100644 --- a/includes/libs/rdbms/exception/DBQueryError.php +++ b/includes/libs/rdbms/exception/DBQueryError.php @@ -40,19 +40,22 @@ class DBQueryError extends DBExpectedError { * @param int|string $errno * @param string $sql * @param string $fname + * @param string $message Optional message, intended for subclases (optional) */ - public function __construct( IDatabase $db, $error, $errno, $sql, $fname ) { - if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) { - $message = "A connection error occured. \n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - } else { - $message = "A database query error has occurred. Did you forget to run " . - "your application's database schema updater after upgrading? \n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; + public function __construct( IDatabase $db, $error, $errno, $sql, $fname, $message = null ) { + if ( $message === null ) { + if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) { + $message = "A connection error occured. \n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; + } else { + $message = "A database query error has occurred. Did you forget to run " . + "your application's database schema updater after upgrading? \n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; + } } parent::__construct( $db, $message ); diff --git a/includes/libs/rdbms/exception/DBQueryTimeoutError.php b/includes/libs/rdbms/exception/DBQueryTimeoutError.php new file mode 100644 index 0000000000..ea91d9550e --- /dev/null +++ b/includes/libs/rdbms/exception/DBQueryTimeoutError.php @@ -0,0 +1,38 @@ +