Skip to content

Commit 6ad0608

Browse files
committed
CDRIVER-3306 non-empty server reply does not imply valid stream
1 parent 214c433 commit 6ad0608

File tree

4 files changed

+125
-2
lines changed

4 files changed

+125
-2
lines changed

src/libmongoc/src/mongoc/mongoc-cluster-private.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ mongoc_cluster_stream_for_server (mongoc_cluster_t *cluster,
134134
bson_t *reply,
135135
bson_error_t *error);
136136

137+
bool
138+
mongoc_cluster_stream_valid (mongoc_cluster_t *cluster,
139+
mongoc_server_stream_t *server_stream);
140+
137141
bool
138142
mongoc_cluster_run_command_monitored (mongoc_cluster_t *cluster,
139143
mongoc_cmd_t *cmd,

src/libmongoc/src/mongoc/mongoc-cluster.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,46 @@ mongoc_cluster_fetch_stream_single (mongoc_cluster_t *cluster,
20642064
}
20652065

20662066

2067+
/*
2068+
*--------------------------------------------------------------------------
2069+
*
2070+
* mongoc_cluster_stream_valid --
2071+
*
2072+
* Internal function to determine if @server_stream is valid and
2073+
* associated with the given cluster.
2074+
*
2075+
* Returns:
2076+
* true if @server_stream is not NULL, hasn't been freed or changed;
2077+
* otherwise false.
2078+
*
2079+
*--------------------------------------------------------------------------
2080+
*/
2081+
2082+
bool
2083+
mongoc_cluster_stream_valid (mongoc_cluster_t *cluster,
2084+
mongoc_server_stream_t *server_stream)
2085+
{
2086+
mongoc_server_stream_t *tmp_stream;
2087+
bool ret = true;
2088+
2089+
BSON_ASSERT (cluster);
2090+
2091+
if (!server_stream) {
2092+
return false;
2093+
}
2094+
2095+
tmp_stream = mongoc_cluster_stream_for_server (
2096+
cluster, server_stream->sd->id, false, NULL, NULL, NULL);
2097+
if (!tmp_stream || tmp_stream->stream != server_stream->stream) {
2098+
/* stream was freed, or has changed. */
2099+
ret = false;
2100+
}
2101+
2102+
mongoc_server_stream_cleanup (tmp_stream);
2103+
2104+
return ret;
2105+
}
2106+
20672107
mongoc_server_stream_t *
20682108
_mongoc_cluster_create_server_stream (mongoc_topology_t *topology,
20692109
uint32_t server_id,

src/libmongoc/src/mongoc/mongoc-write-command.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,8 @@ _mongoc_write_opquery (mongoc_write_command_t *command,
857857

858858
if (!ret) {
859859
result->failed = true;
860-
if (bson_empty (&reply)) {
860+
if (bson_empty (&reply) ||
861+
!mongoc_cluster_stream_valid (&client->cluster, server_stream)) {
861862
/* assembling failed, or a network error running the command */
862863
result->must_stop = true;
863864
}
@@ -1593,4 +1594,4 @@ _mongoc_write_error_update_if_unsupported_storage_engine (bool cmd_ret,
15931594
return true;
15941595
}
15951596
return false;
1596-
}
1597+
}

src/libmongoc/tests/test-mongoc-cluster.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,81 @@ test_write_command_disconnect (void *ctx)
398398
}
399399

400400

401+
/* Test for CDRIVER-3306 - Do not assume non-empty server reply implies stream
402+
* is still valid */
403+
static void
404+
test_cluster_command_notmaster (void)
405+
{
406+
mock_server_t *server;
407+
mongoc_uri_t *uri;
408+
mongoc_client_t *client;
409+
mongoc_collection_t *collection;
410+
mongoc_bulk_operation_t *bulk;
411+
bson_t *doc;
412+
uint32_t i;
413+
bson_error_t error;
414+
future_t *future;
415+
request_t *request;
416+
bson_t reply;
417+
418+
if (!TestSuite_CheckMockServerAllowed ()) {
419+
return;
420+
}
421+
422+
server = mock_server_new ();
423+
mock_server_run (server);
424+
425+
/* server is "recovering": not master, not secondary */
426+
mock_server_auto_ismaster (server,
427+
"{'ok': 1,"
428+
" 'maxWireVersion': %d,"
429+
" 'ismaster': false,"
430+
" 'secondary': false,"
431+
" 'setName': 'rs',"
432+
" 'hosts': ['%s']}",
433+
WIRE_VERSION_OP_MSG - 1,
434+
mock_server_get_host_and_port (server));
435+
436+
uri = mongoc_uri_copy (mock_server_get_uri (server));
437+
mongoc_uri_set_option_as_utf8 (uri, "replicaSet", "rs");
438+
439+
client = mongoc_client_new_from_uri (uri);
440+
441+
collection = mongoc_client_get_collection (client, "db", "test");
442+
/* use an unordered bulk write, so it attempts to continue on error */
443+
bulk = mongoc_collection_create_bulk_operation_with_opts (
444+
collection, tmp_bson("{'ordered': false}"));
445+
/* Set a "hint" aka "server id" to force the write to be directed to the
446+
* non-primary */
447+
mongoc_bulk_operation_set_hint (bulk, 1);
448+
doc = tmp_bson("{'foo': 1}");
449+
/* Have enough inserts to ensure some batch splits */
450+
for (i = 0; i < 10001; i++) {
451+
mongoc_bulk_operation_insert_with_opts (bulk, doc, NULL, &error);
452+
}
453+
/* If CDRIVER-3306 is still present, then this operation will trigger a
454+
* segfault; once the below non-empty reply is received from the mock
455+
* server, the stream will be invalidated but the non-empty reply will be
456+
* interpreted as meaning it is OK to proceed with the other operations */
457+
future = future_bulk_operation_execute (bulk, &reply, &error);
458+
459+
request = mock_server_receives_request (server);
460+
mock_server_replies_simple (
461+
request, "{ 'code': 10107, 'errmsg': 'not master', 'ok': 0 }");
462+
ASSERT (future_wait (future));
463+
464+
mongoc_client_destroy (client);
465+
mongoc_collection_destroy (collection);
466+
mongoc_bulk_operation_destroy (bulk);
467+
bson_destroy (&reply);
468+
request_destroy (request);
469+
future_destroy (future);
470+
mongoc_uri_destroy (uri);
471+
mock_server_destroy (server);
472+
473+
}
474+
475+
401476
typedef struct {
402477
int calls;
403478
bson_t *cluster_time;
@@ -1793,6 +1868,9 @@ test_cluster_install (TestSuite *suite)
17931868
TestSuite_AddMockServerTest (suite,
17941869
"/Cluster/command/timeout/pooled",
17951870
test_cluster_command_timeout_pooled);
1871+
TestSuite_AddMockServerTest (suite,
1872+
"/Cluster/command/notmaster",
1873+
test_cluster_command_notmaster);
17961874
TestSuite_AddFull (suite,
17971875
"/Cluster/write_command/disconnect",
17981876
test_write_command_disconnect,

0 commit comments

Comments
 (0)