Skip to content

Commit

Permalink
PHPLIB-1531: Replace FailPointObserver with different logic (#1427)
Browse files Browse the repository at this point in the history
* Extract fail point management to a trait used by Context

* Ensure event listeners are still unregistered after an operation fails

This change was prompted by the addition of a finally block after executing operations. It may address a memory leak whereby listeners (and the Context they reference) would remain in PHPC's global registry until RSHUTDOWN.

---------

Co-authored-by: Jeremy Mikola <jmikola@gmail.com>
  • Loading branch information
alcaeus and jmikola committed Sep 20, 2024
1 parent 50d1db7 commit 90dcf18
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 81 deletions.
2 changes: 2 additions & 0 deletions tests/UnifiedSpecTests/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
*/
final class Context
{
use ManagesFailPointsTrait;

private ?string $activeClient = null;

private EntityMap $entityMap;
Expand Down
63 changes: 0 additions & 63 deletions tests/UnifiedSpecTests/FailPointObserver.php

This file was deleted.

40 changes: 40 additions & 0 deletions tests/UnifiedSpecTests/ManagesFailPointsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace MongoDB\Tests\UnifiedSpecTests;

use MongoDB\Driver\Server;
use MongoDB\Operation\DatabaseCommand;
use stdClass;

use function PHPUnit\Framework\assertIsString;
use function PHPUnit\Framework\assertObjectHasAttribute;

trait ManagesFailPointsTrait
{
/** @var list<list{string, Server}> */
private array $failPointsAndServers = [];

public function configureFailPoint(stdClass $failPoint, Server $server): void
{
assertObjectHasAttribute('configureFailPoint', $failPoint);
assertIsString($failPoint->configureFailPoint);
assertObjectHasAttribute('mode', $failPoint);

$operation = new DatabaseCommand('admin', $failPoint);
$operation->execute($server);

if ($failPoint->mode !== 'off') {
$this->failPointsAndServers[] = [$failPoint->configureFailPoint, $server];
}
}

public function disableFailPoints(): void
{
foreach ($this->failPointsAndServers as [$failPoint, $server]) {
$operation = new DatabaseCommand('admin', ['configureFailPoint' => $failPoint, 'mode' => 'off']);
$operation->execute($server);
}

$this->failPointsAndServers = [];
}
}
8 changes: 4 additions & 4 deletions tests/UnifiedSpecTests/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use MongoDB\Model\CollectionInfo;
use MongoDB\Model\DatabaseInfo;
use MongoDB\Model\IndexInfo;
use MongoDB\Operation\DatabaseCommand;
use MongoDB\Operation\FindOneAndReplace;
use MongoDB\Operation\FindOneAndUpdate;
use PHPUnit\Framework\Assert;
Expand Down Expand Up @@ -909,16 +908,17 @@ private function executeForTestRunner()
assertArrayHasKey('failPoint', $args);
assertInstanceOf(Client::class, $args['client']);
assertInstanceOf(stdClass::class, $args['failPoint']);
$args['client']->selectDatabase('admin')->command($args['failPoint']);
// Configure the fail point via the Context so it can later be disabled
$this->context->configureFailPoint($args['failPoint'], $args['client']->getManager()->selectServer());
break;
case 'targetedFailPoint':
assertArrayHasKey('session', $args);
assertArrayHasKey('failPoint', $args);
assertInstanceOf(Session::class, $args['session']);
assertInstanceOf(stdClass::class, $args['failPoint']);
assertNotNull($args['session']->getServer(), 'Session is pinned');
$operation = new DatabaseCommand('admin', $args['failPoint']);
$operation->execute($args['session']->getServer());
// Configure the fail point via the Context so it can later be disabled
$this->context->configureFailPoint($args['failPoint'], $args['session']->getServer());
break;
case 'loop':
assertArrayHasKey('operations', $args);
Expand Down
23 changes: 9 additions & 14 deletions tests/UnifiedSpecTests/UnifiedTestRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ final class UnifiedTestRunner
/** @var callable(EntityMap):void */
private $entityMapObserver;

private ?FailPointObserver $failPointObserver = null;

private ServerParameterHelper $serverParameterHelper;

public function __construct(string $internalClientUri)
Expand Down Expand Up @@ -150,9 +148,6 @@ private function doSetUp(): void
* after transient error within a transaction" pinning test causes the
* subsequent transaction test to block. */
$this->killAllSessions();

$this->failPointObserver = new FailPointObserver();
$this->failPointObserver->start();
}

private function doTearDown(bool $hasFailed): void
Expand All @@ -163,9 +158,6 @@ private function doTearDown(bool $hasFailed): void
$this->killAllSessions();
}

$this->failPointObserver->stop();
$this->failPointObserver->disableFailPoints();

/* Manually invoking garbage collection since each test is prone to
* create cycles (perhaps due to EntityMap), which can leak and prevent
* sessions from being released back into the pool. */
Expand Down Expand Up @@ -216,14 +208,17 @@ private function doTestCase(stdClass $test, string $schemaVersion, ?array $runOn
$context->startEventObservers();
$context->startEventCollectors();

foreach ($test->operations as $o) {
$operation = new Operation($o, $context);
$operation->assert();
try {
foreach ($test->operations as $o) {
$operation = new Operation($o, $context);
$operation->assert();
}
} finally {
$context->stopEventObservers();
$context->stopEventCollectors();
$context->disableFailPoints();
}

$context->stopEventObservers();
$context->stopEventCollectors();

if (isset($test->expectEvents)) {
assertIsArray($test->expectEvents);
$context->assertExpectedEventsForClients($test->expectEvents);
Expand Down

0 comments on commit 90dcf18

Please sign in to comment.