mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Improved timeout handling for both SQlite and PostgreSQL
This commit is contained in:
parent
4e444fd86c
commit
736a8c9d0c
6 changed files with 58 additions and 13 deletions
|
@ -19,12 +19,16 @@ class Conf {
|
|||
public $dbDriver = Db\SQLite3\Driver::class;
|
||||
/** @var boolean Whether to attempt to automatically update the database when updated to a new version with schema changes */
|
||||
public $dbAutoUpdate = true;
|
||||
/** @var float Number of seconds to wait before returning a timeout error when connecting to a database (zero waits forever; not applicable to SQLite) */
|
||||
public $dbTimeoutConnect = 5.0;
|
||||
/** @var float Number of seconds to wait before returning a timeout error when executing a database operation (zero waits forever; not applicable to SQLite) */
|
||||
public $dbTimeoutExec = 0.0;
|
||||
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
|
||||
public $dbSQLite3File = null;
|
||||
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
|
||||
public $dbSQLite3Key = "";
|
||||
/** @var integer Number of seconds for SQLite to wait before returning a timeout error when writing to the database */
|
||||
public $dbSQLite3Timeout = 60;
|
||||
/** @var float 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) */
|
||||
public $dbPostgreSQLHost = "";
|
||||
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
|
||||
|
|
|
@ -19,6 +19,9 @@ trait PDOError {
|
|||
case "23000":
|
||||
case "23502":
|
||||
return [ExceptionInput::class, "constraintViolation", $err[2]];
|
||||
case "55P03":
|
||||
case "57014":
|
||||
return [ExceptionTimeout::class, 'general', $err[2]];
|
||||
case "HY000":
|
||||
// engine-specific errors
|
||||
switch ($this->db->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
|
||||
|
|
|
@ -35,6 +35,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
$base = [
|
||||
'client_encoding' => "UTF8",
|
||||
'application_name' => "arsse",
|
||||
'connect_timeout' => (string) ceil(Arsse::$conf->dbTimeoutConnect ?? 0),
|
||||
];
|
||||
$out = [];
|
||||
if ($service != "") {
|
||||
|
@ -66,9 +67,11 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
}
|
||||
|
||||
public static function makeSetupQueries(string $schema = ""): array {
|
||||
$timeout = ceil(Arsse::$conf->dbTimeoutExec * 1000);
|
||||
$out = [
|
||||
"SET TIME ZONE UTC",
|
||||
"SET DateStyle = 'ISO, MDY'"
|
||||
"SET DateStyle = 'ISO, MDY'",
|
||||
"SET statement_timeout = '$timeout'",
|
||||
];
|
||||
if (strlen($schema) > 0) {
|
||||
$out[] = 'SET search_path = \'"'.str_replace('"', '""', $schema).'", "$user", public\'';
|
||||
|
@ -100,8 +103,30 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
return $this->query("SELECT pg_encoding_to_char(encoding) from pg_database where datname = current_database()")->getValue() == "UTF8";
|
||||
}
|
||||
|
||||
public function savepointCreate(bool $lock = false): int {
|
||||
if (!$this->transDepth) {
|
||||
$this->exec("BEGIN TRANSACTION");
|
||||
}
|
||||
return parent::savepointCreate($lock);
|
||||
}
|
||||
|
||||
public function savepointRelease(int $index = null): bool {
|
||||
$out = parent::savepointUndo($index);
|
||||
if ($out && !$this->transDepth) {
|
||||
$this->exec("COMMIT TRANSACTION");
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function savepointUndo(int $index = null): bool {
|
||||
$out = parent::savepointUndo($index);
|
||||
if ($out && !$this->transDepth) {
|
||||
$this->exec("ROLLBACK TRANSACTION");
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected function lock(): bool {
|
||||
$this->exec("BEGIN TRANSACTION");
|
||||
if ($this->schemaVersion()) {
|
||||
$this->exec("LOCK TABLE arsse_meta IN EXCLUSIVE MODE NOWAIT");
|
||||
}
|
||||
|
|
|
@ -27,13 +27,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
}
|
||||
// if no database file is specified in the configuration, use a suitable default
|
||||
$dbFile = $dbFile ?? Arsse::$conf->dbSQLite3File ?? \JKingWeb\Arsse\BASE."arsse.db";
|
||||
$timeout = Arsse::$conf->dbSQLite3Timeout * 1000;
|
||||
try {
|
||||
$this->makeConnection($dbFile, Arsse::$conf->dbSQLite3Key);
|
||||
// set the timeout; parameters are not allowed for pragmas, but this usage should be safe
|
||||
$this->exec("PRAGMA busy_timeout = $timeout");
|
||||
// set other initial options
|
||||
$this->exec("PRAGMA foreign_keys = yes");
|
||||
} catch (\Throwable $e) {
|
||||
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be
|
||||
$files = [
|
||||
|
@ -55,6 +50,11 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
// otherwise the database is probably corrupt
|
||||
throw new Exception("fileCorrupt", $dbFile);
|
||||
}
|
||||
// set the timeout
|
||||
$timeout = (int) ceil((Arsse::$conf->dbSQLite3Timeout ?? 0) * 1000);
|
||||
$this->setTimeout($timeout);
|
||||
// set other initial options
|
||||
$this->exec("PRAGMA foreign_keys = yes");
|
||||
}
|
||||
|
||||
public static function requirementsMet(): bool {
|
||||
|
@ -67,6 +67,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
$this->db->enableExceptions(true);
|
||||
}
|
||||
|
||||
protected function setTimeout(int $msec) {
|
||||
$this->exec("PRAGMA busy_timeout = $msec");
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
try {
|
||||
$this->db->close();
|
||||
|
@ -157,7 +161,13 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
}
|
||||
|
||||
protected function lock(): bool {
|
||||
$this->exec("BEGIN EXCLUSIVE TRANSACTION");
|
||||
$timeout = (int) $this->query("PRAGMA busy_timeout")->getValue();
|
||||
$this->setTimeout(0);
|
||||
try {
|
||||
$this->exec("BEGIN EXCLUSIVE TRANSACTION");
|
||||
} finally {
|
||||
$this->setTimeout($timeout);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,14 +6,17 @@
|
|||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\TestCase\Db\PostgreSQL;
|
||||
|
||||
use JKingWeb\Arsse\Arsse;
|
||||
use JKingWeb\Arsse\Db\PostgreSQL\Driver;
|
||||
|
||||
/**
|
||||
* @covers \JKingWeb\Arsse\Db\PostgreSQL\Driver<extended> */
|
||||
class TestDriver extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||
class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||
/** @dataProvider provideConnectionStrings */
|
||||
public function testGenerateConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service, string $exp) {
|
||||
$postfix = "application_name='arsse' client_encoding='UTF8'";
|
||||
$this->setConf();
|
||||
$timeout = (string) ceil(Arsse::$conf->dbTimeoutConnect ?? 0);
|
||||
$postfix = "application_name='arsse' client_encoding='UTF8' connect_timeout='$timeout'";
|
||||
$act = Driver::makeConnectionString($pdo, $user, $pass, $db, $host, $port, $service);
|
||||
if ($act==$postfix) {
|
||||
$this->assertSame($exp, "");
|
|
@ -56,7 +56,7 @@
|
|||
<file>cases/Db/SQLite3PDO/TestDriver.php</file>
|
||||
<file>cases/Db/SQLite3PDO/TestUpdate.php</file>
|
||||
|
||||
<file>cases/Db/PostgreSQL/TestDriver.php</file>
|
||||
<file>cases/Db/PostgreSQL/TestCreation.php</file>
|
||||
</testsuite>
|
||||
<testsuite name="Database functions">
|
||||
<file>cases/Db/SQLite3/Database/TestMiscellany.php</file>
|
||||
|
|
Loading…
Reference in a new issue