Skip to content

Commit 403b4d4

Browse files
committed
PHPC-1294: Connections survive primary stepdown
1 parent 9074847 commit 403b4d4

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests;
4+
5+
use IteratorIterator;
6+
use MongoDB\Client;
7+
use MongoDB\Collection;
8+
use MongoDB\Driver\Command;
9+
use MongoDB\Driver\Exception\BulkWriteException;
10+
use MongoDB\Driver\Exception\Exception as DriverException;
11+
use MongoDB\Driver\ReadPreference;
12+
use MongoDB\Driver\WriteConcern;
13+
use MongoDB\Operation\BulkWrite;
14+
use MongoDB\Tests\CommandObserver;
15+
use UnexpectedValueException;
16+
17+
class PrimaryStepDownSpecTest extends FunctionalTestCase
18+
{
19+
/** @var Client */
20+
private $client;
21+
22+
/** @var Collection */
23+
private $collection;
24+
25+
public function setUp()
26+
{
27+
parent::setUp();
28+
29+
$this->client = new Client(static::getUri(), ['retryWrites' => false]);
30+
31+
$this->dropAndRecreateCollection();
32+
$this->collection = $this->client->selectCollection($this->getDatabaseName(), $this->getCollectionName());
33+
}
34+
35+
public function testGetMoreIteration()
36+
{
37+
$runOn = [(object) ['minServerVersion' => '4.1.11', 'topology' => [self::TOPOLOGY_REPLICASET]]];
38+
$this->checkServerRequirements($runOn);
39+
40+
// Insert 5 documents into a collection with a majority write concern.
41+
$this->insertDocuments(5);
42+
43+
// Start a find operation on the collection with a batch size of 2, and retrieve the first batch of results.
44+
$cursor = $this->collection->find([], ['batchSize' => 2]);
45+
46+
$iterator = new IteratorIterator($cursor);
47+
$iterator->rewind();
48+
$this->assertTrue($iterator->valid());
49+
50+
$iterator->next();
51+
$this->assertTrue($iterator->valid());
52+
53+
$totalConnectionsCreated = $this->getTotalConnectionsCreated();
54+
55+
// Send a {replSetStepDown: 5, force: true} command to the current primary and verify that the command succeeded
56+
$primary = $this->client->getManager()->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
57+
$primary->executeCommand('admin', new Command(['replSetStepDown' => 5, 'force' => true]));
58+
59+
// Retrieve the next batch of results from the cursor obtained in the find operation, and verify that this operation succeeded.
60+
$events = [];
61+
$observer = new CommandObserver();
62+
$observer->observe(
63+
function () use ($iterator) {
64+
$iterator->next();
65+
},
66+
function ($event) use (&$events) {
67+
$events[] = $event;
68+
}
69+
);
70+
$this->assertTrue($iterator->valid());
71+
$this->assertCount(1, $events);
72+
$this->assertSame('getMore', $events[0]['started']->getCommandName());
73+
74+
// Verify that no new connections have been created
75+
$this->assertSame($totalConnectionsCreated, $this->getTotalConnectionsCreated());
76+
77+
// Wait to allow primary election to complete and prevent subsequent test failures
78+
$this->waitForMasterReelection();
79+
}
80+
81+
public function testNotMasterKeepsConnectionPool()
82+
{
83+
$runOn = [(object) ['minServerVersion' => '4.1.11', 'topology' => [self::TOPOLOGY_REPLICASET]]];
84+
$this->checkServerRequirements($runOn);
85+
86+
// Set a fail point
87+
$this->configureFailPoint([
88+
'configureFailPoint' => 'failCommand',
89+
'mode' => ['times' => 1],
90+
'data' => [
91+
'failCommands' => ['insert'],
92+
'errorCode' => 10107,
93+
],
94+
]);
95+
96+
$totalConnectionsCreated = $this->getTotalConnectionsCreated();
97+
98+
// Execute an insert into the test collection of a {test: 1} document.
99+
try {
100+
$this->insertDocuments(1);
101+
} catch (BulkWriteException $e) {
102+
// Verify that the insert failed with an operation failure with 10107 code.
103+
$this->assertSame(10107, $e->getCode());
104+
}
105+
106+
// Execute an insert into the test collection of a {test: 1} document and verify that it succeeds.
107+
$result = $this->insertDocuments(1);
108+
$this->assertSame(1, $result->getInsertedCount());
109+
110+
// Verify that the connection pool has not been cleared
111+
$this->assertSame($totalConnectionsCreated, $this->getTotalConnectionsCreated());
112+
}
113+
114+
public function testNotMasterResetConnectionPool()
115+
{
116+
$runOn = [(object) ['maxServerVersion' => '4.0.999', 'topology' => [self::TOPOLOGY_REPLICASET]]];
117+
$this->checkServerRequirements($runOn);
118+
119+
// Set a fail point
120+
$this->configureFailPoint([
121+
'configureFailPoint' => 'failCommand',
122+
'mode' => ['times' => 1],
123+
'data' => [
124+
'failCommands' => ['insert'],
125+
'errorCode' => 10107,
126+
],
127+
]);
128+
129+
$totalConnectionsCreated = $this->getTotalConnectionsCreated();
130+
131+
// Execute an insert into the test collection of a {test: 1} document.
132+
try {
133+
$this->insertDocuments(1);
134+
} catch (BulkWriteException $e) {
135+
// Verify that the insert failed with an operation failure with 10107 code.
136+
$this->assertSame(10107, $e->getCode());
137+
}
138+
139+
// Verify that the connection pool has been cleared
140+
$this->assertSame($totalConnectionsCreated + 1, $this->getTotalConnectionsCreated());
141+
}
142+
143+
public function testShutdownResetConnectionPool()
144+
{
145+
$runOn = [(object) ['minServerVersion' => '4.0.0', 'topology' => [self::TOPOLOGY_REPLICASET]]];
146+
$this->checkServerRequirements($runOn);
147+
148+
// Set a fail point
149+
$this->configureFailPoint([
150+
'configureFailPoint' => 'failCommand',
151+
'mode' => ['times' => 1],
152+
'data' => [
153+
'failCommands' => ['insert'],
154+
'errorCode' => 91,
155+
],
156+
]);
157+
158+
$totalConnectionsCreated = $this->getTotalConnectionsCreated();
159+
160+
// Execute an insert into the test collection of a {test: 1} document.
161+
try {
162+
$this->insertDocuments(1);
163+
} catch (BulkWriteException $e) {
164+
// Verify that the insert failed with an operation failure with 10107 code.
165+
$this->assertSame(91, $e->getCode());
166+
}
167+
168+
// Verify that the connection pool has been cleared
169+
$this->assertSame($totalConnectionsCreated + 1, $this->getTotalConnectionsCreated());
170+
}
171+
172+
public function testInterruptedAtShutdownResetConnectionPool()
173+
{
174+
$runOn = [(object) ['maxServerVersion' => '4.0.999', 'topology' => [self::TOPOLOGY_REPLICASET]]];
175+
$this->checkServerRequirements($runOn);
176+
177+
// Set a fail point
178+
$this->configureFailPoint([
179+
'configureFailPoint' => 'failCommand',
180+
'mode' => ['times' => 1],
181+
'data' => [
182+
'failCommands' => ['insert'],
183+
'errorCode' => 11600,
184+
],
185+
]);
186+
187+
$totalConnectionsCreated = $this->getTotalConnectionsCreated();
188+
189+
// Execute an insert into the test collection of a {test: 1} document.
190+
try {
191+
$this->insertDocuments(1);
192+
} catch (BulkWriteException $e) {
193+
// Verify that the insert failed with an operation failure with 10107 code.
194+
$this->assertSame(11600, $e->getCode());
195+
}
196+
197+
// Verify that the connection pool has been cleared
198+
$this->assertSame($totalConnectionsCreated + 1, $this->getTotalConnectionsCreated());
199+
}
200+
201+
private function insertDocuments($count)
202+
{
203+
$operations = [];
204+
205+
for ($i = 1; $i <= $count; $i++) {
206+
$operations[] = [
207+
BulkWrite::INSERT_ONE => [['test' => $i]],
208+
];
209+
}
210+
211+
return $this->collection->bulkWrite($operations, ['writeConcern' => new WriteConcern('majority')]);
212+
}
213+
214+
private function dropAndRecreateCollection()
215+
{
216+
$this->client->selectCollection($this->getDatabaseName(), $this->getCollectionName())->drop();
217+
$this->client->selectDatabase($this->getDatabaseName())->command(['create' => $this->getCollectionName()]);
218+
}
219+
220+
private function getTotalConnectionsCreated()
221+
{
222+
$cursor = $this->client->getManager()->executeCommand(
223+
$this->getDatabaseName(),
224+
new Command(['serverStatus' => 1]),
225+
new ReadPreference(ReadPreference::RP_PRIMARY)
226+
);
227+
228+
$cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
229+
$document = current($cursor->toArray());
230+
231+
if (isset($document['connections'], $document['connections']['totalCreated'])) {
232+
return (int) $document['connections']['totalCreated'];
233+
}
234+
235+
throw new UnexpectedValueException('Could not determine number of total connections');
236+
}
237+
238+
private function waitForMasterReelection()
239+
{
240+
for ($i = 0; $i < 10; $i++) {
241+
try {
242+
$this->insertDocuments(1);
243+
return;
244+
} catch (DriverException $e) {
245+
sleep(2);
246+
}
247+
}
248+
249+
$this->fail('Expected primary to be re-elected within 20 seconds.');
250+
}
251+
}

0 commit comments

Comments
 (0)