Skip to content

Commit 56733ed

Browse files
authored
Merge pull request #47 from aternosorg/mysql-retry
flexible mysql retry handling
2 parents 451cd46 + 91ad392 commit 56733ed

3 files changed

Lines changed: 160 additions & 34 deletions

File tree

src/Driver/Mysqli/Mysqli.php

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
class Mysqli extends Driver implements CRUDAbleInterface, CRUDQueryableInterface
2929
{
3030
public const string ID = "mysqli";
31+
32+
/** @var int[] */
33+
protected const array CONNECTION_ERROR_CODES = [2006, 2013];
34+
3135
protected string $id = self::ID;
3236

3337
/**
@@ -77,8 +81,6 @@ class Mysqli extends Driver implements CRUDAbleInterface, CRUDQueryableInterface
7781
*/
7882
protected ?\mysqli $connection = null;
7983

80-
protected int $connectionRetries = 1;
81-
8284
/**
8385
* Mysqli constructor.
8486
*
@@ -123,6 +125,17 @@ protected function reconnect(): void
123125
$this->connect();
124126
}
125127

128+
/**
129+
* @return RetryCollection
130+
*/
131+
protected function getRetryCollection(): RetryCollection
132+
{
133+
return new RetryCollection([
134+
new RetryGroup(static::CONNECTION_ERROR_CODES, 1), // connection
135+
new RetryGroup([1213], 3) // deadlock
136+
]);
137+
}
138+
126139
/**
127140
* Execute a mysql query
128141
*
@@ -134,24 +147,19 @@ protected function reconnect(): void
134147
protected function rawQuery(string $query): mysqli_result|true
135148
{
136149
$this->connect();
137-
$retries = $this->connectionRetries;
150+
$retries = $this->getRetryCollection();
138151
while (true) {
139152
try {
140153
return $this->connection->query($query);
141154
} catch (mysqli_sql_exception $e) {
142-
// no more retries left
143-
if ($retries <= 0) {
144-
throw MysqliException::fromException($e);
145-
}
146-
147-
// connection error, try to reconnect and retry
148-
if ($e->getCode() === 2006 || $e->getCode() === 2013) {
149-
$this->reconnect();
150-
$retries--;
155+
if ($retries->canRetry($e->getCode())) {
156+
if (in_array($e->getCode(), static::CONNECTION_ERROR_CODES, true)) {
157+
$this->reconnect();
158+
}
159+
$this->handleRetriedException($e);
151160
continue;
152161
}
153162

154-
// other error, throw exception
155163
throw MysqliException::fromException($e);
156164
}
157165
}
@@ -166,29 +174,33 @@ protected function rawQuery(string $query): mysqli_result|true
166174
protected function escape(string $data): string
167175
{
168176
$this->connect();
169-
$retries = $this->connectionRetries;
177+
$retries = $this->getRetryCollection();
170178
while (true) {
171179
try {
172180
return $this->connection->real_escape_string($data);
173181
} catch (mysqli_sql_exception $e) {
174-
// no more retries left
175-
if ($retries <= 0) {
176-
throw MysqliException::fromException($e);
177-
}
178-
179-
// connection error, try to reconnect and retry
180-
if ($e->getCode() === 2006 || $e->getCode() === 2013) {
181-
$this->reconnect();
182-
$retries--;
182+
if ($retries->canRetry($e->getCode())) {
183+
if (in_array($e->getCode(), static::CONNECTION_ERROR_CODES, true)) {
184+
$this->reconnect();
185+
}
186+
$this->handleRetriedException($e);
183187
continue;
184188
}
185189

186-
// other error, throw exception
187190
throw MysqliException::fromException($e);
188191
}
189192
}
190193
}
191194

195+
/**
196+
* @param mysqli_sql_exception $exception
197+
* @return void
198+
*/
199+
protected function handleRetriedException(mysqli_sql_exception $exception): void
200+
{
201+
// can be used to log retried exceptions
202+
}
203+
192204
/**
193205
* Save the model
194206
*
@@ -383,14 +395,4 @@ public function setDatabase(string $database): Mysqli
383395
$this->database = $database;
384396
return $this;
385397
}
386-
387-
/**
388-
* @param int $connectionRetries
389-
* @return $this
390-
*/
391-
public function setConnectionRetries(int $connectionRetries): static
392-
{
393-
$this->connectionRetries = $connectionRetries;
394-
return $this;
395-
}
396398
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Aternos\Model\Driver\Mysqli;
4+
5+
class RetryCollection
6+
{
7+
/**
8+
* @param RetryGroup[] $groups
9+
*/
10+
public function __construct(protected array $groups = [])
11+
{
12+
}
13+
14+
/**
15+
* @param RetryGroup $group
16+
* @return $this
17+
*/
18+
public function addGroup(RetryGroup $group): static
19+
{
20+
$this->groups[] = $group;
21+
return $this;
22+
}
23+
24+
/**
25+
* @param int[] $statusCodes
26+
* @return $this
27+
*/
28+
public function removeGroup(array $statusCodes): static
29+
{
30+
$this->groups = array_values(array_filter($this->groups, function (RetryGroup $group) use ($statusCodes) {
31+
return !$group->matchesStatusCodes($statusCodes);
32+
}));
33+
return $this;
34+
}
35+
36+
/**
37+
* @param int $statusCode
38+
* @return bool
39+
*/
40+
public function canRetry(int $statusCode): bool
41+
{
42+
foreach ($this->groups as $group) {
43+
if ($group->matchesStatusCode($statusCode) && $group->hasRetriesLeft()) {
44+
$group->addRetry();
45+
return true;
46+
}
47+
}
48+
return false;
49+
}
50+
}

src/Driver/Mysqli/RetryGroup.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Aternos\Model\Driver\Mysqli;
4+
5+
class RetryGroup
6+
{
7+
protected int $retries = 0;
8+
9+
/**
10+
* @param int[] $statusCodes
11+
* @param int $maxRetries
12+
*/
13+
public function __construct(
14+
protected array $statusCodes = [],
15+
protected int $maxRetries = 1,
16+
)
17+
{
18+
}
19+
20+
/**
21+
* @return int
22+
*/
23+
public function getMaxRetries(): int
24+
{
25+
return $this->maxRetries;
26+
}
27+
28+
/**
29+
* @return void
30+
*/
31+
public function addRetry(): void
32+
{
33+
$this->retries++;
34+
}
35+
36+
/**
37+
* @return bool
38+
*/
39+
public function hasRetriesLeft(): bool
40+
{
41+
return $this->retries < $this->maxRetries;
42+
}
43+
44+
/**
45+
* @return int[]
46+
*/
47+
public function getStatusCodes(): array
48+
{
49+
return $this->statusCodes;
50+
}
51+
52+
/**
53+
* @param int $statusCode
54+
* @return bool
55+
*/
56+
public function matchesStatusCode(int $statusCode): bool
57+
{
58+
return in_array($statusCode, $this->statusCodes);
59+
}
60+
61+
/**
62+
* @param int[] $statusCodes
63+
* @return bool
64+
*/
65+
public function matchesStatusCodes(array $statusCodes): bool
66+
{
67+
foreach ($statusCodes as $statusCode) {
68+
if ($this->matchesStatusCode($statusCode)) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
}
74+
}

0 commit comments

Comments
 (0)