Skip to content

Commit d15b0e4

Browse files
committed
CDRIVER-699 implement serverSelectionTryOnce
Server Selection Spec: Single-threaded drivers MUST provide a "serverSelectionTryOnce" mode, in which the driver scans the topology exactly once after server selection fails, then either selects a server or raises an error. The serverSelectionTryOnce option MUST be true by default. If it is set false, then the driver repeatedly searches for an appropriate server for up to serverSelectionTimeoutMS milliseconds (pausing minHeartbeatFrequencyMS between attempts).
1 parent 9e280c8 commit d15b0e4

File tree

5 files changed

+148
-19
lines changed

5 files changed

+148
-19
lines changed

src/mongoc/mongoc-error.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ typedef enum
9494
MONGOC_ERROR_QUERY_NOT_TAILABLE = 13051,
9595

9696
MONGOC_ERROR_SERVER_SELECTION_BAD_WIRE_VERSION,
97-
MONGOC_ERROR_SERVER_SELECTION_TIMEOUT,
97+
MONGOC_ERROR_SERVER_SELECTION_FAILURE,
9898
MONGOC_ERROR_SERVER_SELECTION_INVALID_ID,
9999

100100
/* Dup with query failure. */

src/mongoc/mongoc-topology.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -411,10 +411,14 @@ mongoc_topology_select (mongoc_topology_t *topology,
411411
return mongoc_server_description_new_copy(selected_server);
412412
}
413413

414+
if (topology->server_selection_try_once) {
415+
goto FAIL;
416+
}
417+
414418
/* error if we've timed out */
415419
now = bson_get_monotonic_time();
416420
if (now >= expire_at) {
417-
goto TIMEOUT;
421+
goto FAIL;
418422
}
419423

420424
/* rescan */
@@ -443,16 +447,16 @@ mongoc_topology_select (mongoc_topology_t *topology,
443447

444448
if (r == ETIMEDOUT) {
445449
/* handle timeouts */
446-
goto TIMEOUT;
450+
goto FAIL;
447451
} else if (r) {
448452
/* TODO handle other errors */
449-
goto TIMEOUT;
453+
goto FAIL;
450454
}
451455

452456
now = bson_get_monotonic_time ();
453457

454458
if (now > expire_at) {
455-
goto TIMEOUT;
459+
goto FAIL;
456460
}
457461
} else {
458462
selected_server = mongoc_server_description_new_copy(selected_server);
@@ -461,11 +465,15 @@ mongoc_topology_select (mongoc_topology_t *topology,
461465
}
462466
}
463467

464-
TIMEOUT:
468+
FAIL:
469+
topology->stale = true;
465470
bson_set_error(error,
466471
MONGOC_ERROR_SERVER_SELECTION,
467-
MONGOC_ERROR_SERVER_SELECTION_TIMEOUT,
472+
MONGOC_ERROR_SERVER_SELECTION_FAILURE,
473+
topology->server_selection_try_once ?
474+
"No suitable servers found" :
468475
"Timed out trying to select a server");
476+
469477
return NULL;
470478
}
471479

tests/test-mongoc-client.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,14 +438,15 @@ test_unavailable_seeds (void)
438438
mongoc_cursor_t *cursor;
439439
bson_t query = BSON_INITIALIZER;
440440
const bson_t *doc;
441+
bson_error_t error;
441442

442443
const char* uri_strs[] = {
443-
"mongodb://a:1/?connectTimeoutMS=1&serverSelectionTimeoutMS=1",
444-
"mongodb://a:1,a:2/?connectTimeoutMS=1&serverSelectionTimeoutMS=1",
445-
"mongodb://a:1,a:2/?replicaSet=r&connectTimeoutMS=1&serverSelectionTimeoutMS=1",
446-
"mongodb://u:p@a:1/?connectTimeoutMS=1&serverSelectionTimeoutMS=1",
447-
"mongodb://u:p@a:1,a:2/?connectTimeoutMS=1&serverSelectionTimeoutMS=1",
448-
"mongodb://u:p@a:1,a:2/?replicaSet=r&connectTimeoutMS=1&serverSelectionTimeoutMS=1",
444+
"mongodb://a:1/?connectTimeoutMS=1",
445+
"mongodb://a:1,a:2/?connectTimeoutMS=1",
446+
"mongodb://a:1,a:2/?replicaSet=r&connectTimeoutMS=1",
447+
"mongodb://u:p@a:1/?connectTimeoutMS=1",
448+
"mongodb://u:p@a:1,a:2/?connectTimeoutMS=1",
449+
"mongodb://u:p@a:1,a:2/?replicaSet=r&connectTimeoutMS=1",
449450
};
450451

451452
int i;
@@ -470,6 +471,9 @@ test_unavailable_seeds (void)
470471
NULL);
471472

472473
assert (! mongoc_cursor_next (cursor, &doc));
474+
assert (mongoc_cursor_error (cursor, &error));
475+
ASSERT_CMPINT (error.domain, ==, MONGOC_ERROR_SERVER_SELECTION);
476+
ASSERT_CMPINT (error.code, ==, MONGOC_ERROR_SERVER_SELECTION_FAILURE);
473477

474478
mongoc_cursor_destroy (cursor);
475479
mongoc_collection_destroy (collection);

tests/test-mongoc-topology-reconcile.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,10 @@ _test_topology_reconcile_rs (bool pooled)
135135
RS_RESPONSE_TO_ISMASTER (server1, true, false, server0, server1);
136136

137137
/* provide secondary in seed list */
138+
/* TODO: CDRIVER-751 serverSelectionTryOnce=false shouldn't be needed */
138139
uri_str = bson_strdup_printf (
139-
"mongodb://%s/?replicaSet=rs&connectTimeoutMS=10",
140+
"mongodb://%s/?replicaSet=rs&connectTimeoutMS=10"
141+
"&serverSelectionTryOnce=false",
140142
mock_server_get_host_and_port (server0));
141143

142144
uri = mongoc_uri_new (uri_str);
@@ -155,6 +157,7 @@ _test_topology_reconcile_rs (bool pooled)
155157

156158
/*
157159
* server0 is selected, server1 is discovered and added to scanner.
160+
* TODO: CDRIVER-751 actually it isn't.
158161
*/
159162
assert (selects_server (client, secondary_read_prefs, server0));
160163
assert (get_node (client->topology->scanner,

tests/test-mongoc-topology.c

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,120 @@ test_server_selection_try_once_option (void)
130130
}
131131
}
132132

133+
static void
134+
_test_server_selection (bool try_once)
135+
{
136+
mock_server_t *server;
137+
char *secondary_response;
138+
char *primary_response;
139+
mongoc_uri_t *uri;
140+
mongoc_client_t *client;
141+
mongoc_read_prefs_t *primary_pref;
142+
future_t *future;
143+
bson_error_t error;
144+
request_t *request;
145+
mongoc_server_description_t *sd;
146+
147+
server = mock_server_new ();
148+
mock_server_set_request_timeout_msec (server, 600);
149+
mock_server_run (server);
150+
151+
secondary_response = bson_strdup_printf (
152+
"{'ok': 1, "
153+
" 'ismaster': false,"
154+
" 'secondary': true,"
155+
" 'setName': 'rs',"
156+
" 'hosts': ['%s']}",
157+
mock_server_get_host_and_port (server));
158+
159+
primary_response = bson_strdup_printf (
160+
"{'ok': 1, "
161+
" 'ismaster': true,"
162+
" 'setName': 'rs',"
163+
" 'hosts': ['%s']}",
164+
mock_server_get_host_and_port (server));
165+
166+
uri = mongoc_uri_copy (mock_server_get_uri (server));
167+
mongoc_uri_set_option_as_utf8 (uri, "replicaSet", "rs");
168+
mongoc_uri_set_option_as_int32 (uri, "heartbeatFrequencyMS", 500);
169+
mongoc_uri_set_option_as_int32 (uri, "serverSelectionTimeoutMS", 100);
170+
if (!try_once) {
171+
/* serverSelectionTryOnce is on by default */
172+
mongoc_uri_set_option_as_bool (uri, "serverSelectionTryOnce", false);
173+
}
174+
175+
client = mongoc_client_new_from_uri (uri);
176+
primary_pref = mongoc_read_prefs_new (MONGOC_READ_PRIMARY);
177+
178+
/* no primary, selection fails after one try */
179+
future = future_topology_select (client->topology, MONGOC_SS_READ,
180+
primary_pref, 15, &error);
181+
assert (request = mock_server_receives_ismaster (server));
182+
mock_server_replies_simple (request, secondary_response);
183+
184+
if (try_once) {
185+
/* selection fails without another ismaster call */
186+
assert (!mock_server_receives_ismaster (server));
187+
} else {
188+
/* TODO: SPEC-289 the driver thinks there's time for one more check
189+
* but there isn't, since selection time remaining < heartbeat.
190+
* the test works until SPEC-289 is resolved and implemented.
191+
*/
192+
assert (request = mock_server_receives_ismaster (server));
193+
mock_server_replies_simple (request, secondary_response);
194+
request_destroy (request);
195+
}
196+
197+
/* selection fails */
198+
assert (!future_get_mongoc_server_description_ptr (future));
199+
ASSERT_CMPINT (error.domain, ==, MONGOC_ERROR_SERVER_SELECTION);
200+
ASSERT_CMPINT (error.code, ==, MONGOC_ERROR_SERVER_SELECTION_FAILURE);
201+
202+
if (try_once) {
203+
ASSERT_CMPSTR ("No suitable servers found", error.message);
204+
} else {
205+
ASSERT_CMPSTR ("Timed out trying to select a server", error.message);
206+
}
207+
208+
assert (client->topology->stale);
209+
future_destroy (future);
210+
211+
_mongoc_usleep (510 * 1000); /* one heartbeat, plus a few milliseconds */
212+
213+
/* second selection, now we try ismaster again */
214+
future = future_topology_select (client->topology, MONGOC_SS_READ,
215+
primary_pref, 15, &error);
216+
assert (request = mock_server_receives_ismaster (server));
217+
218+
/* the secondary is now primary, selection succeeds */
219+
mock_server_replies_simple (request, primary_response);
220+
sd = future_get_mongoc_server_description_ptr (future);
221+
assert (sd);
222+
assert (!client->topology->stale);
223+
request_destroy (request);
224+
future_destroy (future);
225+
226+
mongoc_server_description_destroy (sd);
227+
mongoc_read_prefs_destroy (primary_pref);
228+
mongoc_client_destroy (client);
229+
mongoc_uri_destroy (uri);
230+
bson_free (secondary_response);
231+
bson_free (primary_response);
232+
mock_server_destroy (server);
233+
}
234+
235+
static void
236+
test_server_selection_try_once (void)
237+
{
238+
_test_server_selection (true);
239+
}
240+
241+
static void
242+
test_server_selection_try_once_false (void)
243+
{
244+
_test_server_selection (false);
245+
}
246+
133247
static void
134248
test_topology_invalidate_server (void)
135249
{
@@ -356,7 +470,8 @@ test_cooldown_rs (void)
356470
}
357471

358472
uri_str = bson_strdup_printf (
359-
"mongodb://localhost:%hu/?replicaSet=rs&serverSelectionTimeoutMS=1000",
473+
"mongodb://localhost:%hu/?replicaSet=rs&serverSelectionTimeoutMS=1000"
474+
"&serverSelectionTryOnce=false",
360475
mock_server_get_port (servers[0]));
361476

362477
client = mongoc_client_new (uri_str);
@@ -440,10 +555,9 @@ test_topology_install (TestSuite *suite)
440555
{
441556
TestSuite_Add (suite, "/Topology/client_creation", test_topology_client_creation);
442557
TestSuite_Add (suite, "/Topology/client_pool_creation", test_topology_client_pool_creation);
443-
TestSuite_Add (suite,
444-
"/Topology/server_selection_try_once_option",
445-
test_server_selection_try_once_option);
446-
558+
TestSuite_Add (suite, "/Topology/server_selection_try_once_option", test_server_selection_try_once_option);
559+
TestSuite_Add (suite, "/Topology/server_selection_try_once", test_server_selection_try_once);
560+
TestSuite_Add (suite, "/Topology/server_selection_try_once_false", test_server_selection_try_once_false);
447561
TestSuite_Add (suite, "/Topology/invalidate_server", test_topology_invalidate_server);
448562
TestSuite_Add (suite, "/Topology/invalid_cluster_node", test_invalid_cluster_node);
449563
TestSuite_Add (suite, "/Topology/max_wire_version_race_condition", test_max_wire_version_race_condition);

0 commit comments

Comments
 (0)