Skip to content

Commit

Permalink
In long-running processes, allows checking the database connection by…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
alli83 committed Apr 11, 2024
1 parent 140ecec commit fc382de
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 3 deletions.
12 changes: 12 additions & 0 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class Configuration

private ?SchemaManagerFactory $schemaManagerFactory = null;

private ?float $checkConnectionTiming = null;

public function __construct()
{
$this->schemaAssetsFilter = static function (): bool {
Expand Down Expand Up @@ -153,4 +155,14 @@ public function setDisableTypeComments(bool $disableTypeComments): self

return $this;
}

public function setCheckConnectionTiming(float $timing): void

Check warning on line 159 in src/Configuration.php

View check run for this annotation

Codecov / codecov/patch

src/Configuration.php#L159

Added line #L159 was not covered by tests
{
$this->checkConnectionTiming = $timing;

Check warning on line 161 in src/Configuration.php

View check run for this annotation

Codecov / codecov/patch

src/Configuration.php#L161

Added line #L161 was not covered by tests
}

public function getCheckConnectionTiming(): ?float
{
return $this->checkConnectionTiming;
}
}
38 changes: 37 additions & 1 deletion src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use function is_string;
use function key;
use function sprintf;
use function time;

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

private SchemaManagerFactory $schemaManagerFactory;

private bool $isChecking = false;

private ?int $lastCheckedAt = null;

private ?int $heartbeat;

/**
* Initializes a new instance of the Connection class.
*
Expand All @@ -119,6 +126,8 @@ public function __construct(
$this->params = $params;
$this->autoCommit = $this->_config->getAutoCommit();

$this->heartbeat = $this->_config->getCheckConnectionTiming();

$this->schemaManagerFactory = $this->_config->getSchemaManagerFactory()
?? new DefaultSchemaManagerFactory();
}
Expand Down Expand Up @@ -210,7 +219,23 @@ public function createExpressionBuilder(): ExpressionBuilder
protected function connect(): DriverConnection
{
if ($this->_conn !== null) {
return $this->_conn;
$isTimeToCheck = $this->lastCheckedAt === null || time() - $this->lastCheckedAt >= $this->heartbeat;
$noCheckNeeded = $this->heartbeat === null || $this->isChecking;

if ($noCheckNeeded || ! $isTimeToCheck) {
return $this->_conn;
}

$this->isChecking = true;

Check warning on line 229 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L229

Added line #L229 was not covered by tests

$isAvailable = $this->reconnectOnFailure();

Check warning on line 231 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L231

Added line #L231 was not covered by tests

$this->lastCheckedAt = time();
$this->isChecking = false;

Check warning on line 234 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L233-L234

Added lines #L233 - L234 were not covered by tests

if ($isAvailable) {
return $this->_conn;

Check warning on line 237 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L236-L237

Added lines #L236 - L237 were not covered by tests
}
}

try {
Expand Down Expand Up @@ -1371,4 +1396,15 @@ private function handleDriverException(

return $exception;
}

private function reconnectOnFailure(): bool

Check warning on line 1400 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L1400

Added line #L1400 was not covered by tests
{
try {
$this->executeQuery($this->getDatabasePlatform($this)->getDummySelectSQL());

Check warning on line 1403 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L1403

Added line #L1403 was not covered by tests

return true;
} catch (ConnectionLost) {
return false;

Check warning on line 1407 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L1405-L1407

Added lines #L1405 - L1407 were not covered by tests
}
}
}
41 changes: 41 additions & 0 deletions tests/Functional/Connection/ConnectionReactivatedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\Connection;

use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Tests\FunctionalTestCase;

use function sleep;

class ConnectionReactivatedTest extends FunctionalTestCase
{
public static function setUpBeforeClass(): void
{
self::markConnectionWithHeartBeat();
}

protected function setUp(): void
{
if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
return;
}

self::markTestSkipped('Currently only supported with MySQL');
}

public function testConnectionReactivated(): void
{
$this->connection->executeStatement('SET SESSION wait_timeout=1');

sleep(2);

$query = $this->connection->getDatabasePlatform()
->getDummySelectSQL();

$this->connection->executeQuery($query);

self::assertEquals(1, $this->connection->fetchOne($query));
}
}
9 changes: 8 additions & 1 deletion tests/FunctionalTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ abstract class FunctionalTestCase extends TestCase
*/
private bool $isConnectionReusable = true;

protected static bool $hasHeartBeat = false;

/**
* Mark shared connection not reusable for subsequent tests.
*
Expand All @@ -37,11 +39,16 @@ protected function markConnectionNotReusable(): void
$this->isConnectionReusable = false;
}

protected static function markConnectionWithHeartBeat(): void
{
self::$hasHeartBeat = true;
}

#[Before]
final protected function connect(): void
{
if (self::$sharedConnection === null) {
self::$sharedConnection = TestUtil::getConnection();
self::$sharedConnection = TestUtil::getConnection(self::$hasHeartBeat);
}

$this->connection = self::$sharedConnection;
Expand Down
6 changes: 5 additions & 1 deletion tests/TestUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ class TestUtil
*
* @return Connection The database connection instance.
*/
public static function getConnection(): Connection
public static function getConnection($hasHeartBeat = false): Connection
{
$params = self::getConnectionParams();

if ($hasHeartBeat) {
$params['check_connection_frequency'] = 1;
}

if (empty($params['memory']) && ! self::$initialized) {
self::initializeDatabase();
self::$initialized = true;
Expand Down

0 comments on commit fc382de

Please sign in to comment.