Skip to content

Commit d8a4aac

Browse files
authored
Reconnect the database after any failed worker cycle (#170)
A failed cycle can leave the connection in a state it never recovers from on its own. When a transaction is aborted and the rollback that follows also fails, the connection throws away its own transaction bookkeeping while the driver keeps considering a transaction open. Every later attempt to begin one is then rejected by the driver with "There is already an active transaction", which is not a lost connection, so the worker held on to the connection and failed every single cycle until it was restarted by hand.
1 parent 26b2b1f commit d8a4aac

2 files changed

Lines changed: 33 additions & 19 deletions

File tree

src/RequestInsurance/RequestInsuranceWorker.php

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -139,29 +139,29 @@ public function run(bool $runOnlyOnce = false): void
139139
*/
140140
protected function handleCycleFailure(Throwable $throwable): void
141141
{
142-
if ( ! $this->wasCausedByLostConnection($throwable)) {
142+
if ($this->wasCausedByLostConnection($throwable)) {
143+
$this->consecutiveLostConnections++;
144+
145+
$message = sprintf(
146+
'RequestInsurance Worker (#%s) lost its database connection during %s and is reconnecting (%d in a row)',
147+
$this->runningHash,
148+
$this->currentPhase,
149+
$this->consecutiveLostConnections
150+
);
151+
152+
if ($this->consecutiveLostConnections > self::QUIET_LOST_CONNECTION_RECOVERIES) {
153+
Log::error($message, ['exception' => $throwable]);
154+
} else {
155+
Log::debug($message);
156+
}
157+
} else {
143158
$this->consecutiveLostConnections = 0;
144159

145160
Log::error($throwable);
146-
147-
return;
148-
}
149-
150-
$this->consecutiveLostConnections++;
151-
152-
$message = sprintf(
153-
'RequestInsurance Worker (#%s) lost its database connection during %s and is reconnecting (%d in a row)',
154-
$this->runningHash,
155-
$this->currentPhase,
156-
$this->consecutiveLostConnections
157-
);
158-
159-
if ($this->consecutiveLostConnections > self::QUIET_LOST_CONNECTION_RECOVERIES) {
160-
Log::error($message, ['exception' => $throwable]);
161-
} else {
162-
Log::debug($message);
163161
}
164162

163+
// The connection is given up on no matter what failed the cycle, since a failure can leave it in a
164+
// state it never recovers from on its own, such as a transaction its driver still considers open.
165165
rescue(fn () => $this->reconnectToDatabase(), null, false);
166166
}
167167

tests/Unit/RequestInsuranceWorkerTest.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Exception;
66
use Throwable;
7+
use PDOException;
78
use Tests\TestCase;
89
use GuzzleHttp\Psr7\Request;
910
use Illuminate\Support\Facades\Log;
@@ -501,7 +502,20 @@ public function test_it_reports_cycle_failures_that_are_not_lost_connections():
501502

502503
$worker->exposeReportCycleFailure($throwable);
503504

504-
$this->assertSame(0, $worker->reconnects);
505+
$this->assertSame(1, $worker->reconnects);
506+
}
507+
508+
public function test_it_gives_up_on_a_connection_holding_a_stranded_transaction(): void
509+
{
510+
$worker = $this->getWorkerProbe();
511+
512+
Log::shouldReceive('error')->times(5);
513+
514+
foreach (range(1, 5) as $ignored) {
515+
$worker->exposeReportCycleFailure(new PDOException('There is already an active transaction'));
516+
}
517+
518+
$this->assertSame(5, $worker->reconnects);
505519
}
506520

507521
/**

0 commit comments

Comments
 (0)