further enhanced error handling + added retries in case of parallel writes
This commit is contained in:
59
db_init.php
59
db_init.php
@ -140,18 +140,37 @@ function qdb_apply_migrations(PDO $pdo, string $schemaSql): bool {
|
||||
return $changed;
|
||||
}
|
||||
|
||||
function qdb_acquire_lock() {
|
||||
/**
|
||||
* Exclusive lock with short retries (non-blocking attempts). Avoids hung PHP workers
|
||||
* when two writes overlap (app upload + website edit).
|
||||
*/
|
||||
function qdb_flock_exclusive_with_retry($lockFp, int $maxAttempts = 12, int $sleepMicros = 75000): void
|
||||
{
|
||||
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
|
||||
if (flock($lockFp, LOCK_EX | LOCK_NB)) {
|
||||
return;
|
||||
}
|
||||
if ($attempt < $maxAttempts - 1) {
|
||||
usleep($sleepMicros);
|
||||
}
|
||||
}
|
||||
throw new Exception('Could not acquire lock');
|
||||
}
|
||||
|
||||
function qdb_open_writable_lock()
|
||||
{
|
||||
$lockFp = fopen(QDB_LOCK, 'c');
|
||||
if ($lockFp === false) {
|
||||
throw new Exception('Could not open lock file');
|
||||
}
|
||||
if (!flock($lockFp, LOCK_EX)) {
|
||||
fclose($lockFp);
|
||||
throw new Exception('Could not acquire lock');
|
||||
}
|
||||
qdb_flock_exclusive_with_retry($lockFp);
|
||||
return $lockFp;
|
||||
}
|
||||
|
||||
function qdb_acquire_lock() {
|
||||
return qdb_open_writable_lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the master DB file into a temp SQLite file, or create a fresh one
|
||||
* from schema.sql if no DB exists yet.
|
||||
@ -171,12 +190,7 @@ function qdb_open(bool $writable = false): array {
|
||||
|
||||
$lockFp = null;
|
||||
if ($writable) {
|
||||
$lockFp = fopen(QDB_LOCK, 'c');
|
||||
if ($lockFp === false) throw new Exception("Could not open lock file");
|
||||
if (!flock($lockFp, LOCK_EX)) {
|
||||
fclose($lockFp);
|
||||
throw new Exception("Could not acquire lock");
|
||||
}
|
||||
$lockFp = qdb_open_writable_lock();
|
||||
}
|
||||
|
||||
$tmpDb = tempnam(sys_get_temp_dir(), 'qdb_');
|
||||
@ -289,33 +303,26 @@ function qdb_discard(string $tmpDb, $lockFp): void {
|
||||
*
|
||||
* @return array{0: PDO, 1: string, 2: resource|null}|null
|
||||
*/
|
||||
function qdb_open_read_or_fail(): ?array {
|
||||
/**
|
||||
* @return array{0: PDO, 1: string, 2: resource|null}
|
||||
*/
|
||||
function qdb_open_read_or_fail(): array {
|
||||
try {
|
||||
return qdb_open(false);
|
||||
} catch (Throwable $e) {
|
||||
if (function_exists('qdb_api_log_note_exception')) {
|
||||
qdb_api_log_note_exception($e);
|
||||
}
|
||||
require_once __DIR__ . '/lib/errors.php';
|
||||
error_log('qdb_open_read: ' . $e->getMessage());
|
||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Database read'), 500);
|
||||
qdb_emit_api_error($e, 'Database read');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a writable DB connection; on failure logs and returns a JSON error response.
|
||||
*
|
||||
* @return array{0: PDO, 1: string, 2: resource|null}|null
|
||||
* @return array{0: PDO, 1: string, 2: resource|null}
|
||||
*/
|
||||
function qdb_open_write_or_fail(): ?array {
|
||||
function qdb_open_write_or_fail(): array {
|
||||
try {
|
||||
return qdb_open(true);
|
||||
} catch (Throwable $e) {
|
||||
if (function_exists('qdb_api_log_note_exception')) {
|
||||
qdb_api_log_note_exception($e);
|
||||
}
|
||||
require_once __DIR__ . '/lib/errors.php';
|
||||
error_log('qdb_open_write: ' . $e->getMessage());
|
||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Database write'), 500);
|
||||
qdb_emit_api_error($e, 'Database write');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user