Skip to content

Commit

Permalink
close db and redis connections when worker is in idle state (#3757)
Browse files Browse the repository at this point in the history
  • Loading branch information
henzigo authored Jan 28, 2025
1 parent 4544fd2 commit 35d4f28
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
49 changes: 49 additions & 0 deletions src/Component/Messenger/CloseIdleConnectionSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrameworkBundle\Component\Messenger;

use Doctrine\Persistence\ManagerRegistry;
use Shopsys\FrameworkBundle\Component\Redis\RedisFacade;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Event\WorkerRunningEvent;

class CloseIdleConnectionSubscriber implements EventSubscriberInterface
{
/**
* @param \Doctrine\Persistence\ManagerRegistry $managerRegistry
* @param \Shopsys\FrameworkBundle\Component\Redis\RedisFacade $redisFacade
*/
public function __construct(
protected readonly ManagerRegistry $managerRegistry,
protected readonly RedisFacade $redisFacade,
) {
}

/**
* @return iterable
*/
public static function getSubscribedEvents(): iterable
{
yield WorkerRunningEvent::class => 'onWorkerRunning';
}

/**
* @param \Symfony\Component\Messenger\Event\WorkerRunningEvent $event
*/
public function onWorkerRunning(WorkerRunningEvent $event): void
{
if (!$event->isWorkerIdle()) {
return;
}

foreach ($this->managerRegistry->getConnections() as $connection) {
$connection->close();
}

foreach ($this->redisFacade->getConnections() as $redis) {
$redis->close();
}
}
}
8 changes: 8 additions & 0 deletions src/Component/Redis/RedisFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ protected function cleanCacheByScan(Redis $redisClient, string $pattern): void
}
}

/**
* @return iterable<\Redis>
*/
public function getConnections(): iterable
{
return $this->allClients;
}

public function pingAllClients(): void
{
foreach ($this->allClients as $redis) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Tests\FrameworkBundle\Unit\Component\Messenger;

use Doctrine\DBAL\Connection;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Redis;
use Shopsys\FrameworkBundle\Component\Messenger\CloseIdleConnectionSubscriber;
use Shopsys\FrameworkBundle\Component\Redis\RedisFacade;
use Symfony\Component\Messenger\Event\WorkerRunningEvent;
use Symfony\Component\Messenger\Worker;

class CloseIdleConnectionSubscriberTest extends TestCase
{
private ManagerRegistry|MockObject $managerRegistry;

private RedisFacade|MockObject $redisFacade;

private CloseIdleConnectionSubscriber $subscriber;

protected function setUp(): void
{
parent::setUp();

$this->managerRegistry = $this->createMock(ManagerRegistry::class);
$this->redisFacade = $this->createMock(RedisFacade::class);
$this->subscriber = new CloseIdleConnectionSubscriber($this->managerRegistry, $this->redisFacade);
}

public function testGetSubscribedEvents(): void
{
$expected = [
WorkerRunningEvent::class => 'onWorkerRunning',
];

$this->assertEquals($expected, iterator_to_array(CloseIdleConnectionSubscriber::getSubscribedEvents()));
}

public function testWhenWorkerIsIdle(): void
{
$dbConnection1 = $this->createMock(Connection::class);
$dbConnection2 = $this->createMock(Connection::class);

$redisConnection = $this->createMock(Redis::class);

$this->managerRegistry
->expects($this->once())
->method('getConnections')
->willReturn([$dbConnection1, $dbConnection2]);

$this->redisFacade
->expects($this->once())
->method('getConnections')
->willReturn([$redisConnection]);

$redisConnection
->expects($this->once())
->method('close');

$dbConnection1
->expects($this->once())
->method('close');

$dbConnection2
->expects($this->once())
->method('close');

$worker = $this->createMock(Worker::class);
$event = new WorkerRunningEvent($worker, true);

$this->subscriber->onWorkerRunning($event);
}

public function testWhenWorkerIsNotIdle(): void
{
$this->managerRegistry
->expects($this->never())
->method('getConnections');

$this->redisFacade
->expects($this->never())
->method('getConnections');

$worker = $this->createMock(Worker::class);
$event = new WorkerRunningEvent($worker, false);

$this->subscriber->onWorkerRunning($event);
}
}

0 comments on commit 35d4f28

Please sign in to comment.