Skip to content

Commit a5a39ec

Browse files
committed
PHPLIB-1400: Work around MigrationConflict errors on sharded clusters (#1232)
1 parent 12ba712 commit a5a39ec

File tree

3 files changed

+62
-11
lines changed

3 files changed

+62
-11
lines changed

tests/UnifiedSpecTests/CollectionData.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,22 @@ public function __construct(stdClass $o)
3939
$this->documents = $o->documents;
4040
}
4141

42-
public function prepareInitialData(Client $client): void
42+
public function prepareInitialData(Client $client, ?Session $session = null): void
4343
{
4444
$database = $client->selectDatabase(
4545
$this->databaseName,
4646
['writeConcern' => new WriteConcern(WriteConcern::MAJORITY)],
4747
);
4848

49-
$database->dropCollection($this->collectionName);
49+
$database->dropCollection($this->collectionName, ['session' => $session]);
5050

5151
if (empty($this->documents)) {
52-
$database->createCollection($this->collectionName);
52+
$database->createCollection($this->collectionName, ['session' => $session]);
5353

5454
return;
5555
}
5656

57-
$database->selectCollection($this->collectionName)->insertMany($this->documents);
57+
$database->selectCollection($this->collectionName)->insertMany($this->documents, ['session' => $session]);
5858
}
5959

6060
public function assertOutcome(Client $client): void

tests/UnifiedSpecTests/Context.php

+20-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ final class Context
5858

5959
private string $multiMongosUri;
6060

61+
private ?object $advanceClusterTime = null;
62+
6163
public function __construct(Client $internalClient, string $uri)
6264
{
6365
$this->entityMap = new EntityMap();
@@ -150,6 +152,17 @@ public function setActiveClient(?string $clientId = null): void
150152
$this->activeClient = $clientId;
151153
}
152154

155+
/**
156+
* Set a cluster time to use for advancing newly created session entities.
157+
*
158+
* This is used to ensure causal consistency with initialData collections
159+
* in sharded environments (see: DRIVERS-2816).
160+
*/
161+
public function setAdvanceClusterTime(?object $clusterTime): void
162+
{
163+
$this->advanceClusterTime = $clusterTime;
164+
}
165+
153166
public function isInLoop(): bool
154167
{
155168
return $this->inLoop;
@@ -463,7 +476,13 @@ private function createSession(string $id, stdClass $o): void
463476
$options = self::prepareSessionOptions((array) $o->sessionOptions);
464477
}
465478

466-
$this->entityMap->set($id, $client->startSession($options), $clientId);
479+
$session = $client->startSession($options);
480+
481+
if ($this->advanceClusterTime !== null) {
482+
$session->advanceClusterTime($this->advanceClusterTime);
483+
}
484+
485+
$this->entityMap->set($id, $session, $clientId);
467486
}
468487

469488
private function createBucket(string $id, stdClass $o): void

tests/UnifiedSpecTests/UnifiedTestRunner.php

+38-6
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,14 @@ private function doTestCase(stdClass $test, string $schemaVersion, ?array $runOn
181181
$this->checkRunOnRequirements($test->runOnRequirements);
182182
}
183183

184-
if (isset($initialData)) {
185-
$this->prepareInitialData($initialData);
186-
}
184+
assertIsArray($test->operations);
187185

188186
$context = $this->createContext();
189187

188+
if (isset($initialData)) {
189+
$this->prepareInitialData($initialData, $context, $this->isAdvanceClusterTimeNeeded($test->operations));
190+
}
191+
190192
/* If an EntityMap observer has been configured, assign the Context's
191193
* EntityMap to a class property so it can later be accessed from run(),
192194
* irrespective of whether this test succeeds or fails. */
@@ -198,7 +200,6 @@ private function doTestCase(stdClass $test, string $schemaVersion, ?array $runOn
198200
$context->createEntities($createEntities);
199201
}
200202

201-
assertIsArray($test->operations);
202203
$this->preventStaleDbVersionError($test->operations, $context);
203204

204205
$context->startEventObservers();
@@ -421,17 +422,48 @@ private function assertOutcome(array $outcome): void
421422
}
422423
}
423424

424-
private function prepareInitialData(array $initialData): void
425+
private function prepareInitialData(array $initialData, Context $context, bool $isAdvanceClusterTimeNeeded): void
425426
{
426427
assertNotEmpty($initialData);
427428
assertContainsOnly('object', $initialData);
428429

430+
/* In order to avoid MigrationConflict errors on sharded clusters, use the cluster time obtained from creating
431+
* collections to advance session entities. This is necessary because initialData uses an internal MongoClient,
432+
* which will not share/gossip its cluster time via the test entities. */
433+
if ($isAdvanceClusterTimeNeeded) {
434+
$session = $this->internalClient->startSession();
435+
}
436+
429437
foreach ($initialData as $data) {
430438
$collectionData = new CollectionData($data);
431-
$collectionData->prepareInitialData($this->internalClient);
439+
$collectionData->prepareInitialData($this->internalClient, $session ?? null);
440+
}
441+
442+
if (isset($session)) {
443+
$context->setAdvanceClusterTime($session->getClusterTime());
432444
}
433445
}
434446

447+
/**
448+
* Work around potential MigrationConflict errors on sharded clusters.
449+
*/
450+
private function isAdvanceClusterTimeNeeded(array $operations): bool
451+
{
452+
if (! in_array($this->getPrimaryServer()->getType(), [Server::TYPE_MONGOS, Server::TYPE_LOAD_BALANCER], true)) {
453+
return false;
454+
}
455+
456+
foreach ($operations as $operation) {
457+
switch ($operation->name) {
458+
case 'startTransaction':
459+
case 'withTransaction':
460+
return true;
461+
}
462+
}
463+
464+
return false;
465+
}
466+
435467
/**
436468
* Work around potential error executing distinct on sharded clusters.
437469
*

0 commit comments

Comments
 (0)