Skip to content

Commit cd787db

Browse files
committed
PYTHON-1791 Fix reference counting leaks
Fix batched op_msg/op_query reference leak of overflow doc. Fix theoretically possible (but practically impossible) reference leak of $clusterTime in op_query. Optimization: Don't encode document past the batch size in batched op_query.
1 parent 92ddc09 commit cd787db

File tree

3 files changed

+59
-80
lines changed

3 files changed

+59
-80
lines changed

bson/_cbsonmodule.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,9 @@ static int _load_object(PyObject** object, char* module_name, char* object_name)
346346
*
347347
* Returns non-zero on failure. */
348348
static int _load_python_objects(PyObject* module) {
349-
PyObject* empty_string;
350-
PyObject* re_compile;
351-
PyObject* compiled;
349+
PyObject* empty_string = NULL;
350+
PyObject* re_compile = NULL;
351+
PyObject* compiled = NULL;
352352
struct module_state *state = GETSTATE(module);
353353

354354
if (_load_object(&state->Binary, "bson.binary", "Binary") ||

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ Changes in Version 3.8.0.dev0
8787
- Changes can now be requested from a ``ChangeStream`` cursor without blocking
8888
indefinitely using the new
8989
:meth:`pymongo.change_stream.ChangeStream.try_next` method.
90+
- Fixed a reference leak bug when splitting a batched write command based on
91+
maxWriteBatchSize or the max message size.
9092

9193
Issues Resolved
9294
...............

pymongo/_cmessagemodule.c

Lines changed: 54 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
434434
buffer_t buffer;
435435
int length_location, message_length;
436436
unsigned char check_keys = 0;
437-
PyObject* result;
437+
PyObject* result = NULL;
438438

439439
if (!PyArg_ParseTuple(args, "Iet#iiOOO&|b",
440440
&flags,
@@ -477,18 +477,14 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
477477
/* PyDict_GetItemString returns a borrowed reference. */
478478
Py_INCREF(cluster_time);
479479
if (-1 == PyMapping_DelItemString(query, "$clusterTime")) {
480-
destroy_codec_options(&options);
481-
PyMem_Free(collection_name);
482-
return NULL;
480+
goto fail;
483481
}
484482
}
485483
} else if (PyMapping_HasKeyString(query, "$clusterTime")) {
486484
cluster_time = PyMapping_GetItemString(query, "$clusterTime");
487485
if (!cluster_time
488486
|| -1 == PyMapping_DelItemString(query, "$clusterTime")) {
489-
destroy_codec_options(&options);
490-
PyMem_Free(collection_name);
491-
return NULL;
487+
goto fail;
492488
}
493489
}
494490
if (!buffer_write_int32(buffer, (int32_t)request_id) ||
@@ -498,20 +494,12 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
498494
collection_name_length + 1) ||
499495
!buffer_write_int32(buffer, (int32_t)num_to_skip) ||
500496
!buffer_write_int32(buffer, (int32_t)num_to_return)) {
501-
destroy_codec_options(&options);
502-
buffer_free(buffer);
503-
PyMem_Free(collection_name);
504-
Py_XDECREF(cluster_time);
505-
return NULL;
497+
goto fail;
506498
}
507499

508500
begin = buffer_get_position(buffer);
509501
if (!write_dict(state->_cbson, buffer, query, check_keys, &options, 1)) {
510-
destroy_codec_options(&options);
511-
buffer_free(buffer);
512-
PyMem_Free(collection_name);
513-
Py_XDECREF(cluster_time);
514-
return NULL;
502+
goto fail;
515503
}
516504

517505
/* back up a byte and write $clusterTime */
@@ -522,19 +510,11 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
522510
buffer_update_position(buffer, buffer_get_position(buffer) - 1);
523511
if (!write_pair(state->_cbson, buffer, "$clusterTime", 12, cluster_time,
524512
0, &options, 1)) {
525-
destroy_codec_options(&options);
526-
buffer_free(buffer);
527-
PyMem_Free(collection_name);
528-
Py_DECREF(cluster_time);
529-
return NULL;
513+
goto fail;
530514
}
531515

532516
if (!buffer_write_bytes(buffer, &zero, 1)) {
533-
destroy_codec_options(&options);
534-
buffer_free(buffer);
535-
PyMem_Free(collection_name);
536-
Py_DECREF(cluster_time);
537-
return NULL;
517+
goto fail;
538518
}
539519

540520
length = buffer_get_position(buffer) - begin;
@@ -543,14 +523,10 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
543523
/* undo popping $clusterTime */
544524
if (-1 == PyMapping_SetItemString(
545525
query, "$clusterTime", cluster_time)) {
546-
destroy_codec_options(&options);
547-
buffer_free(buffer);
548-
PyMem_Free(collection_name);
549-
Py_DECREF(cluster_time);
550-
return NULL;
526+
goto fail;
551527
}
552528

553-
Py_DECREF(cluster_time);
529+
Py_CLEAR(cluster_time);
554530
}
555531

556532
max_size = buffer_get_position(buffer) - begin;
@@ -559,17 +535,12 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
559535
begin = buffer_get_position(buffer);
560536
if (!write_dict(state->_cbson, buffer, field_selector, 0,
561537
&options, 1)) {
562-
destroy_codec_options(&options);
563-
buffer_free(buffer);
564-
PyMem_Free(collection_name);
565-
return NULL;
538+
goto fail;
566539
}
567540
cur_size = buffer_get_position(buffer) - begin;
568541
max_size = (cur_size > max_size) ? cur_size : max_size;
569542
}
570543

571-
PyMem_Free(collection_name);
572-
573544
message_length = buffer_get_position(buffer) - length_location;
574545
buffer_write_int32_at_position(
575546
buffer, length_location, (int32_t)message_length);
@@ -579,8 +550,12 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
579550
buffer_get_buffer(buffer),
580551
buffer_get_position(buffer),
581552
max_size);
553+
554+
fail:
555+
PyMem_Free(collection_name);
582556
destroy_codec_options(&options);
583557
buffer_free(buffer);
558+
Py_XDECREF(cluster_time);
584559
return result;
585560
}
586561

@@ -1142,11 +1117,11 @@ _batched_op_msg(
11421117
int size_location;
11431118
int position;
11441119
int length;
1145-
PyObject* max_bson_size_obj;
1146-
PyObject* max_write_batch_size_obj;
1147-
PyObject* max_message_size_obj;
1148-
PyObject* doc;
1149-
PyObject* iterator;
1120+
PyObject* max_bson_size_obj = NULL;
1121+
PyObject* max_write_batch_size_obj = NULL;
1122+
PyObject* max_message_size_obj = NULL;
1123+
PyObject* doc = NULL;
1124+
PyObject* iterator = NULL;
11501125
char* flags = ack ? "\x00\x00\x00\x00" : "\x02\x00\x00\x00";
11511126

11521127
max_bson_size_obj = PyObject_GetAttrString(ctx, "max_bson_size");
@@ -1209,23 +1184,23 @@ _batched_op_msg(
12091184
case _INSERT:
12101185
{
12111186
if (!buffer_write_bytes(buffer, "documents\x00", 10))
1212-
goto cmdfail;
1187+
goto fail;
12131188
break;
12141189
}
12151190
case _UPDATE:
12161191
{
12171192
/* MongoDB does key validation for update. */
12181193
check_keys = 0;
12191194
if (!buffer_write_bytes(buffer, "updates\x00", 8))
1220-
goto cmdfail;
1195+
goto fail;
12211196
break;
12221197
}
12231198
case _DELETE:
12241199
{
12251200
/* Never check keys in a delete command. */
12261201
check_keys = 0;
12271202
if (!buffer_write_bytes(buffer, "deletes\x00", 8))
1228-
goto cmdfail;
1203+
goto fail;
12291204
break;
12301205
}
12311206
default:
@@ -1255,7 +1230,7 @@ _batched_op_msg(
12551230
int unacked_doc_too_large = 0;
12561231
if (!write_dict(state->_cbson, buffer, doc, check_keys,
12571232
&options, 1)) {
1258-
goto cmditerfail;
1233+
goto fail;
12591234
}
12601235
cur_size = buffer_get_position(buffer) - cur_doc_begin;
12611236

@@ -1285,7 +1260,7 @@ _batched_op_msg(
12851260
Py_DECREF(DocumentTooLarge);
12861261
}
12871262
}
1288-
goto cmditerfail;
1263+
goto fail;
12891264
}
12901265
/* We have enough data, return this batch. */
12911266
if (buffer_get_position(buffer) > max_message_size) {
@@ -1294,10 +1269,11 @@ _batched_op_msg(
12941269
* of the last document encoded.
12951270
*/
12961271
buffer_update_position(buffer, cur_doc_begin);
1272+
Py_CLEAR(doc);
12971273
break;
12981274
}
12991275
if (PyList_Append(to_publish, doc) < 0) {
1300-
goto cmditerfail;
1276+
goto fail;
13011277
}
13021278
Py_CLEAR(doc);
13031279
idx += 1;
@@ -1306,21 +1282,20 @@ _batched_op_msg(
13061282
break;
13071283
}
13081284
}
1309-
Py_DECREF(iterator);
1285+
Py_CLEAR(iterator);
13101286

13111287
if (PyErr_Occurred()) {
1312-
goto cmdfail;
1288+
goto fail;
13131289
}
13141290

13151291
position = buffer_get_position(buffer);
13161292
length = position - size_location;
13171293
buffer_write_int32_at_position(buffer, size_location, (int32_t)length);
13181294
return 1;
13191295

1320-
cmditerfail:
1296+
fail:
13211297
Py_XDECREF(doc);
1322-
Py_DECREF(iterator);
1323-
cmdfail:
1298+
Py_XDECREF(iterator);
13241299
return 0;
13251300
}
13261301

@@ -1466,10 +1441,10 @@ _batched_write_command(
14661441
int lst_len_loc;
14671442
int position;
14681443
int length;
1469-
PyObject* max_bson_size_obj;
1470-
PyObject* max_write_batch_size_obj;
1471-
PyObject* doc;
1472-
PyObject* iterator;
1444+
PyObject* max_bson_size_obj = NULL;
1445+
PyObject* max_write_batch_size_obj = NULL;
1446+
PyObject* doc = NULL;
1447+
PyObject* iterator = NULL;
14731448

14741449
max_bson_size_obj = PyObject_GetAttrString(ctx, "max_bson_size");
14751450
#if PY_MAJOR_VERSION >= 3
@@ -1524,23 +1499,23 @@ _batched_write_command(
15241499
case _INSERT:
15251500
{
15261501
if (!buffer_write_bytes(buffer, "documents\x00", 10))
1527-
goto cmdfail;
1502+
goto fail;
15281503
break;
15291504
}
15301505
case _UPDATE:
15311506
{
15321507
/* MongoDB does key validation for update. */
15331508
check_keys = 0;
15341509
if (!buffer_write_bytes(buffer, "updates\x00", 8))
1535-
goto cmdfail;
1510+
goto fail;
15361511
break;
15371512
}
15381513
case _DELETE:
15391514
{
15401515
/* Never check keys in a delete command. */
15411516
check_keys = 0;
15421517
if (!buffer_write_bytes(buffer, "deletes\x00", 8))
1543-
goto cmdfail;
1518+
goto fail;
15441519
break;
15451520
}
15461521
default:
@@ -1575,25 +1550,23 @@ _batched_write_command(
15751550
int cur_doc_begin;
15761551
int cur_size;
15771552
int enough_data = 0;
1578-
int enough_documents = 0;
15791553
char key[16];
15801554
INT2STRING(key, idx);
15811555
if (!buffer_write_bytes(buffer, "\x03", 1) ||
15821556
!buffer_write_bytes(buffer, key, (int)strlen(key) + 1)) {
1583-
goto cmditerfail;
1557+
goto fail;
15841558
}
15851559
cur_doc_begin = buffer_get_position(buffer);
15861560
if (!write_dict(state->_cbson, buffer, doc,
15871561
check_keys, &options, 1)) {
1588-
goto cmditerfail;
1562+
goto fail;
15891563
}
15901564

15911565
/* We have enough data, return this batch.
15921566
* max_cmd_size accounts for the two trailing null bytes.
15931567
*/
15941568
enough_data = (buffer_get_position(buffer) > max_cmd_size);
1595-
enough_documents = (idx >= max_write_batch_size);
1596-
if (enough_data || enough_documents) {
1569+
if (enough_data) {
15971570
cur_size = buffer_get_position(buffer) - cur_doc_begin;
15981571

15991572
/* This single document is too large for the command. */
@@ -1614,30 +1587,35 @@ _batched_write_command(
16141587
Py_DECREF(DocumentTooLarge);
16151588
}
16161589
}
1617-
goto cmditerfail;
1590+
goto fail;
16181591
}
16191592
/*
16201593
* Roll the existing buffer back to the beginning
16211594
* of the last document encoded.
16221595
*/
16231596
buffer_update_position(buffer, sub_doc_begin);
1597+
Py_CLEAR(doc);
16241598
break;
16251599
}
16261600
if (PyList_Append(to_publish, doc) < 0) {
1627-
goto cmditerfail;
1601+
goto fail;
16281602
}
16291603
Py_CLEAR(doc);
16301604
idx += 1;
1605+
/* We have enough documents, return this batch. */
1606+
if (idx == max_write_batch_size) {
1607+
break;
1608+
}
16311609
}
1632-
Py_DECREF(iterator);
1610+
Py_CLEAR(iterator);
16331611

16341612
if (PyErr_Occurred()) {
1635-
goto cmdfail;
1613+
goto fail;
16361614
}
16371615

1638-
if (!buffer_write_bytes(buffer, "\x00\x00", 2))
1639-
goto cmdfail;
1640-
1616+
if (!buffer_write_bytes(buffer, "\x00\x00", 2)) {
1617+
goto fail;
1618+
}
16411619

16421620
position = buffer_get_position(buffer);
16431621
length = position - lst_len_loc - 1;
@@ -1646,10 +1624,9 @@ _batched_write_command(
16461624
buffer_write_int32_at_position(buffer, cmd_len_loc, (int32_t)length);
16471625
return 1;
16481626

1649-
cmditerfail:
1627+
fail:
16501628
Py_XDECREF(doc);
1651-
Py_DECREF(iterator);
1652-
cmdfail:
1629+
Py_XDECREF(iterator);
16531630
return 0;
16541631
}
16551632

0 commit comments

Comments
 (0)