Skip to content

Commit 8b2c052

Browse files
authored
PHPLIB-1400: Work around MigrationConflict errors on sharded clusters (#1232)
1 parent 280f20c commit 8b2c052

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
@@ -183,12 +183,14 @@ private function doTestCase(stdClass $test, string $schemaVersion, ?array $runOn
183183
$this->checkRunOnRequirements($test->runOnRequirements);
184184
}
185185

186-
if (isset($initialData)) {
187-
$this->prepareInitialData($initialData);
188-
}
186+
assertIsArray($test->operations);
189187

190188
$context = $this->createContext();
191189

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

203-
assertIsArray($test->operations);
204205
$this->preventStaleDbVersionError($test->operations, $context);
205206

206207
$context->startEventObservers();
@@ -423,17 +424,48 @@ private function assertOutcome(array $outcome): void
423424
}
424425
}
425426

426-
private function prepareInitialData(array $initialData): void
427+
private function prepareInitialData(array $initialData, Context $context, bool $isAdvanceClusterTimeNeeded): void
427428
{
428429
assertNotEmpty($initialData);
429430
assertContainsOnly('object', $initialData);
430431

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

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

0 commit comments

Comments
 (0)