Skip to content

Commit 462ab95

Browse files
feature #53214 [DoctrineBridge] Idle connection listener for long running runtime (alli83)
This PR was merged into the 7.1 branch. Discussion ---------- [DoctrineBridge] Idle connection listener for long running runtime | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #51661 | License | MIT This pull request introduces a solution based on the RoadRunner bundle's Doctrine ORM/ODM middleware https://github.com/Baldinof/roadrunner-bundle/blob/3.x/src/Integration/Doctrine/DoctrineORMMiddleware.php#L22. It checks the status of Doctrine connection, then if the connection is initialized and connected, it performs a 'ping' to check its viability. If the ping fails, it closes the connection. linked to doctrine/DoctrineBundle#1739 Commits ------- f7cc44e [DoctrineBridge] Idle connection listener for long running runtime
2 parents 0d7c20a + f7cc44e commit 462ab95

File tree

7 files changed

+192
-8
lines changed

7 files changed

+192
-8
lines changed

phpunit.xml.dist

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,14 @@
7575
<array>
7676
<element key="0"><string>Cache\IntegrationTests</string></element>
7777
<element key="1"><string>Symfony\Bridge\Doctrine\Middleware\Debug</string></element>
78-
<element key="2"><string>Symfony\Component\Cache</string></element>
79-
<element key="3"><string>Symfony\Component\Cache\Tests\Fixtures</string></element>
80-
<element key="4"><string>Symfony\Component\Cache\Tests\Traits</string></element>
81-
<element key="5"><string>Symfony\Component\Cache\Traits</string></element>
82-
<element key="6"><string>Symfony\Component\Console</string></element>
83-
<element key="7"><string>Symfony\Component\HttpFoundation</string></element>
84-
<element key="8"><string>Symfony\Component\Uid</string></element>
78+
<element key="2"><string>Symfony\Bridge\Doctrine\Middleware\IdleConnection</string></element>
79+
<element key="3"><string>Symfony\Component\Cache</string></element>
80+
<element key="4"><string>Symfony\Component\Cache\Tests\Fixtures</string></element>
81+
<element key="5"><string>Symfony\Component\Cache\Tests\Traits</string></element>
82+
<element key="6"><string>Symfony\Component\Cache\Traits</string></element>
83+
<element key="7"><string>Symfony\Component\Console</string></element>
84+
<element key="8"><string>Symfony\Component\HttpFoundation</string></element>
85+
<element key="9"><string>Symfony\Component\Uid</string></element>
8586
</array>
8687
</element>
8788
</array>

src/Symfony/Bridge/Doctrine/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate the `DoctrineExtractor::getTypes()` method, use `DoctrineExtractor::getType()` instead
88
* Allow `EntityValueResolver` to return a list of entities
9+
* Add support for auto-closing idle connections
910

1011
7.0
1112
---
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Middleware\IdleConnection;
13+
14+
use Doctrine\DBAL\Driver as DriverInterface;
15+
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
16+
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
17+
18+
final class Driver extends AbstractDriverMiddleware
19+
{
20+
public function __construct(
21+
DriverInterface $driver,
22+
private \ArrayObject $connectionExpiries,
23+
private readonly int $ttl,
24+
private readonly string $connectionName,
25+
) {
26+
parent::__construct($driver);
27+
}
28+
29+
public function connect(array $params): ConnectionInterface
30+
{
31+
$timestamp = time();
32+
$connection = parent::connect($params);
33+
$this->connectionExpiries[$this->connectionName] = $timestamp + $this->ttl;
34+
35+
return $connection;
36+
}
37+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Middleware\IdleConnection;
13+
14+
use Symfony\Component\DependencyInjection\ContainerInterface;
15+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpKernel\Event\RequestEvent;
17+
use Symfony\Component\HttpKernel\KernelEvents;
18+
19+
final class Listener implements EventSubscriberInterface
20+
{
21+
/**
22+
* @param \ArrayObject<string, int> $connectionExpiries
23+
*/
24+
public function __construct(
25+
private readonly \ArrayObject $connectionExpiries,
26+
private ContainerInterface $container,
27+
) {
28+
}
29+
30+
public function onKernelRequest(RequestEvent $event): void
31+
{
32+
$timestamp = time();
33+
34+
foreach ($this->connectionExpiries as $name => $expiry) {
35+
if ($timestamp >= $expiry) {
36+
// unset before so that we won't retry in case of any failure
37+
$this->connectionExpiries->offsetUnset($name);
38+
39+
try {
40+
$connection = $this->container->get("doctrine.dbal.{$name}_connection");
41+
$connection->close();
42+
} catch (\Exception) {
43+
// ignore exceptions to remain fail-safe
44+
}
45+
}
46+
}
47+
}
48+
49+
public static function getSubscribedEvents(): array
50+
{
51+
return [
52+
KernelEvents::REQUEST => 'onKernelRequest',
53+
];
54+
}
55+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\Middleware\IdleConnection;
13+
14+
use Doctrine\DBAL\Driver as DriverInterface;
15+
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Driver;
18+
19+
class DriverTest extends TestCase
20+
{
21+
/**
22+
* @group time-sensitive
23+
*/
24+
public function testConnect()
25+
{
26+
$driverMock = $this->createMock(DriverInterface::class);
27+
$connectionMock = $this->createMock(ConnectionInterface::class);
28+
29+
$driverMock->expects($this->once())
30+
->method('connect')
31+
->willReturn($connectionMock);
32+
33+
$connectionExpiries = new \ArrayObject();
34+
35+
$driver = new Driver($driverMock, $connectionExpiries, 60, 'default');
36+
$connection = $driver->connect([]);
37+
38+
$this->assertSame($connectionMock, $connection);
39+
$this->assertArrayHasKey('default', $connectionExpiries);
40+
$this->assertSame(time() + 60, $connectionExpiries['default']);
41+
}
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Middleware\IdleConnection;
13+
14+
use Doctrine\DBAL\Connection as ConnectionInterface;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener;
17+
use Symfony\Component\DependencyInjection\ContainerInterface;
18+
use Symfony\Component\HttpKernel\Event\RequestEvent;
19+
20+
class ListenerTest extends TestCase
21+
{
22+
public function testOnKernelRequest()
23+
{
24+
$containerMock = $this->createMock(ContainerInterface::class);
25+
$connectionExpiries = new \ArrayObject(['connectionone' => time() - 30, 'connectiontwo' => time() + 40]);
26+
27+
$connectionOneMock = $this->getMockBuilder(ConnectionInterface::class)
28+
->disableOriginalConstructor()
29+
->getMock();
30+
31+
$containerMock->expects($this->exactly(1))
32+
->method('get')
33+
->with('doctrine.dbal.connectionone_connection')
34+
->willReturn($connectionOneMock);
35+
36+
$listener = new Listener($connectionExpiries, $containerMock);
37+
38+
$listener->onKernelRequest($this->createMock(RequestEvent::class));
39+
40+
$this->assertArrayNotHasKey('connectionone', (array) $connectionExpiries);
41+
$this->assertArrayHasKey('connectiontwo', (array) $connectionExpiries);
42+
}
43+
}

src/Symfony/Bridge/Doctrine/phpunit.xml.dist

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@
3333
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
3434
<arguments>
3535
<array>
36-
<element key="time-sensitive"><string>Symfony\Bridge\Doctrine\Middleware\Debug</string></element>
36+
<element key="time-sensitive">
37+
<array>
38+
<element key="0"><string>Symfony\Bridge\Doctrine\Middleware\Debug</string></element>
39+
<element key="1"><string>Symfony\Bridge\Doctrine\Middleware\Debug</string></element>
40+
</array>
41+
</element>
3742
</array>
3843
</arguments>
3944
</listener>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy