Skip to content

Commit b80fe31

Browse files
committed
In long-running processes, allows checking the database connection by pinging it, thus avoiding exceptions. Additionally, it provides the flexibility to configure the frequency of the check to be performed in the case of long-running processes.
1 parent 140ecec commit b80fe31

File tree

5 files changed

+105
-5
lines changed

5 files changed

+105
-5
lines changed

src/Configuration.php

+12
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class Configuration
3636

3737
private ?SchemaManagerFactory $schemaManagerFactory = null;
3838

39+
private ?int $checkConnectionTiming = null;
40+
3941
public function __construct()
4042
{
4143
$this->schemaAssetsFilter = static function (): bool {
@@ -153,4 +155,14 @@ public function setDisableTypeComments(bool $disableTypeComments): self
153155

154156
return $this;
155157
}
158+
159+
public function setCheckConnectionTiming(int $timing): void
160+
{
161+
$this->checkConnectionTiming = $timing;
162+
}
163+
164+
public function getCheckConnectionTiming(): ?int
165+
{
166+
return $this->checkConnectionTiming;
167+
}
156168
}

src/Connection.php

+37-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
use function is_string;
4343
use function key;
4444
use function sprintf;
45+
use function time;
4546

4647
/**
4748
* A database abstraction-level connection that implements features like transaction isolation levels,
@@ -99,6 +100,12 @@ class Connection implements ServerVersionProvider
99100

100101
private SchemaManagerFactory $schemaManagerFactory;
101102

103+
private bool $isChecking = false;
104+
105+
private int $lastCheckedAt = 0;
106+
107+
private ?int $heartbeat;
108+
102109
/**
103110
* Initializes a new instance of the Connection class.
104111
*
@@ -119,6 +126,8 @@ public function __construct(
119126
$this->params = $params;
120127
$this->autoCommit = $this->_config->getAutoCommit();
121128

129+
$this->heartbeat = $this->_config->getCheckConnectionTiming();
130+
122131
$this->schemaManagerFactory = $this->_config->getSchemaManagerFactory()
123132
?? new DefaultSchemaManagerFactory();
124133
}
@@ -210,7 +219,23 @@ public function createExpressionBuilder(): ExpressionBuilder
210219
protected function connect(): DriverConnection
211220
{
212221
if ($this->_conn !== null) {
213-
return $this->_conn;
222+
$isTimeToCheck = time() - $this->lastCheckedAt >= $this->heartbeat;
223+
$noCheckNeeded = $this->heartbeat === null || $this->isChecking;
224+
225+
if ($noCheckNeeded || ! $isTimeToCheck) {
226+
return $this->_conn;
227+
}
228+
229+
$this->isChecking = true;
230+
231+
$isAvailable = $this->reconnectOnFailure();
232+
233+
$this->lastCheckedAt = time();
234+
$this->isChecking = false;
235+
236+
if ($isAvailable) {
237+
return $this->_conn;
238+
}
214239
}
215240

216241
try {
@@ -1371,4 +1396,15 @@ private function handleDriverException(
13711396

13721397
return $exception;
13731398
}
1399+
1400+
private function reconnectOnFailure(): bool
1401+
{
1402+
try {
1403+
$this->executeQuery($this->getDatabasePlatform($this)->getDummySelectSQL());
1404+
1405+
return true;
1406+
} catch (ConnectionLost) {
1407+
return false;
1408+
}
1409+
}
13741410
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Tests\Functional\Connection;
6+
7+
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
8+
use Doctrine\DBAL\Tests\FunctionalTestCase;
9+
10+
use function sleep;
11+
12+
class ConnectionReactivatedTest extends FunctionalTestCase
13+
{
14+
public static function setUpBeforeClass(): void
15+
{
16+
self::markConnectionWithHeartBeat();
17+
}
18+
19+
protected function setUp(): void
20+
{
21+
if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
22+
return;
23+
}
24+
25+
self::markTestSkipped('Currently only supported with MySQL');
26+
}
27+
28+
public function testConnectionReactivated(): void
29+
{
30+
$this->connection->executeStatement('SET SESSION wait_timeout=1');
31+
32+
sleep(2);
33+
34+
$query = $this->connection->getDatabasePlatform()
35+
->getDummySelectSQL();
36+
37+
$this->connection->executeQuery($query);
38+
39+
self::assertEquals(1, $this->connection->fetchOne($query));
40+
}
41+
}

tests/FunctionalTestCase.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ abstract class FunctionalTestCase extends TestCase
2626
*/
2727
private bool $isConnectionReusable = true;
2828

29+
protected static bool $hasHeartBeat = false;
30+
2931
/**
3032
* Mark shared connection not reusable for subsequent tests.
3133
*
@@ -37,11 +39,16 @@ protected function markConnectionNotReusable(): void
3739
$this->isConnectionReusable = false;
3840
}
3941

42+
protected static function markConnectionWithHeartBeat(): void
43+
{
44+
self::$hasHeartBeat = true;
45+
}
46+
4047
#[Before]
4148
final protected function connect(): void
4249
{
4350
if (self::$sharedConnection === null) {
44-
self::$sharedConnection = TestUtil::getConnection();
51+
self::$sharedConnection = TestUtil::getConnection(self::$hasHeartBeat);
4552
}
4653

4754
$this->connection = self::$sharedConnection;

tests/TestUtil.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class TestUtil
6262
*
6363
* @return Connection The database connection instance.
6464
*/
65-
public static function getConnection(): Connection
65+
public static function getConnection($hasHeartBeat = false): Connection
6666
{
6767
$params = self::getConnectionParams();
6868

@@ -75,7 +75,7 @@ public static function getConnection(): Connection
7575

7676
return DriverManager::getConnection(
7777
$params,
78-
self::createConfiguration($params['driver']),
78+
self::createConfiguration($params['driver'], $hasHeartBeat),
7979
);
8080
}
8181

@@ -153,7 +153,7 @@ private static function initializeDatabase(): void
153153
$privConn->close();
154154
}
155155

156-
private static function createConfiguration(string $driver): Configuration
156+
private static function createConfiguration(string $driver, $hasHearBeat): Configuration
157157
{
158158
$configuration = new Configuration();
159159

@@ -170,6 +170,10 @@ private static function createConfiguration(string $driver): Configuration
170170

171171
$configuration->setSchemaManagerFactory(new DefaultSchemaManagerFactory());
172172

173+
if ($hasHearBeat) {
174+
$configuration->setCheckConnectionTiming(1);
175+
}
176+
173177
return $configuration;
174178
}
175179

0 commit comments

Comments
 (0)