further enhanced error handling + added retries in case of parallel writes

This commit is contained in:
2026-06-03 17:27:51 +02:00
parent d0daa7e937
commit fc84d55bd6
21 changed files with 184 additions and 383 deletions

View File

@ -1,5 +1,16 @@
<?php
/**
* Shared API error helpers for handlers and db_init.
*/
function qdb_is_lock_exception(Throwable $e): bool
{
$msg = $e->getMessage();
return str_contains($msg, 'Could not acquire lock')
|| str_contains($msg, 'Could not open lock file');
}
/**
* Map internal exceptions to safe, identifiable API error messages (details go to error_log).
*/
@ -7,8 +18,8 @@ function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operatio
{
$msg = $e->getMessage();
if (str_contains($msg, 'Could not acquire lock') || str_contains($msg, 'Could not open lock')) {
return 'Database is busy. Please try again in a moment.';
if (qdb_is_lock_exception($e)) {
return 'Database is busy because another update is in progress. Please try again in a moment.';
}
if (str_contains($msg, 'decrypt') || str_contains($msg, 'QDB_MASTER_KEY')) {
return 'Database configuration error. Contact the server administrator.';
@ -23,3 +34,50 @@ function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operatio
return $contextLabel . ' failed. If this persists, contact support.';
}
/**
* @return array{0: string, 1: string, 2: int} code, message, http status
*/
function qdb_api_error_for_exception(Throwable $e, string $contextLabel = 'Operation'): array
{
if (qdb_is_lock_exception($e)) {
return ['LOCKED', qdb_public_error_message($e, $contextLabel), 503];
}
return ['SERVER_ERROR', qdb_public_error_message($e, $contextLabel), 500];
}
/**
* Standard handler catch: rollback, discard DB temp file, log, JSON error (never returns).
*/
function qdb_handler_fail(
Throwable $e,
string $contextLabel,
?PDO $pdo = null,
?string $tmpDb = null,
$lockFp = null
): never {
if ($pdo !== null && $pdo->inTransaction()) {
try {
$pdo->rollBack();
} catch (Throwable) {
}
}
if ($tmpDb !== null && $lockFp !== null) {
require_once __DIR__ . '/../db_init.php';
qdb_discard($tmpDb, $lockFp);
}
qdb_emit_api_error($e, $contextLabel);
}
/**
* Log exception and exit with unified JSON error (never returns).
*/
function qdb_emit_api_error(Throwable $e, string $contextLabel): never
{
if (function_exists('qdb_api_log_note_exception')) {
qdb_api_log_note_exception($e);
}
error_log($contextLabel . ': ' . $e->getMessage());
[$code, $message, $status] = qdb_api_error_for_exception($e, $contextLabel);
json_error($code, $message, $status);
}