1 /*
2 * Copyright 2018-present MongoDB, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <bson/bson.h>
18
19 #include "mongoc-error.h"
20 #include "mongoc-error-private.h"
21 #include "mongoc-rpc-private.h"
22 #include "mongoc-client-private.h"
23
24 bool
mongoc_error_has_label(const bson_t * reply,const char * label)25 mongoc_error_has_label (const bson_t *reply, const char *label)
26 {
27 bson_iter_t iter;
28 bson_iter_t error_labels;
29
30 BSON_ASSERT (reply);
31 BSON_ASSERT (label);
32
33 if (bson_iter_init_find (&iter, reply, "errorLabels") &&
34 bson_iter_recurse (&iter, &error_labels)) {
35 while (bson_iter_next (&error_labels)) {
36 if (BSON_ITER_HOLDS_UTF8 (&error_labels) &&
37 !strcmp (bson_iter_utf8 (&error_labels, NULL), label)) {
38 return true;
39 }
40 }
41 }
42
43 if (!bson_iter_init_find (&iter, reply, "writeConcernError")) {
44 return false;
45 }
46
47 BSON_ASSERT (bson_iter_recurse (&iter, &iter));
48
49 if (bson_iter_find (&iter, "errorLabels") &&
50 bson_iter_recurse (&iter, &error_labels)) {
51 while (bson_iter_next (&error_labels)) {
52 if (BSON_ITER_HOLDS_UTF8 (&error_labels) &&
53 !strcmp (bson_iter_utf8 (&error_labels, NULL), label)) {
54 return true;
55 }
56 }
57 }
58
59 return false;
60 }
61
62 static bool
_mongoc_error_is_server(bson_error_t * error)63 _mongoc_error_is_server (bson_error_t *error)
64 {
65 if (!error) {
66 return false;
67 }
68
69 return error->domain == MONGOC_ERROR_SERVER ||
70 error->domain == MONGOC_ERROR_WRITE_CONCERN;
71 }
72
73 static bool
_mongoc_write_error_is_retryable(bson_error_t * error)74 _mongoc_write_error_is_retryable (bson_error_t *error)
75 {
76 if (!_mongoc_error_is_server (error)) {
77 return false;
78 }
79
80 switch (error->code) {
81 case MONGOC_SERVER_ERR_HOSTUNREACHABLE:
82 case MONGOC_SERVER_ERR_HOSTNOTFOUND:
83 case MONGOC_SERVER_ERR_NETWORKTIMEOUT:
84 case MONGOC_SERVER_ERR_SHUTDOWNINPROGRESS:
85 case MONGOC_SERVER_ERR_PRIMARYSTEPPEDDOWN:
86 case MONGOC_SERVER_ERR_EXCEEDEDTIMELIMIT:
87 case MONGOC_SERVER_ERR_SOCKETEXCEPTION:
88 case MONGOC_SERVER_ERR_NOTMASTER:
89 case MONGOC_SERVER_ERR_INTERRUPTEDATSHUTDOWN:
90 case MONGOC_SERVER_ERR_INTERRUPTEDDUETOREPLSTATECHANGE:
91 case MONGOC_SERVER_ERR_NOTMASTERNOSLAVEOK:
92 case MONGOC_SERVER_ERR_NOTMASTERORSECONDARY:
93 return true;
94 default:
95 return false;
96 }
97 }
98
99 static void
_mongoc_write_error_append_retryable_label(bson_t * reply)100 _mongoc_write_error_append_retryable_label (bson_t *reply)
101 {
102 bson_t reply_local = BSON_INITIALIZER;
103
104 if (!reply) {
105 bson_destroy (&reply_local);
106 return;
107 }
108
109 bson_copy_to_excluding_noinit (reply, &reply_local, "errorLabels", NULL);
110 _mongoc_error_copy_labels_and_upsert (
111 reply, &reply_local, RETRYABLE_WRITE_ERROR);
112
113 bson_destroy (reply);
114 bson_steal (reply, &reply_local);
115 }
116
117 void
_mongoc_write_error_handle_labels(bool cmd_ret,const bson_error_t * cmd_err,bson_t * reply,int32_t server_max_wire_version)118 _mongoc_write_error_handle_labels (bool cmd_ret,
119 const bson_error_t *cmd_err,
120 bson_t *reply,
121 int32_t server_max_wire_version)
122 {
123 bson_error_t error;
124
125 /* check for a client error. */
126 if (!cmd_ret && _mongoc_error_is_network (cmd_err)) {
127 /* Retryable writes spec: When the driver encounters a network error
128 * communicating with any server version that supports retryable
129 * writes, it MUST add a RetryableWriteError label to that error. */
130 _mongoc_write_error_append_retryable_label (reply);
131 return;
132 }
133
134 if (server_max_wire_version >= WIRE_VERSION_RETRYABLE_WRITE_ERROR_LABEL) {
135 return;
136 }
137
138 /* check for a server error. */
139 if (_mongoc_cmd_check_ok_no_wce (
140 reply, MONGOC_ERROR_API_VERSION_2, &error)) {
141 return;
142 }
143
144 if (_mongoc_write_error_is_retryable (&error)) {
145 _mongoc_write_error_append_retryable_label (reply);
146 }
147 }
148
149
150 /*--------------------------------------------------------------------------
151 *
152 * _mongoc_read_error_get_type --
153 *
154 * Checks if the error or reply from a read command is considered
155 * retryable according to the retryable reads spec. Checks both
156 * for a client error (a network exception) and a server error in
157 * the reply. @cmd_ret and @cmd_err come from the result of a
158 * read_command function.
159 *
160 *
161 * Return:
162 * A mongoc_read_error_type_t indicating the type of error (if any).
163 *
164 *--------------------------------------------------------------------------
165 */
166 mongoc_read_err_type_t
_mongoc_read_error_get_type(bool cmd_ret,const bson_error_t * cmd_err,const bson_t * reply)167 _mongoc_read_error_get_type (bool cmd_ret,
168 const bson_error_t *cmd_err,
169 const bson_t *reply)
170 {
171 bson_error_t error;
172
173 /* check for a client error. */
174 if (!cmd_ret && cmd_err && _mongoc_error_is_network (cmd_err)) {
175 /* Retryable reads spec: "considered retryable if [...] any network
176 * exception (e.g. socket timeout or error) */
177 return MONGOC_READ_ERR_RETRY;
178 }
179
180 /* check for a server error. */
181 if (_mongoc_cmd_check_ok_no_wce (
182 reply, MONGOC_ERROR_API_VERSION_2, &error)) {
183 return MONGOC_READ_ERR_NONE;
184 }
185
186 switch (error.code) {
187 case MONGOC_SERVER_ERR_INTERRUPTEDATSHUTDOWN:
188 case MONGOC_SERVER_ERR_INTERRUPTEDDUETOREPLSTATECHANGE:
189 case MONGOC_SERVER_ERR_NOTMASTER:
190 case MONGOC_SERVER_ERR_NOTMASTERNOSLAVEOK:
191 case MONGOC_SERVER_ERR_NOTMASTERORSECONDARY:
192 case MONGOC_SERVER_ERR_PRIMARYSTEPPEDDOWN:
193 case MONGOC_SERVER_ERR_SHUTDOWNINPROGRESS:
194 case MONGOC_SERVER_ERR_HOSTNOTFOUND:
195 case MONGOC_SERVER_ERR_HOSTUNREACHABLE:
196 case MONGOC_SERVER_ERR_NETWORKTIMEOUT:
197 case MONGOC_SERVER_ERR_SOCKETEXCEPTION:
198 return MONGOC_READ_ERR_RETRY;
199 default:
200 if (strstr (error.message, "not master") ||
201 strstr (error.message, "node is recovering")) {
202 return MONGOC_READ_ERR_RETRY;
203 }
204 return MONGOC_READ_ERR_OTHER;
205 }
206 }
207
208 void
_mongoc_error_copy_labels_and_upsert(const bson_t * src,bson_t * dst,char * label)209 _mongoc_error_copy_labels_and_upsert (const bson_t *src,
210 bson_t *dst,
211 char *label)
212 {
213 bson_iter_t iter;
214 bson_iter_t src_label;
215 bson_t dst_labels;
216 char str[16];
217 uint32_t i = 0;
218 const char *key;
219
220 BSON_APPEND_ARRAY_BEGIN (dst, "errorLabels", &dst_labels);
221 BSON_APPEND_UTF8 (&dst_labels, "0", label);
222
223 /* append any other errorLabels already in "src" */
224 if (bson_iter_init_find (&iter, src, "errorLabels") &&
225 bson_iter_recurse (&iter, &src_label)) {
226 while (bson_iter_next (&src_label) && BSON_ITER_HOLDS_UTF8 (&src_label)) {
227 if (strcmp (bson_iter_utf8 (&src_label, NULL), label) != 0) {
228 i++;
229 bson_uint32_to_string (i, &key, str, sizeof str);
230 BSON_APPEND_UTF8 (
231 &dst_labels, key, bson_iter_utf8 (&src_label, NULL));
232 }
233 }
234 }
235
236 bson_append_array_end (dst, &dst_labels);
237 }
238
239 /* Defined in SDAM spec under "Application Errors".
240 * @error should have been obtained from a command reply, e.g. with
241 * _mongoc_cmd_check_ok.
242 */
243 bool
_mongoc_error_is_shutdown(bson_error_t * error)244 _mongoc_error_is_shutdown (bson_error_t *error)
245 {
246 if (!_mongoc_error_is_server (error)) {
247 return false;
248 }
249 switch (error->code) {
250 case 11600: /* InterruptedAtShutdown */
251 case 91: /* ShutdownInProgress */
252 return true;
253 default:
254 return false;
255 }
256 }
257
258 bool
_mongoc_error_is_not_master(bson_error_t * error)259 _mongoc_error_is_not_master (bson_error_t *error)
260 {
261 if (!_mongoc_error_is_server (error)) {
262 return false;
263 }
264
265 if (_mongoc_error_is_recovering (error)) {
266 return false;
267 }
268 switch (error->code) {
269 case MONGOC_SERVER_ERR_NOTMASTER:
270 case MONGOC_SERVER_ERR_NOTMASTERNOSLAVEOK:
271 return true;
272 default:
273 return NULL != strstr (error->message, "not master");
274 }
275 }
276
277 bool
_mongoc_error_is_recovering(bson_error_t * error)278 _mongoc_error_is_recovering (bson_error_t *error)
279 {
280 if (!_mongoc_error_is_server (error)) {
281 return false;
282 }
283 switch (error->code) {
284 case MONGOC_SERVER_ERR_INTERRUPTEDATSHUTDOWN:
285 case MONGOC_SERVER_ERR_INTERRUPTEDDUETOREPLSTATECHANGE:
286 case MONGOC_SERVER_ERR_NOTMASTERORSECONDARY:
287 case MONGOC_SERVER_ERR_PRIMARYSTEPPEDDOWN:
288 case MONGOC_SERVER_ERR_SHUTDOWNINPROGRESS:
289 return true;
290 default:
291 return NULL != strstr (error->message, "not master or secondary") ||
292 NULL != strstr (error->message, "node is recovering");
293 }
294 }
295
296 /* Assumes @error was parsed as an API V2 error. */
297 bool
_mongoc_error_is_state_change(bson_error_t * error)298 _mongoc_error_is_state_change (bson_error_t *error)
299 {
300 return _mongoc_error_is_recovering (error) ||
301 _mongoc_error_is_not_master (error);
302 }
303
304 bool
_mongoc_error_is_network(const bson_error_t * error)305 _mongoc_error_is_network (const bson_error_t *error)
306 {
307 if (!error) {
308 return false;
309 }
310 if (error->domain == MONGOC_ERROR_STREAM) {
311 return true;
312 }
313
314 if (error->domain == MONGOC_ERROR_PROTOCOL &&
315 error->code == MONGOC_ERROR_PROTOCOL_INVALID_REPLY) {
316 return true;
317 }
318
319 return false;
320 }
321