Skip to content

Commit e25e8ed

Browse files
fix: catch PDOException when MySQL server has gone away during SET SESSION wait_timeout (#200)
1 parent d64a1aa commit e25e8ed

File tree

2 files changed

+105
-3
lines changed

2 files changed

+105
-3
lines changed

src/Runtime/Octane/Octane.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,15 @@ protected static function manageDatabaseSessions($databaseSessionPersist, $datab
123123
return $hasSession;
124124
})->map->getRawPdo()->filter(function ($pdo) {
125125
return $pdo instanceof PDO;
126-
})->each->exec(sprintf(
127-
'SET SESSION wait_timeout=%s', $databaseSessionTtl
128-
));
126+
})->each(function ($pdo) use ($databaseSessionTtl) {
127+
try {
128+
$pdo->exec(sprintf(
129+
'SET SESSION wait_timeout=%s', $databaseSessionTtl
130+
));
131+
} catch (Throwable $e) {
132+
// Connection already gone away, safe to ignore...
133+
}
134+
});
129135
};
130136
}
131137

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace Laravel\Vapor\Tests\Feature;
4+
5+
use Laravel\Vapor\Tests\TestCase;
6+
use Mockery;
7+
use PDO;
8+
use PDOException;
9+
10+
class OctaneManageDatabaseSessionsTest extends TestCase
11+
{
12+
public function test_it_does_not_throw_when_pdo_connection_has_gone_away()
13+
{
14+
$stalePdo = Mockery::mock(PDO::class);
15+
$stalePdo->shouldReceive('exec')
16+
->andThrow(new PDOException(
17+
'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away'
18+
));
19+
20+
$this->assertDoesntThrow(function () use ($stalePdo) {
21+
collect([$stalePdo])
22+
->filter(fn ($pdo) => $pdo instanceof PDO)
23+
->each(function ($pdo) {
24+
try {
25+
$pdo->exec(sprintf('SET SESSION wait_timeout=%s', 10));
26+
} catch (\Throwable $e) {
27+
// Connection already gone away, safe to ignore...
28+
}
29+
});
30+
});
31+
}
32+
33+
public function test_it_proves_bug_exists_without_fix()
34+
{
35+
$stalePdo = Mockery::mock(PDO::class);
36+
$stalePdo->shouldReceive('exec')
37+
->andThrow(new PDOException(
38+
'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away'
39+
));
40+
41+
$this->expectException(PDOException::class);
42+
43+
// This is the ORIGINAL broken code - proves the bug
44+
collect([$stalePdo])
45+
->filter(fn ($pdo) => $pdo instanceof PDO)
46+
->each->exec(sprintf('SET SESSION wait_timeout=%s', 10));
47+
}
48+
49+
public function test_it_still_sets_wait_timeout_on_live_connections()
50+
{
51+
$livePdo = Mockery::mock(PDO::class);
52+
$livePdo->shouldReceive('exec')
53+
->once()
54+
->with('SET SESSION wait_timeout=10')
55+
->andReturn(true);
56+
57+
collect([$livePdo])
58+
->filter(fn ($pdo) => $pdo instanceof PDO)
59+
->each(function ($pdo) {
60+
try {
61+
$pdo->exec(sprintf('SET SESSION wait_timeout=%s', 10));
62+
} catch (\Throwable $e) {
63+
//
64+
}
65+
});
66+
67+
$this->addToAssertionCount(
68+
Mockery::getContainer()->mockery_getExpectationCount()
69+
);
70+
}
71+
72+
public function test_it_handles_mixed_live_and_stale_connections()
73+
{
74+
// Simulates Aurora writer alive + reader dead
75+
$livePdo = Mockery::mock(PDO::class);
76+
$livePdo->shouldReceive('exec')->once()->andReturn(true);
77+
78+
$stalePdo = Mockery::mock(PDO::class);
79+
$stalePdo->shouldReceive('exec')
80+
->andThrow(new PDOException(
81+
'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away'
82+
));
83+
84+
$this->assertDoesntThrow(function () use ($livePdo, $stalePdo) {
85+
collect([$livePdo, $stalePdo])
86+
->filter(fn ($pdo) => $pdo instanceof PDO)
87+
->each(function ($pdo) {
88+
try {
89+
$pdo->exec(sprintf('SET SESSION wait_timeout=%s', 10));
90+
} catch (\Throwable $e) {
91+
//
92+
}
93+
});
94+
});
95+
}
96+
}

0 commit comments

Comments
 (0)