mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Unify SQL timeouts
- Exec and lock timeouts now apply to MySQL - Lock timeout now applies to PostgreSQL - SQLite now uses a generic lock timeout setting which applies to all
This commit is contained in:
parent
bc8d443d84
commit
8ea1df920a
7 changed files with 46 additions and 20 deletions
|
@ -65,6 +65,7 @@ abstract class AbstractException extends \Exception {
|
||||||
"Conf/Exception.fileCorrupt" => 10306,
|
"Conf/Exception.fileCorrupt" => 10306,
|
||||||
"Conf/Exception.typeMismatch" => 10311,
|
"Conf/Exception.typeMismatch" => 10311,
|
||||||
"Conf/Exception.semanticMismatch" => 10312,
|
"Conf/Exception.semanticMismatch" => 10312,
|
||||||
|
"Conf/Exception.ambiguousDefault" => 10313,
|
||||||
"User/Exception.functionNotImplemented" => 10401,
|
"User/Exception.functionNotImplemented" => 10401,
|
||||||
"User/Exception.doesNotExist" => 10402,
|
"User/Exception.doesNotExist" => 10402,
|
||||||
"User/Exception.alreadyExists" => 10403,
|
"User/Exception.alreadyExists" => 10403,
|
||||||
|
|
38
lib/Conf.php
38
lib/Conf.php
|
@ -21,16 +21,16 @@ class Conf {
|
||||||
public $dbDriver = "sqlite3";
|
public $dbDriver = "sqlite3";
|
||||||
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
|
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
|
||||||
public $dbAutoUpdate = true;
|
public $dbAutoUpdate = true;
|
||||||
/** @var \DateInterval Number of seconds to wait before returning a timeout error when connecting to a database (zero waits forever; not applicable to SQLite) */
|
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when connecting to a database (null waits forever; not applicable to SQLite) */
|
||||||
public $dbTimeoutConnect = 5.0;
|
public $dbTimeoutConnect = 5.0;
|
||||||
/** @var \DateInterval Number of seconds to wait before returning a timeout error when executing a database operation (zero waits forever; not applicable to SQLite) */
|
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when executing a database operation (null waits forever; not applicable to SQLite) */
|
||||||
public $dbTimeoutExec = 0.0;
|
public $dbTimeoutExec = null;
|
||||||
|
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when acquiring a database lock (null waits forever) */
|
||||||
|
public $dbTimeoutLock = 60.0;
|
||||||
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
|
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
|
||||||
public $dbSQLite3File = null;
|
public $dbSQLite3File = null;
|
||||||
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
|
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
|
||||||
public $dbSQLite3Key = "";
|
public $dbSQLite3Key = "";
|
||||||
/** @var \DateInterval Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
|
|
||||||
public $dbSQLite3Timeout = 60.0;
|
|
||||||
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
|
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
|
||||||
public $dbPostgreSQLHost = "";
|
public $dbPostgreSQLHost = "";
|
||||||
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
|
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
|
||||||
|
@ -109,6 +109,11 @@ class Conf {
|
||||||
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
|
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
|
||||||
public $httpOriginsDenied = "";
|
public $httpOriginsDenied = "";
|
||||||
|
|
||||||
|
### OBSOLETE SETTINGS
|
||||||
|
|
||||||
|
/** @var \DateInterval|null (OBSOLETE) Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
|
||||||
|
public $dbSQLite3Timeout = null; // previously 60.0
|
||||||
|
|
||||||
const TYPE_NAMES = [
|
const TYPE_NAMES = [
|
||||||
Value::T_BOOL => "boolean",
|
Value::T_BOOL => "boolean",
|
||||||
Value::T_STRING => "string",
|
Value::T_STRING => "string",
|
||||||
|
@ -116,6 +121,12 @@ class Conf {
|
||||||
VALUE::T_INT => "integer",
|
VALUE::T_INT => "integer",
|
||||||
Value::T_INTERVAL => "interval",
|
Value::T_INTERVAL => "interval",
|
||||||
];
|
];
|
||||||
|
const EXPECTED_TYPES = [
|
||||||
|
'dbTimeoutExec' => "double",
|
||||||
|
'dbTimeoutLock' => "double",
|
||||||
|
'dbTimeoutConnect' => "double",
|
||||||
|
'dbSQLite3Timeout' => "double",
|
||||||
|
];
|
||||||
|
|
||||||
protected static $types = [];
|
protected static $types = [];
|
||||||
|
|
||||||
|
@ -261,26 +272,28 @@ class Conf {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function propertyImport(string $key, $value, string $file = "") {
|
protected function propertyImport(string $key, $value, string $file = "") {
|
||||||
|
$typeName = static::$types[$key]['name'] ?? "mixed";
|
||||||
|
$typeConst = static::$types[$key]['const'] ?? Value::T_MIXED;
|
||||||
|
$nullable = (int) (bool) (static::$types[$key]['const'] & Value::M_NULL);
|
||||||
try {
|
try {
|
||||||
$typeName = static::$types[$key]['name'] ?? "mixed";
|
|
||||||
$typeConst = static::$types[$key]['const'] ?? Value::T_MIXED;
|
|
||||||
if ($typeName === "\\DateInterval") {
|
if ($typeName === "\\DateInterval") {
|
||||||
// date intervals have special handling: if the existing value (ultimately, the default value)
|
// date intervals have special handling: if the existing value (ultimately, the default value)
|
||||||
// is an integer or float, the new value should be imported as numeric. If the new value is a string
|
// is an integer or float, the new value should be imported as numeric. If the new value is a string
|
||||||
// it is first converted to an interval and then converted to the numeric type if necessary
|
// it is first converted to an interval and then converted to the numeric type if necessary
|
||||||
|
$mode = $nullable ? Value::M_STRICT | Value::M_NULL : Value::M_STRICT;
|
||||||
if (is_string($value)) {
|
if (is_string($value)) {
|
||||||
$value = Value::normalize($value, Value::T_INTERVAL | Value::M_STRICT);
|
$value = Value::normalize($value, Value::T_INTERVAL | $mode);
|
||||||
}
|
}
|
||||||
switch (gettype($this->$key)) {
|
switch (self::EXPECTED_TYPES[$key] ?? gettype($this->$key)) {
|
||||||
case "integer":
|
case "integer":
|
||||||
return Value::normalize($value, Value::T_INT | Value::M_STRICT);
|
return Value::normalize($value, Value::T_INT | $mode);
|
||||||
case "double":
|
case "double":
|
||||||
return Value::normalize($value, Value::T_FLOAT | Value::M_STRICT);
|
return Value::normalize($value, Value::T_FLOAT | $mode);
|
||||||
case "string":
|
case "string":
|
||||||
case "object":
|
case "object":
|
||||||
return $value;
|
return $value;
|
||||||
default:
|
default:
|
||||||
throw new ExceptionType("strictFailure"); // @codeCoverageIgnore
|
throw new Conf\Exception("ambiguousDefault", ['param' => $key]); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$value = Value::normalize($value, $typeConst);
|
$value = Value::normalize($value, $typeConst);
|
||||||
|
@ -303,7 +316,6 @@ class Conf {
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
} catch (ExceptionType $e) {
|
} catch (ExceptionType $e) {
|
||||||
$nullable = (int) (bool) (static::$types[$key] & Value::M_NULL);
|
|
||||||
$type = static::$types[$key]['const'] & ~(Value::M_STRICT | Value::M_DROP | Value::M_NULL | Value::M_ARRAY);
|
$type = static::$types[$key]['const'] & ~(Value::M_STRICT | Value::M_DROP | Value::M_NULL | Value::M_ARRAY);
|
||||||
throw new Conf\Exception("typeMismatch", ['param' => $key, 'type' => self::TYPE_NAMES[$type], 'file' => $file, 'nullable' => $nullable]);
|
throw new Conf\Exception("typeMismatch", ['param' => $key, 'type' => self::TYPE_NAMES[$type], 'file' => $file, 'nullable' => $nullable]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES";
|
const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES";
|
||||||
const TRANSACTIONAL_LOCKS = false;
|
const TRANSACTIONAL_LOCKS = false;
|
||||||
|
|
||||||
/** @var \mysql */
|
/** @var \mysqli */
|
||||||
protected $db;
|
protected $db;
|
||||||
protected $transStart = 0;
|
protected $transStart = 0;
|
||||||
protected $packetSize = 4194304;
|
protected $packetSize = 4194304;
|
||||||
|
@ -48,7 +48,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return [
|
return [
|
||||||
"SET sql_mode = '".self::SQL_MODE."'",
|
"SET sql_mode = '".self::SQL_MODE."'",
|
||||||
"SET time_zone = '+00:00'",
|
"SET time_zone = '+00:00'",
|
||||||
"SET lock_wait_timeout = 1",
|
"SET lock_wait_timeout = ".self::lockTimeout(),
|
||||||
|
"SET max_execution_time = ".ceil(Arsse::$conf->dbTimeoutExec * 1000),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
try {
|
try {
|
||||||
$this->exec("SET lock_wait_timeout = 1; LOCK TABLES $tables");
|
$this->exec("SET lock_wait_timeout = 1; LOCK TABLES $tables");
|
||||||
} finally {
|
} finally {
|
||||||
$this->exec("SET lock_wait_timeout = 60");
|
$this->exec("SET lock_wait_timeout = ".self::lockTimeout());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -141,6 +142,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static function lockTimeout(): int {
|
||||||
|
return (int) max(min(ceil(Arsse::$conf->dbTimeoutLock ?? 31536000), 31536000), 1);
|
||||||
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
if (isset($this->db)) {
|
if (isset($this->db)) {
|
||||||
$this->db->close();
|
$this->db->close();
|
||||||
|
@ -157,7 +162,9 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) {
|
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) {
|
||||||
$this->db = @new \mysqli($host, $user, $password, $db, $port, $socket);
|
$this->db = mysqli_init();
|
||||||
|
$this->db->options(\MYSQLI_OPT_CONNECT_TIMEOUT, ceil(Arsse::$conf->dbTimeoutConnect));
|
||||||
|
@$this->db->real_connect($host, $user, $password, $db, $port, $socket);
|
||||||
if ($this->db->connect_errno) {
|
if ($this->db->connect_errno) {
|
||||||
list($excClass, $excMsg, $excData) = $this->buildConnectionException($this->db->connect_errno, $this->db->connect_error);
|
list($excClass, $excMsg, $excData) = $this->buildConnectionException($this->db->connect_errno, $this->db->connect_error);
|
||||||
throw new $excClass($excMsg, $excData);
|
throw new $excClass($excMsg, $excData);
|
||||||
|
|
|
@ -74,11 +74,13 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function makeSetupQueries(string $schema = ""): array {
|
public static function makeSetupQueries(string $schema = ""): array {
|
||||||
$timeout = ceil(Arsse::$conf->dbTimeoutExec * 1000);
|
$timeExec = is_null(Arsse::$conf->dbTimeoutExec) ? 0 : ceil(max(Arsse::$conf->dbTimeoutExec * 1000, 1));
|
||||||
|
$timeLock = is_null(Arsse::$conf->dbTimeoutLock) ? 0 : ceil(max(Arsse::$conf->dbTimeoutLock * 1000, 1));
|
||||||
$out = [
|
$out = [
|
||||||
"SET TIME ZONE UTC",
|
"SET TIME ZONE UTC",
|
||||||
"SET DateStyle = 'ISO, MDY'",
|
"SET DateStyle = 'ISO, MDY'",
|
||||||
"SET statement_timeout = '$timeout'",
|
"SET statement_timeout = '$timeExec'",
|
||||||
|
"SET lock_timeout = '$timeLock'",
|
||||||
];
|
];
|
||||||
if (strlen($schema) > 0) {
|
if (strlen($schema) > 0) {
|
||||||
$schema = '"'.str_replace('"', '""', $schema).'"';
|
$schema = '"'.str_replace('"', '""', $schema).'"';
|
||||||
|
|
|
@ -55,7 +55,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
throw new Exception("fileCorrupt", $dbFile);
|
throw new Exception("fileCorrupt", $dbFile);
|
||||||
}
|
}
|
||||||
// set the timeout
|
// set the timeout
|
||||||
$timeout = (int) ceil(Arsse::$conf->dbSQLite3Timeout * 1000);
|
$timeout = Arsse::$conf->dbSQLite3Timeout ?? Arsse::$conf->dbTimeoutLock; // old SQLite-specific timeout takes precedence
|
||||||
|
$timeout = is_null($timeout) ? PHP_INT_MAX : (int) ceil($timeout * 1000);
|
||||||
$this->setTimeout($timeout);
|
$this->setTimeout($timeout);
|
||||||
// set other initial options
|
// set other initial options
|
||||||
$this->exec("PRAGMA foreign_keys = yes");
|
$this->exec("PRAGMA foreign_keys = yes");
|
||||||
|
|
|
@ -74,6 +74,8 @@ return [
|
||||||
other {, or null}
|
other {, or null}
|
||||||
}',
|
}',
|
||||||
'Exception.JKingWeb/Arsse/Conf/Exception.semanticMismatch' => 'Configuration parameter "{param}" in file "{file}" is not a valid value. Consult the documentation for possible values',
|
'Exception.JKingWeb/Arsse/Conf/Exception.semanticMismatch' => 'Configuration parameter "{param}" in file "{file}" is not a valid value. Consult the documentation for possible values',
|
||||||
|
// indicates programming error
|
||||||
|
'Exception.JKingWeb/Arsse/Conf/Exception.ambiguousDefault' => 'Preferred type of configuration parameter "{param}" could not be inferred from its default value. The parameter must be added to the Conf::EXPECTED_TYPES array',
|
||||||
'Exception.JKingWeb/Arsse/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed',
|
'Exception.JKingWeb/Arsse/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed',
|
||||||
'Exception.JKingWeb/Arsse/Db/Exception.fileMissing' => 'Database file "{0}" does not exist',
|
'Exception.JKingWeb/Arsse/Db/Exception.fileMissing' => 'Database file "{0}" does not exist',
|
||||||
'Exception.JKingWeb/Arsse/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
|
'Exception.JKingWeb/Arsse/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
|
||||||
|
|
|
@ -19,6 +19,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
protected $setVersion;
|
protected $setVersion;
|
||||||
protected static $conf = [
|
protected static $conf = [
|
||||||
'dbTimeoutExec' => 0.5,
|
'dbTimeoutExec' => 0.5,
|
||||||
|
'dbTimeoutLock' => 0.001,
|
||||||
'dbSQLite3Timeout' => 0,
|
'dbSQLite3Timeout' => 0,
|
||||||
//'dbSQLite3File' => "(temporary file)",
|
//'dbSQLite3File' => "(temporary file)",
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue