Skip to content

Commit e8faf67

Browse files
committed
Merge branch 'feature/improvement' into develop
2 parents e5efc9d + e7890aa commit e8faf67

File tree

2 files changed

+75
-8
lines changed

2 files changed

+75
-8
lines changed

src/DBTransactionRetryHelper.php

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class DBTransactionRetryHelper
1919
* @throws QueryException
2020
* @throws Throwable
2121
*/
22-
public static function transactionWithRetry(callable $callback, int $maxRetries = 5, int $retryDelay = 5, string $logFileName = 'mysql-deadlocks-log'): mixed
22+
public static function transactionWithRetry(callable $callback, int $maxRetries = 3, int $retryDelay = 2, string $logFileName = 'mysql-deadlocks'): mixed
2323
{
2424
$attempt = 0;
2525
$throwable = null;
@@ -80,5 +80,64 @@ public static function transactionWithRetry(callable $callback, int $maxRetries
8080
}
8181
}
8282
}
83+
84+
// If we exit the loop without returning, throw a generic runtime exception
85+
throw new \RuntimeException('Transaction with retry exhausted after ' . $maxRetries . ' attempts.');
86+
}
87+
88+
protected static function isDeadlockOrSerializationError(QueryException $e): bool
89+
{
90+
// MySQL deadlock: driver error 1213; lock wait timeout: 1205 (often not retryable); SQLSTATE 40001 serialization failure
91+
$sqlState = $e->getCode(); // In Laravel, getCode often returns SQLSTATE (e.g., '40001')
92+
$driverErr = is_array($e->errorInfo ?? null) && isset($e->errorInfo[1]) ? $e->errorInfo[1] : null;
93+
94+
return ($sqlState === '40001')
95+
|| ($driverErr === 1213)
96+
|| ($sqlState === 1213) // in case driver bubbles numeric
97+
;
98+
}
99+
100+
protected static function buildLogContext(QueryException $e, int $attempt): array
101+
{
102+
$requestData = [
103+
'url' => null,
104+
'method' => null,
105+
'token' => null,
106+
'userId' => null,
107+
];
108+
109+
try {
110+
// Only access request() when available (HTTP context)
111+
if (function_exists('request') && app()->bound('request')) {
112+
$req = request();
113+
$requestData['url'] = method_exists($req, 'getUri') ? $req->getUri() : null;
114+
$requestData['method'] = method_exists($req, 'getMethod') ? $req->getMethod() : null;
115+
$requestData['token'] = method_exists($req, 'header') ? ($req->header('authorization') ?? null) : null;
116+
$requestData['userId'] = method_exists($req, 'user') && $req->user() ? ($req->user()->id ?? null) : null;
117+
}
118+
} catch (Throwable) {
119+
// ignore request context errors for CLI/queue
120+
}
121+
122+
return array_merge([
123+
'attempt' => $attempt,
124+
'errorInfo' => $e->errorInfo,
125+
'ExceptionName' => get_class($e),
126+
'QueryException' => $e->getMessage(),
127+
'trace' => getDebugBacktraceArray() ?? null,
128+
], $requestData);
129+
}
130+
131+
/**
132+
* @throws RandomException
133+
*/
134+
protected static function backoffDelay(int $baseDelay, int $attempt): int
135+
{
136+
// Simple exponential backoff with jitter: baseDelay * 2^(attempt-1) +/- 25%
137+
$delay = max(1, (int)round($baseDelay * pow(2, max(0, $attempt - 1))));
138+
$jitter = max(0, (int)round($delay * 0.25));
139+
$min = max(1, $delay - $jitter);
140+
$max = $delay + $jitter;
141+
return random_int($min, $max);
83142
}
84143
}

src/Helper.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,27 @@ function getDebugBacktraceArray(): array
2222
if (!function_exists('generateLog')) {
2323
function generateLog($var, $logFileName, $logType = 'error'): void
2424
{
25-
if (is_null($logFileName)) {
26-
$logFilePath = storage_path('logs/general_' . now()->toDateString() . '.log');
25+
$date = function_exists('now') ? now()->toDateString() : date('Y-m-d');
26+
27+
if (empty($logFileName)) {
28+
$logFilePath = storage_path('logs/' . date('Y-m-d') . 'general_' . $date . '.log');
2729
} else {
28-
$logFilePath = storage_path("logs/{$logFileName}_" . now()->toDateString() . '.log');
30+
$logFilePath = storage_path("logs/" . date('Y-m-d') . "{$logFileName}_" . $date . '.log');
2931
}
3032
$log = Log::build([
3133
'driver' => 'single',
3234
'path' => $logFilePath,
3335
]);
34-
if ($logType == 'error') {
35-
$log->error(var_export($var, true));
36-
} elseif ($logType == 'warning') {
37-
$log->warning(var_export($var, true));
36+
$payload = is_array($var) ? $var : ['message' => (string)$var];
37+
$attempts = $var['attempt'] ?? 0;
38+
$errorInfo = $var['errorInfo'][2] ?? '';
39+
40+
if ($logType === 'error') {
41+
$log->error($attempts . ' ' . $errorInfo, $payload);
42+
} elseif ($logType === 'warning') {
43+
$log->warning($attempts . ' ' . $errorInfo, $payload);
44+
} else {
45+
$log->info($attempts . ' ' . $errorInfo, $payload);
3846
}
3947
}
4048
}

0 commit comments

Comments
 (0)