Skip to content

Commit fdc7565

Browse files
committed
PHPC-1161: Free reference to Session once Cursor is exhausted
1 parent 427854e commit fdc7565

File tree

5 files changed

+275
-0
lines changed

5 files changed

+275
-0
lines changed

src/MongoDB/Cursor.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@
2828

2929
zend_class_entry *php_phongo_cursor_ce;
3030

31+
/* Check if the cursor is exhausted (i.e. ID is zero) and free any reference to
32+
* the session. Calling this function during iteration will allow an implicit
33+
* session to return to the pool immediately after a getMore indicates that the
34+
* server has no more results to return. */
35+
static void php_phongo_cursor_free_session_if_exhausted(php_phongo_cursor_t *cursor) /* {{{ */
36+
{
37+
if (mongoc_cursor_get_id(cursor->cursor)) {
38+
return;
39+
}
40+
41+
if (!Z_ISUNDEF(cursor->session)) {
42+
zval_ptr_dtor(&cursor->session);
43+
ZVAL_UNDEF(&cursor->session);
44+
}
45+
} /* }}} */
46+
3147
static void php_phongo_cursor_free_current(php_phongo_cursor_t *cursor) /* {{{ */
3248
{
3349
if (!Z_ISUNDEF(cursor->visitor_data.zchild)) {
@@ -117,6 +133,8 @@ static void php_phongo_cursor_iterator_move_forward(zend_object_iterator *iter T
117133
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
118134
}
119135
}
136+
137+
php_phongo_cursor_free_session_if_exhausted(cursor);
120138
} /* }}} */
121139

122140
static void php_phongo_cursor_iterator_rewind(zend_object_iterator *iter TSRMLS_DC) /* {{{ */
@@ -147,6 +165,8 @@ static void php_phongo_cursor_iterator_rewind(zend_object_iterator *iter TSRMLS_
147165
if (doc) {
148166
php_phongo_bson_to_zval_ex(bson_get_data(doc), doc->len, &cursor->visitor_data);
149167
}
168+
169+
php_phongo_cursor_free_session_if_exhausted(cursor);
150170
} /* }}} */
151171

152172
static zend_object_iterator_funcs php_phongo_cursor_iterator_funcs = {

tests/cursor/cursor-session-001.phpt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
MongoDB\Driver\Cursor debug output for query cursor includes explicit session
3+
--SKIPIF--
4+
<?php require __DIR__ . "/" ."../utils/basic-skipif.inc"; ?>
5+
<?php NEEDS_CRYPTO(); ?>
6+
<?php NEEDS('STANDALONE'); ?>
7+
<?php NEEDS_ATLEAST_MONGODB_VERSION(STANDALONE, "3.6"); ?>
8+
<?php CLEANUP(STANDALONE); ?>
9+
--FILE--
10+
<?php
11+
require_once __DIR__ . "/../utils/basic.inc";
12+
13+
$manager = new MongoDB\Driver\Manager(STANDALONE);
14+
15+
$bulk = new MongoDB\Driver\BulkWrite;
16+
$bulk->insert(['_id' => 1]);
17+
$bulk->insert(['_id' => 2]);
18+
$bulk->insert(['_id' => 3]);
19+
$manager->executeBulkWrite(NS, $bulk);
20+
21+
$query = new MongoDB\Driver\Query([], ['batchSize' => 2]);
22+
$session = $manager->startSession();
23+
24+
$cursor = $manager->executeQuery(NS, $query, ['session' => $session]);
25+
26+
$iterator = new IteratorIterator($cursor);
27+
$iterator->rewind();
28+
$iterator->next();
29+
30+
printf("Cursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
31+
var_dump($cursor);
32+
33+
$iterator->next();
34+
35+
/* Per PHPC-1161, the Cursor will free a reference to the Session as soon as it
36+
* is exhausted. While this is primarily done to ensure implicit sessions for
37+
* command cursors are returned to the pool ASAP, it also applies to explicit
38+
* sessions. */
39+
printf("\nCursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
40+
var_dump($cursor);
41+
42+
?>
43+
===DONE===
44+
<?php exit(0); ?>
45+
--EXPECTF--
46+
Cursor ID is zero: no
47+
object(MongoDB\Driver\Cursor)#%d (%d) {
48+
%a
49+
["session"]=>
50+
object(MongoDB\Driver\Session)#%d (%d) {
51+
%a
52+
}
53+
%a
54+
}
55+
56+
Cursor ID is zero: yes
57+
object(MongoDB\Driver\Cursor)#%d (%d) {
58+
%a
59+
["session"]=>
60+
NULL
61+
%a
62+
}
63+
===DONE===

tests/cursor/cursor-session-002.phpt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
MongoDB\Driver\Cursor debug output for query cursor omits implicit session
3+
--SKIPIF--
4+
<?php require __DIR__ . "/" ."../utils/basic-skipif.inc"; ?>
5+
<?php NEEDS_CRYPTO(); ?>
6+
<?php NEEDS('STANDALONE'); ?>
7+
<?php NEEDS_ATLEAST_MONGODB_VERSION(STANDALONE, "3.6"); ?>
8+
<?php CLEANUP(STANDALONE); ?>
9+
--FILE--
10+
<?php
11+
require_once __DIR__ . "/../utils/basic.inc";
12+
13+
$manager = new MongoDB\Driver\Manager(STANDALONE);
14+
15+
$bulk = new MongoDB\Driver\BulkWrite;
16+
$bulk->insert(['_id' => 1]);
17+
$bulk->insert(['_id' => 2]);
18+
$bulk->insert(['_id' => 3]);
19+
$manager->executeBulkWrite(NS, $bulk);
20+
21+
$query = new MongoDB\Driver\Query([], ['batchSize' => 2]);
22+
23+
$cursor = $manager->executeQuery(NS, $query);
24+
25+
$iterator = new IteratorIterator($cursor);
26+
$iterator->rewind();
27+
$iterator->next();
28+
29+
/* Implicit sessions for query cursors are never exposed to PHPC, as they are
30+
* handled internally by libmongoc. Cursor debug ouput should never report such
31+
* sessions. */
32+
printf("Cursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
33+
var_dump($cursor);
34+
35+
$iterator->next();
36+
37+
printf("\nCursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
38+
var_dump($cursor);
39+
40+
?>
41+
===DONE===
42+
<?php exit(0); ?>
43+
--EXPECTF--
44+
Cursor ID is zero: no
45+
object(MongoDB\Driver\Cursor)#%d (%d) {
46+
%a
47+
["session"]=>
48+
NULL
49+
%a
50+
}
51+
52+
Cursor ID is zero: yes
53+
object(MongoDB\Driver\Cursor)#%d (%d) {
54+
%a
55+
["session"]=>
56+
NULL
57+
%a
58+
}
59+
===DONE===

tests/cursor/cursor-session-003.phpt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
--TEST--
2+
MongoDB\Driver\Cursor debug output for command cursor includes explicit session
3+
--SKIPIF--
4+
<?php require __DIR__ . "/" ."../utils/basic-skipif.inc"; ?>
5+
<?php NEEDS_CRYPTO(); ?>
6+
<?php NEEDS('STANDALONE'); ?>
7+
<?php NEEDS_ATLEAST_MONGODB_VERSION(STANDALONE, "3.6"); ?>
8+
<?php CLEANUP(STANDALONE); ?>
9+
--FILE--
10+
<?php
11+
require_once __DIR__ . "/../utils/basic.inc";
12+
13+
$manager = new MongoDB\Driver\Manager(STANDALONE);
14+
15+
$bulk = new MongoDB\Driver\BulkWrite;
16+
$bulk->insert(['_id' => 1]);
17+
$bulk->insert(['_id' => 2]);
18+
$bulk->insert(['_id' => 3]);
19+
$manager->executeBulkWrite(NS, $bulk);
20+
21+
$command = new MongoDB\Driver\Command([
22+
'aggregate' => COLLECTION_NAME,
23+
'pipeline' => [['$match' => new stdClass]],
24+
'cursor' => ['batchSize' => 2],
25+
]);
26+
$session = $manager->startSession();
27+
28+
$cursor = $manager->executeCommand(DATABASE_NAME, $command, ['session' => $session]);
29+
30+
$iterator = new IteratorIterator($cursor);
31+
$iterator->rewind();
32+
$iterator->next();
33+
34+
printf("Cursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
35+
var_dump($cursor);
36+
37+
$iterator->next();
38+
39+
/* Per PHPC-1161, the Cursor will free a reference to the Session as soon as it
40+
* is exhausted. While this is primarily done to ensure implicit sessions for
41+
* command cursors are returned to the pool ASAP, it also applies to explicit
42+
* sessions. */
43+
printf("\nCursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
44+
var_dump($cursor);
45+
46+
?>
47+
===DONE===
48+
<?php exit(0); ?>
49+
--EXPECTF--
50+
Cursor ID is zero: no
51+
object(MongoDB\Driver\Cursor)#%d (%d) {
52+
%a
53+
["session"]=>
54+
object(MongoDB\Driver\Session)#%d (%d) {
55+
%a
56+
}
57+
%a
58+
}
59+
60+
Cursor ID is zero: yes
61+
object(MongoDB\Driver\Cursor)#%d (%d) {
62+
%a
63+
["session"]=>
64+
NULL
65+
%a
66+
}
67+
===DONE===

tests/cursor/cursor-session-004.phpt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
MongoDB\Driver\Cursor debug output for command cursor includes implicit session
3+
--SKIPIF--
4+
<?php require __DIR__ . "/" ."../utils/basic-skipif.inc"; ?>
5+
<?php NEEDS_CRYPTO(); ?>
6+
<?php NEEDS('STANDALONE'); ?>
7+
<?php NEEDS_ATLEAST_MONGODB_VERSION(STANDALONE, "3.6"); ?>
8+
<?php CLEANUP(STANDALONE); ?>
9+
--FILE--
10+
<?php
11+
require_once __DIR__ . "/../utils/basic.inc";
12+
13+
$manager = new MongoDB\Driver\Manager(STANDALONE);
14+
15+
$bulk = new MongoDB\Driver\BulkWrite;
16+
$bulk->insert(['_id' => 1]);
17+
$bulk->insert(['_id' => 2]);
18+
$bulk->insert(['_id' => 3]);
19+
$manager->executeBulkWrite(NS, $bulk);
20+
21+
$command = new MongoDB\Driver\Command([
22+
'aggregate' => COLLECTION_NAME,
23+
'pipeline' => [['$match' => new stdClass]],
24+
'cursor' => ['batchSize' => 2],
25+
]);
26+
27+
$cursor = $manager->executeCommand(DATABASE_NAME, $command);
28+
29+
$iterator = new IteratorIterator($cursor);
30+
$iterator->rewind();
31+
$iterator->next();
32+
33+
printf("Cursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
34+
var_dump($cursor);
35+
36+
$iterator->next();
37+
38+
/* Unlike implicit sessions for query cursors, which are handled internally by
39+
* libmongoc, PHPC-1152 emulates its own implicit sessions for command cursors
40+
* in order to ensure that command cursors always share the same session as the
41+
* originating command. */
42+
printf("\nCursor ID is zero: %s\n", (string) $cursor->getId() === '0' ? 'yes' : 'no');
43+
var_dump($cursor);
44+
45+
?>
46+
===DONE===
47+
<?php exit(0); ?>
48+
--EXPECTF--
49+
Cursor ID is zero: no
50+
object(MongoDB\Driver\Cursor)#%d (%d) {
51+
%a
52+
["session"]=>
53+
object(MongoDB\Driver\Session)#%d (%d) {
54+
%a
55+
}
56+
%a
57+
}
58+
59+
Cursor ID is zero: yes
60+
object(MongoDB\Driver\Cursor)#%d (%d) {
61+
%a
62+
["session"]=>
63+
NULL
64+
%a
65+
}
66+
===DONE===

0 commit comments

Comments
 (0)