Skip to content
This repository was archived by the owner on Feb 1, 2022. It is now read-only.

Commit e6f687c

Browse files
committed
PHP-1460: Advancing cursor beyond its limit should kill it
Previously, a cursor advanced beyond limit would simply be marked as dead (i.e. dead boolean set to true, cursor ID and connection cleared) without killing the cursor on the server. This was inconsistent with behavior in hasNext(), which was refactored for 1.6.0. In addition to killing the cursor, we should still clear its ID (this ensures MongoCursorInterface::dead() still functions properly). Elsewhere, php_mongo_cursor_mark_dead() is only used as an error callback when we cannot find a suitable connection for issuing a query or initializing a command cursor.
1 parent 970d734 commit e6f687c

3 files changed

Lines changed: 181 additions & 2 deletions

File tree

cursor.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ int php_mongocursor_advance(mongo_cursor *cursor TSRMLS_DC)
380380
}
381381
/* Limit reached */
382382
if (cursor->limit != 0 && cursor->at >= cursor->limit) {
383-
mongo_deregister_callback_from_connection(cursor->connection, cursor);
384-
php_mongo_cursor_mark_dead(cursor);
383+
php_mongo_kill_cursor(cursor->connection, cursor->cursor_id TSRMLS_CC);
384+
cursor->cursor_id = 0;
385385
return FAILURE;
386386
}
387387
if (!php_mongo_get_more(cursor TSRMLS_CC)) {

tests/generic/bug01460-001.phpt

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
--TEST--
2+
Test for PHP-1460: Query with limit leaves open cursors on server (foreach iteration)
3+
--SKIPIF--
4+
<?php require_once "tests/utils/standalone.inc" ?>
5+
--FILE--
6+
<?php
7+
require_once "tests/utils/server.inc";
8+
9+
function getNumOpenCursors(MongoClient $mc) {
10+
$result = $mc->admin->command(array('serverStatus' => 1));
11+
12+
if (isset($result['metrics']['cursor']['open']['total'])) {
13+
return (integer) $result['metrics']['cursor']['open']['total'];
14+
}
15+
16+
if (isset($result['cursors']['totalOpen'])) {
17+
return (integer) $result['cursors']['totalOpen'];
18+
}
19+
20+
throw new RuntimeException('serverStatus did not return cursor metrics');
21+
}
22+
23+
function log_killcursor($server, $info) {
24+
printf("Killing cursor: %d\n", $info['cursor_id']);
25+
}
26+
27+
function log_getmore($server, $info) {
28+
printf("Getmore on cursor: %d\n", $info['cursor_id']);
29+
}
30+
31+
$ctx = stream_context_create(array(
32+
'mongodb' => array(
33+
'log_getmore' => 'log_getmore',
34+
'log_killcursor' => 'log_killcursor',
35+
),
36+
));
37+
38+
$host = MongoShellServer::getStandaloneInfo();
39+
$mc = new MongoClient($host, array(), array('context' => $ctx));
40+
41+
$c = $mc->selectCollection(dbname(), collname(__FILE__));
42+
$c->drop();
43+
44+
for ($i = 0; $i < 15; $i++) {
45+
$c->insert(array('_id' => $i));
46+
}
47+
48+
$numOpenCursorsBeforeQuery = getNumOpenCursors($mc);
49+
printf("Number of open cursors before query: %d\n", $numOpenCursorsBeforeQuery);
50+
51+
$cursor = $c->find()->limit(10)->batchSize(5);
52+
printf("Cursor is dead: %s\n", $cursor->dead() ? 'true' : 'false');
53+
54+
foreach ($cursor as $document) {
55+
printf("Found document: %d\n", $document['_id']);
56+
if ($document['_id'] === 4 || $document['_id'] === 9) {
57+
printf("Cursor is dead: %s\n", $cursor->dead() ? 'true' : 'false');
58+
}
59+
}
60+
61+
printf("Cursor is dead: %s\n", $cursor->dead() ? 'true' : 'false');
62+
63+
$numOpenCursorsAfterQuery = getNumOpenCursors($mc);
64+
printf("Number of open cursors after query: %d\n", $numOpenCursorsAfterQuery);
65+
printf("Same number of cursors open before and after query: %s\n", $numOpenCursorsBeforeQuery === $numOpenCursorsAfterQuery ? 'true' : 'false');
66+
67+
?>
68+
===DONE===
69+
--EXPECTF--
70+
Number of open cursors before query: %d
71+
Cursor is dead: false
72+
Found document: 0
73+
Found document: 1
74+
Found document: 2
75+
Found document: 3
76+
Found document: 4
77+
Cursor is dead: false
78+
Getmore on cursor: %d
79+
Found document: 5
80+
Found document: 6
81+
Found document: 7
82+
Found document: 8
83+
Found document: 9
84+
Cursor is dead: false
85+
Killing cursor: %d
86+
Cursor is dead: true
87+
Number of open cursors after query: %d
88+
Same number of cursors open before and after query: true
89+
===DONE===

tests/generic/bug01460-002.phpt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
Test for PHP-1460: Query with limit leaves open cursors on server (getNext/hasNext iteration)
3+
--SKIPIF--
4+
<?php require_once "tests/utils/standalone.inc" ?>
5+
--FILE--
6+
<?php
7+
require_once "tests/utils/server.inc";
8+
9+
function getNumOpenCursors(MongoClient $mc) {
10+
$result = $mc->admin->command(array('serverStatus' => 1));
11+
12+
if (isset($result['metrics']['cursor']['open']['total'])) {
13+
return (integer) $result['metrics']['cursor']['open']['total'];
14+
}
15+
16+
if (isset($result['cursors']['totalOpen'])) {
17+
return (integer) $result['cursors']['totalOpen'];
18+
}
19+
20+
throw new RuntimeException('serverStatus did not return cursor metrics');
21+
}
22+
23+
function log_killcursor($server, $info) {
24+
printf("Killing cursor: %d\n", $info['cursor_id']);
25+
}
26+
27+
function log_getmore($server, $info) {
28+
printf("Getmore on cursor: %d\n", $info['cursor_id']);
29+
}
30+
31+
$ctx = stream_context_create(array(
32+
'mongodb' => array(
33+
'log_getmore' => 'log_getmore',
34+
'log_killcursor' => 'log_killcursor',
35+
),
36+
));
37+
38+
$host = MongoShellServer::getStandaloneInfo();
39+
$mc = new MongoClient($host, array(), array('context' => $ctx));
40+
41+
$c = $mc->selectCollection(dbname(), collname(__FILE__));
42+
$c->drop();
43+
44+
for ($i = 0; $i < 15; $i++) {
45+
$c->insert(array('_id' => $i));
46+
}
47+
48+
$numOpenCursorsBeforeQuery = getNumOpenCursors($mc);
49+
printf("Number of open cursors before query: %d\n", $numOpenCursorsBeforeQuery);
50+
51+
$cursor = $c->find()->limit(10)->batchSize(5);
52+
printf("Cursor is dead: %s\n", $cursor->dead() ? 'true' : 'false');
53+
54+
while ($cursor->hasNext()) {
55+
$document = $cursor->getNext();
56+
printf("Found document: %d\n", $document['_id']);
57+
if ($document['_id'] === 4 || $document['_id'] === 9) {
58+
printf("Cursor is dead: %s\n", $cursor->dead() ? 'true' : 'false');
59+
}
60+
}
61+
62+
printf("Cursor is dead: %s\n", $cursor->dead() ? 'true' : 'false');
63+
64+
$numOpenCursorsAfterQuery = getNumOpenCursors($mc);
65+
printf("Number of open cursors after query: %d\n", $numOpenCursorsAfterQuery);
66+
printf("Same number of cursors open before and after query: %s\n", $numOpenCursorsBeforeQuery === $numOpenCursorsAfterQuery ? 'true' : 'false');
67+
68+
?>
69+
===DONE===
70+
--EXPECTF--
71+
Number of open cursors before query: %d
72+
Cursor is dead: false
73+
Found document: 0
74+
Found document: 1
75+
Found document: 2
76+
Found document: 3
77+
Found document: 4
78+
Cursor is dead: false
79+
Getmore on cursor: %d
80+
Found document: 5
81+
Found document: 6
82+
Found document: 7
83+
Found document: 8
84+
Found document: 9
85+
Cursor is dead: false
86+
Killing cursor: %d
87+
Cursor is dead: true
88+
Number of open cursors after query: %d
89+
Same number of cursors open before and after query: true
90+
===DONE===

0 commit comments

Comments
 (0)