Skip to content

Commit b2c5e8c

Browse files
committed
PHPLIB-443: Propagate non-resumable errors during ChangeStream iteration
1 parent 7313f12 commit b2c5e8c

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

src/ChangeStream.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ public function next()
120120
$this->resumeCallable = null;
121121
}
122122
} catch (RuntimeException $e) {
123-
if ($this->isResumableError($e)) {
124-
$this->resume();
125-
}
123+
$this->resumeOrThrow($e);
126124
}
127125
}
128126

@@ -144,9 +142,7 @@ public function rewind()
144142
$this->resumeCallable = null;
145143
}
146144
} catch (RuntimeException $e) {
147-
if ($this->isResumableError($e)) {
148-
$this->resume();
149-
}
145+
$this->resumeOrThrow($e);
150146
}
151147
}
152148

@@ -227,4 +223,20 @@ private function resume()
227223
$this->csIt = $newChangeStream->csIt;
228224
$this->csIt->rewind();
229225
}
226+
227+
/**
228+
* Either resumes after a resumable error or re-throws the exception.
229+
*
230+
* @param RuntimeException $exception
231+
* @throws RuntimeException
232+
*/
233+
private function resumeOrThrow(RuntimeException $exception)
234+
{
235+
if ($this->isResumableError($exception)) {
236+
$this->resume();
237+
return;
238+
}
239+
240+
throw $exception;
241+
}
230242
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests;
4+
5+
use MongoDB\Collection;
6+
use MongoDB\Driver\Exception\ServerException;
7+
8+
/**
9+
* Change Streams spec prose tests.
10+
*
11+
* @see https://github.com/mongodb/specifications/tree/master/source/change-streams
12+
*/
13+
class ChangeStreamsProseTest extends FunctionalTestCase
14+
{
15+
private $collection;
16+
17+
public function setUp()
18+
{
19+
parent::setUp();
20+
21+
$this->skipIfChangeStreamIsNotSupported();
22+
23+
$this->collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName());
24+
$this->dropCollection();
25+
}
26+
27+
public function tearDown()
28+
{
29+
if (!$this->hasFailed()) {
30+
$this->dropCollection();
31+
}
32+
33+
parent::tearDown();
34+
}
35+
36+
/**
37+
* ChangeStream will not attempt to resume after encountering error code
38+
* 11601 (Interrupted), 136 (CappedPositionLost), or 237 (CursorKilled)
39+
* while executing a getMore command.
40+
*
41+
* @dataProvider provideNonResumableErrorCodes
42+
*/
43+
public function testProseTest5($errorCode)
44+
{
45+
$this->configureFailPoint([
46+
'configureFailPoint' => 'failCommand',
47+
'mode' => ['times' => 1],
48+
'data' => ['failCommands' => ['getMore'], 'errorCode' => $errorCode],
49+
]);
50+
51+
$this->createCollection();
52+
$changeStream = $this->collection->watch();
53+
54+
$this->expectException(ServerException::class);
55+
$this->expectExceptionCode($errorCode);
56+
$changeStream->rewind();
57+
}
58+
59+
public function provideNonResumableErrorCodes()
60+
{
61+
return [
62+
[136], // CappedPositionLost
63+
[237], // CursorKilled
64+
[11601], // Interrupted
65+
];
66+
}
67+
}

tests/SpecTests/FunctionalTestCase.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,19 @@ protected function checkServerRequirements(array $runOn)
147147
* The fail point will automatically be disabled during tearDown() to avoid
148148
* affecting a subsequent test.
149149
*
150-
* @param stdClass $command configureFailPoint command document
150+
* @param array|stdClass $command configureFailPoint command document
151151
* @throws InvalidArgumentException if $command is not a configureFailPoint command
152152
*/
153-
protected function configureFailPoint(stdClass $command)
153+
protected function configureFailPoint($command)
154154
{
155+
if (is_array($command)) {
156+
$command = (object) $command;
157+
}
158+
159+
if ( ! $command instanceof stdClass) {
160+
throw new InvalidArgumentException('$command is not an array or stdClass instance');
161+
}
162+
155163
if (key($command) !== 'configureFailPoint') {
156164
throw new InvalidArgumentException('$command is not a configureFailPoint command');
157165
}

0 commit comments

Comments
 (0)