fix(banking): record the job deaths that never reach handle()
Every `logSyncAttempt` call lived inside `handle()`, which is exactly what an out-of-band death skips — so a connection's own history showed no trace of the most common way this job dies. One prod connection has 66 job failures and 3 sync-log rows. That gap is why the first pass of this branch mis-attributed the stranding to timeouts: the evidence was only in `failed_jobs`. `duration_ms` becomes nullable at the call site rather than being faked to 0, because nobody timed the attempt. The copy changes too. `failed()` wrote "An unexpected error occurred during sync. Please try again later." — handing our own infrastructure to the user as an action — while the classified-transient path already promised "we will try syncing again later" for the same situation. Every message this method writes describes an out-of-band death, so it now says so and the next scheduled cycle picks the connection back up on its own.
This commit is contained in:
parent
e60fdf4faa
commit
e0481df497
|
|
@ -188,8 +188,23 @@ class SyncBankingConnectionJob implements ShouldBeUnique, ShouldQueue
|
|||
|
||||
$connection->update([
|
||||
'status' => BankingConnectionStatus::Error,
|
||||
'error_message' => $e ? $this->friendlyErrorMessage($e) : __('An unexpected error occurred during sync. Please try again later.'),
|
||||
// Every message written here describes an out-of-band death, so the
|
||||
// generic "an unexpected error occurred, please try again" was pushing
|
||||
// our own infrastructure onto the user. The next scheduled cycle picks
|
||||
// the connection back up on its own.
|
||||
'error_message' => __('The sync did not finish. We will try again later.'),
|
||||
]);
|
||||
|
||||
// handle() owns every other logSyncAttempt call, and it is precisely what
|
||||
// an out-of-band death skips - leaving the connection's own history with no
|
||||
// trace of the most common way this job dies.
|
||||
$this->logSyncAttempt(
|
||||
$connection,
|
||||
BankingSyncLogStatus::Failed,
|
||||
startTime: null,
|
||||
error: $e,
|
||||
metadata: ['reason' => 'job_died_outside_handle'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -302,22 +317,27 @@ class SyncBankingConnectionJob implements ShouldBeUnique, ShouldQueue
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $startTime Null when the caller never got to start a
|
||||
* timer, so the duration is genuinely unknown
|
||||
* rather than zero.
|
||||
*/
|
||||
private function logSyncAttempt(
|
||||
BankingConnection $connection,
|
||||
BankingSyncLogStatus $status,
|
||||
float $startTime,
|
||||
?float $startTime,
|
||||
?\Throwable $error = null,
|
||||
?array $metadata = null,
|
||||
): void {
|
||||
$durationMs = (int) round((microtime(true) - $startTime) * 1000);
|
||||
|
||||
BankingSyncLog::create([
|
||||
'banking_connection_id' => $connection->id,
|
||||
'status' => $status,
|
||||
'attempt' => $this->attempts(),
|
||||
'error_message' => $error?->getMessage(),
|
||||
'error_class' => $error ? get_class($error) : null,
|
||||
'duration_ms' => $durationMs,
|
||||
'duration_ms' => $startTime === null
|
||||
? null
|
||||
: (int) round((microtime(true) - $startTime) * 1000),
|
||||
'metadata' => $metadata,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@
|
|||
"Amount is required": "Se requiere un valor",
|
||||
"An unexpected error occurred during sync.": "Se produjo un error inesperado durante la sincronización.",
|
||||
"An unexpected error occurred during sync. Please try again later.": "Ocurrió un error inesperado durante la sincronización. Inténtalo de nuevo más tarde.",
|
||||
"The sync did not finish. We will try again later.": "La sincronización no se completó. Volveremos a intentarlo más tarde.",
|
||||
"Annual": "Anual",
|
||||
"Annual Interest Rate": "Tasa de Interés Anual",
|
||||
"Annual Interest Rate (%)": "Tasa de Interés Anual (%)",
|
||||
|
|
|
|||
|
|
@ -1642,7 +1642,7 @@ test('failed sync job marks active connection as error so onboarding can continu
|
|||
$connection->refresh();
|
||||
expect($connection->status)->toBe(BankingConnectionStatus::Error);
|
||||
expect($connection->last_synced_at)->toBeNull();
|
||||
expect($connection->error_message)->toBe('An unexpected error occurred during sync. Please try again later.');
|
||||
expect($connection->error_message)->toBe('The sync did not finish. We will try again later.');
|
||||
// A job killed from the outside says nothing about the connection, so it no
|
||||
// longer spends a scheduled retry.
|
||||
expect($connection->consecutive_sync_failures)->toBe(0);
|
||||
|
|
|
|||
|
|
@ -945,3 +945,19 @@ test('a second out-of-band death on an already errored connection changes nothin
|
|||
expect($connection->consecutive_sync_failures)->toBe(2);
|
||||
expect($connection->error_message)->toBe('Earlier failure kept.');
|
||||
});
|
||||
|
||||
test('an out-of-band job death is recorded in the connection history', function () {
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
(new SyncBankingConnectionJob($connection))->failed(
|
||||
new TimeoutExceededException('App\Jobs\SyncBankingConnectionJob has timed out.')
|
||||
);
|
||||
|
||||
$log = BankingSyncLog::where('banking_connection_id', $connection->id)->sole();
|
||||
expect($log->status)->toBe(BankingSyncLogStatus::Failed);
|
||||
expect($log->error_class)->toBe(TimeoutExceededException::class);
|
||||
expect($log->metadata['reason'])->toBe('job_died_outside_handle');
|
||||
// Unknown rather than zero: nobody timed this attempt.
|
||||
expect($log->duration_ms)->toBeNull();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue