Skip to content

Commit f103e06

Browse files
CDRIVER-2873 retryable reads
Co-authored-by: Haris Sheikh <[email protected]>
1 parent a02b8bc commit f103e06

File tree

66 files changed

+17917
-44
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+17917
-44
lines changed

src/libmongoc/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ set (test-libmongoc-sources
787787
${PROJECT_SOURCE_DIR}/tests/test-mongoc-read-write-concern.c
788788
${PROJECT_SOURCE_DIR}/tests/test-mongoc-read-prefs.c
789789
${PROJECT_SOURCE_DIR}/tests/test-mongoc-retryable-writes.c
790+
${PROJECT_SOURCE_DIR}/tests/test-mongoc-retryable-reads.c
790791
${PROJECT_SOURCE_DIR}/tests/test-mongoc-rpc.c
791792
${PROJECT_SOURCE_DIR}/tests/test-mongoc-sample-commands.c
792793
${PROJECT_SOURCE_DIR}/tests/test-mongoc-scram.c

src/libmongoc/doc/mongoc_uri_t.rst

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ Connection Options
9090
========================================== ================================= ============================================================================================================================================================================================================================================
9191
Constant Key Description
9292
========================================== ================================= ============================================================================================================================================================================================================================================
93+
MONGOC_URI_RETRYREADS retryreads If "true" and the server is a MongoDB 3.6+ standalone, replica set, or sharded cluster, the driver safely retries a read that failed due to a network error or replica set failover.
9394
MONGOC_URI_RETRYWRITES retrywrites If "true" and the server is a MongoDB 3.6+ replica set or sharded cluster, the driver safely retries a write that failed due to a network error or replica set failover. Only inserts, updates of single documents, or deletes of single
9495
documents are retried.
9596
MONGOC_URI_APPNAME appname The client application name. This value is used by MongoDB when it logs connection information and profile information, such as slow queries.

src/libmongoc/src/mongoc/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ set (src_libmongoc_src_mongoc_DIST_noinst_hs
9999
mongoc-cyrus-private.h
100100
mongoc-database-private.h
101101
mongoc-errno-private.h
102+
mongoc-error-private.h
102103
mongoc-find-and-modify-private.h
103104
mongoc-gridfs-bucket-file-private.h
104105
mongoc-gridfs-bucket-private.h

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

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ _mongoc_aggregate (mongoc_client_t *client,
4242
const mongoc_read_concern_t *default_rc,
4343
const mongoc_write_concern_t *default_wc);
4444

45+
bool
46+
_has_write_key (bson_iter_t *iter);
4547

4648
BSON_END_DECLS
4749

src/libmongoc/src/mongoc/mongoc-aggregate.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
*--------------------------------------------------------------------------
3838
*/
3939

40-
static bool
40+
bool
4141
_has_write_key (bson_iter_t *iter)
4242
{
4343
bson_iter_t stage;

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

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ BSON_BEGIN_DECLS
6161
#define WIRE_VERSION_OP_MSG 6
6262
/* first version to support array filters for "update" command */
6363
#define WIRE_VERSION_ARRAY_FILTERS 6
64+
/* first version to support retryable reads */
65+
#define WIRE_VERSION_RETRY_READS 6
6466
/* first version to support retryable writes */
6567
#define WIRE_VERSION_RETRY_WRITES 6
6668
/* version corresponding to server 4.0 release */

src/libmongoc/src/mongoc/mongoc-client.c

+77-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "mongoc/mongoc-database-private.h"
4242
#include "mongoc/mongoc-gridfs-private.h"
4343
#include "mongoc/mongoc-error.h"
44+
#include "mongoc/mongoc-error-private.h"
4445
#include "mongoc/mongoc-log.h"
4546
#include "mongoc/mongoc-queue-private.h"
4647
#include "mongoc/mongoc-socket.h"
@@ -1603,7 +1604,8 @@ _mongoc_client_retryable_write_command_with_stream (
16031604
&client->cluster, &parts->assembled, reply, error);
16041605

16051606
if (is_retryable) {
1606-
_mongoc_write_error_update_if_unsupported_storage_engine (ret, error, reply);
1607+
_mongoc_write_error_update_if_unsupported_storage_engine (
1608+
ret, error, reply);
16071609
}
16081610

16091611
/* If a retryable error is encountered and the write is retryable, select
@@ -1642,9 +1644,75 @@ _mongoc_client_retryable_write_command_with_stream (
16421644
}
16431645

16441646

1647+
static bool
1648+
_mongoc_client_retryable_read_command_with_stream (
1649+
mongoc_client_t *client,
1650+
mongoc_cmd_parts_t *parts,
1651+
mongoc_server_stream_t *server_stream,
1652+
bson_t *reply,
1653+
bson_error_t *error)
1654+
{
1655+
mongoc_server_stream_t *retry_server_stream = NULL;
1656+
bool is_retryable = true;
1657+
bool ret;
1658+
bson_t reply_local;
1659+
1660+
if (reply == NULL) {
1661+
reply = &reply_local;
1662+
}
1663+
1664+
ENTRY;
1665+
1666+
BSON_ASSERT (parts->is_retryable_read);
1667+
1668+
retry:
1669+
ret = mongoc_cluster_run_command_monitored (
1670+
&client->cluster, &parts->assembled, reply, error);
1671+
1672+
/* If a retryable error is encountered and the read is retryable, select
1673+
* a new readable stream and retry. If server selection fails or the selected
1674+
* server does not support retryable reads, fall through and allow the
1675+
* original error to be reported. */
1676+
if (is_retryable &&
1677+
_mongoc_read_error_get_type (ret, error, reply) ==
1678+
MONGOC_READ_ERR_RETRY) {
1679+
bson_error_t ignored_error;
1680+
1681+
/* each read command may be retried at most once */
1682+
is_retryable = false;
1683+
1684+
if (retry_server_stream) {
1685+
mongoc_server_stream_cleanup (retry_server_stream);
1686+
}
1687+
1688+
retry_server_stream =
1689+
mongoc_cluster_stream_for_reads (&client->cluster,
1690+
parts->read_prefs,
1691+
parts->assembled.session,
1692+
NULL,
1693+
&ignored_error);
1694+
1695+
if (retry_server_stream &&
1696+
retry_server_stream->sd->max_wire_version >=
1697+
WIRE_VERSION_RETRY_READS) {
1698+
parts->assembled.server_stream = retry_server_stream;
1699+
bson_destroy (reply);
1700+
GOTO (retry);
1701+
}
1702+
}
1703+
1704+
if (retry_server_stream) {
1705+
mongoc_server_stream_cleanup (retry_server_stream);
1706+
}
1707+
1708+
RETURN (ret);
1709+
}
1710+
1711+
16451712
static bool
16461713
_mongoc_client_command_with_stream (mongoc_client_t *client,
16471714
mongoc_cmd_parts_t *parts,
1715+
const mongoc_read_prefs_t *read_prefs,
16481716
mongoc_server_stream_t *server_stream,
16491717
bson_t *reply,
16501718
bson_error_t *error)
@@ -1662,6 +1730,11 @@ _mongoc_client_command_with_stream (mongoc_client_t *client,
16621730
client, parts, server_stream, reply, error));
16631731
}
16641732

1733+
if (parts->is_retryable_read) {
1734+
RETURN (_mongoc_client_retryable_read_command_with_stream (
1735+
client, parts, server_stream, reply, error));
1736+
}
1737+
16651738
RETURN (mongoc_cluster_run_command_monitored (
16661739
&client->cluster, &parts->assembled, reply, error));
16671740
}
@@ -1705,7 +1778,7 @@ mongoc_client_command_simple (mongoc_client_t *client,
17051778

17061779
if (server_stream) {
17071780
ret = _mongoc_client_command_with_stream (
1708-
client, &parts, server_stream, reply, error);
1781+
client, &parts, read_prefs, server_stream, reply, error);
17091782
} else {
17101783
/* reply initialized by mongoc_cluster_stream_for_reads */
17111784
ret = false;
@@ -1914,7 +1987,7 @@ _mongoc_client_command_with_opts (mongoc_client_t *client,
19141987
}
19151988

19161989
ret = _mongoc_client_command_with_stream (
1917-
client, &parts, server_stream, reply_ptr, error);
1990+
client, &parts, user_prefs, server_stream, reply_ptr, error);
19181991

19191992
reply_initialized = true;
19201993

@@ -2071,7 +2144,7 @@ mongoc_client_command_simple_with_server_id (
20712144
parts.read_prefs = read_prefs;
20722145

20732146
ret = _mongoc_client_command_with_stream (
2074-
client, &parts, server_stream, reply, error);
2147+
client, &parts, read_prefs, server_stream, reply, error);
20752148

20762149
mongoc_cmd_parts_cleanup (&parts);
20772150
mongoc_server_stream_cleanup (server_stream);

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

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
BSON_BEGIN_DECLS
3838

39+
#define MONGOC_DEFAULT_RETRYREADS true
3940
/* retryWrites requires sessions, which require crypto */
4041
#ifdef MONGOC_ENABLE_CRYPTO
4142
#define MONGOC_DEFAULT_RETRYWRITES true
@@ -78,6 +79,7 @@ typedef struct _mongoc_cmd_parts_t {
7879
bool is_write_command;
7980
bool prohibit_lsid;
8081
mongoc_cmd_parts_allow_txn_number_t allow_txn_number;
82+
bool is_retryable_read;
8183
bool is_retryable_write;
8284
bool has_temp_session;
8385
mongoc_client_t *client;
@@ -130,6 +132,10 @@ mongoc_cmd_is_compressible (mongoc_cmd_t *cmd);
130132
void
131133
mongoc_cmd_parts_cleanup (mongoc_cmd_parts_t *op);
132134

135+
bool
136+
_is_retryable_read (const mongoc_cmd_parts_t *parts,
137+
const mongoc_server_stream_t *server_stream);
138+
133139
BSON_END_DECLS
134140

135141

src/libmongoc/src/mongoc/mongoc-cmd.c

+39
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mongoc_cmd_parts_init (mongoc_cmd_parts_t *parts,
3939
parts->is_write_command = false;
4040
parts->prohibit_lsid = false;
4141
parts->allow_txn_number = MONGOC_CMD_PARTS_ALLOW_TXN_NUMBER_UNKNOWN;
42+
parts->is_retryable_read = false;
4243
parts->is_retryable_write = false;
4344
parts->has_temp_session = false;
4445
parts->client = client;
@@ -730,6 +731,39 @@ _is_retryable_write (const mongoc_cmd_parts_t *parts,
730731
}
731732

732733

734+
/* Check if the read command should support retryable behavior. */
735+
bool
736+
_is_retryable_read (const mongoc_cmd_parts_t *parts,
737+
const mongoc_server_stream_t *server_stream)
738+
{
739+
if (!parts->is_read_command) {
740+
return false;
741+
}
742+
743+
/* Commands that go through read_write_command helpers are also write
744+
* commands. Prohibit from read retry. */
745+
if (parts->is_write_command) {
746+
return false;
747+
}
748+
749+
if (server_stream->sd->max_wire_version < WIRE_VERSION_RETRY_READS) {
750+
return false;
751+
}
752+
753+
if (_mongoc_client_session_in_txn (parts->assembled.session)) {
754+
return false;
755+
}
756+
757+
if (!mongoc_uri_get_option_as_bool (parts->client->uri,
758+
MONGOC_URI_RETRYREADS,
759+
MONGOC_DEFAULT_RETRYREADS)) {
760+
return false;
761+
}
762+
763+
return true;
764+
}
765+
766+
733767
/*
734768
*--------------------------------------------------------------------------
735769
*
@@ -888,6 +922,11 @@ mongoc_cmd_parts_assemble (mongoc_cmd_parts_t *parts,
888922
parts->is_retryable_write = true;
889923
}
890924

925+
/* Conversely, check if the command is retryable if it is a read. */
926+
if (_is_retryable_read (parts, server_stream) && !is_get_more) {
927+
parts->is_retryable_read = true;
928+
}
929+
891930
if (!bson_empty (&server_stream->cluster_time)) {
892931
cluster_time =
893932
_largest_cluster_time (&server_stream->cluster_time, cluster_time);

src/libmongoc/src/mongoc/mongoc-collection.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ mongoc_collection_command_simple (mongoc_collection_t *collection,
652652
return _mongoc_client_command_with_opts (collection->client,
653653
collection->db,
654654
command,
655-
MONGOC_CMD_READ,
655+
MONGOC_CMD_RAW,
656656
NULL /* opts */,
657657
MONGOC_QUERY_NONE,
658658
read_prefs,

src/libmongoc/src/mongoc/mongoc-cursor-array.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ _prime (mongoc_cursor_t *cursor)
3636
/* this cursor is only used with the listDatabases command. it iterates
3737
* over the array in the response's "databases" field. */
3838
if (_mongoc_cursor_run_command (
39-
cursor, &data->cmd, &cursor->opts, &data->array) &&
39+
cursor, &data->cmd, &cursor->opts, &data->array, false) &&
4040
bson_iter_init_find (&iter, &data->array, data->field_name) &&
4141
BSON_ITER_HOLDS_ARRAY (&iter) &&
4242
bson_iter_recurse (&iter, &data->iter)) {

src/libmongoc/src/mongoc/mongoc-cursor-cmd-deprecated.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ _prime (mongoc_cursor_t *cursor)
3232
data_cmd_deprecated_t *data = (data_cmd_deprecated_t *) cursor->impl.data;
3333
bson_destroy (&data->reply);
3434
if (_mongoc_cursor_run_command (
35-
cursor, &data->cmd, &cursor->opts, &data->reply)) {
35+
cursor, &data->cmd, &cursor->opts, &data->reply, true)) {
3636
return IN_BATCH;
3737
} else {
3838
return DONE;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ bool
176176
_mongoc_cursor_run_command (mongoc_cursor_t *cursor,
177177
const bson_t *command,
178178
const bson_t *opts,
179-
bson_t *reply);
179+
bson_t *reply,
180+
bool retry_prohibited);
180181
bool
181182
_mongoc_cursor_more (mongoc_cursor_t *cursor);
182183

0 commit comments

Comments
 (0)