static bool txn_abort (mongoc_client_session_t *session, bson_t *reply, bson_error_t *error) { bson_t cmd = BSON_INITIALIZER; bson_t opts = BSON_INITIALIZER; bson_error_t err_local; bson_error_t *err_ptr = error ? error : &err_local; bson_t reply_local = BSON_INITIALIZER; mongoc_write_err_type_t error_type; bool r = false; _mongoc_bson_init_if_set (reply); if (!mongoc_client_session_append (session, &opts, err_ptr)) { GOTO (done); } if (session->txn.opts.write_concern) { if (!mongoc_write_concern_append (session->txn.opts.write_concern, &opts)) { bson_set_error (err_ptr, MONGOC_ERROR_TRANSACTION, MONGOC_ERROR_TRANSACTION_INVALID_STATE, "Invalid transaction write concern"); GOTO (done); } } BSON_APPEND_INT32 (&cmd, "abortTransaction", 1); /* will be reinitialized by mongoc_client_write_command_with_opts */ bson_destroy (&reply_local); r = mongoc_client_write_command_with_opts ( session->client, "admin", &cmd, &opts, &reply_local, err_ptr); /* Transactions Spec: "Drivers MUST retry the commitTransaction command once * after it fails with a retryable error", same for abort */ error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local); if (error_type == MONGOC_WRITE_ERR_RETRY) { bson_destroy (&reply_local); r = mongoc_client_write_command_with_opts ( session->client, "admin", &cmd, &opts, &reply_local, err_ptr); } if (!r) { /* we won't return an error from abortTransaction, so warn */ MONGOC_WARNING ("Error in abortTransaction: %s", err_ptr->message); } done: bson_destroy (&reply_local); bson_destroy (&cmd); bson_destroy (&opts); return r; }
static void _mongoc_write_opmsg (mongoc_write_command_t *command, mongoc_client_t *client, mongoc_server_stream_t *server_stream, const char *database, const char *collection, const mongoc_write_concern_t *write_concern, uint32_t index_offset, mongoc_client_session_t *cs, mongoc_write_result_t *result, bson_error_t *error) { mongoc_cmd_parts_t parts; bson_iter_t iter; bson_t cmd; bson_t reply; bool ret = false; int32_t max_msg_size; int32_t max_bson_obj_size; int32_t max_document_count; uint32_t header; uint32_t payload_batch_size = 0; uint32_t payload_total_offset = 0; bool ship_it = false; int document_count = 0; int32_t len; mongoc_server_stream_t *retry_server_stream = NULL; ENTRY; BSON_ASSERT (command); BSON_ASSERT (client); BSON_ASSERT (database); BSON_ASSERT (server_stream); BSON_ASSERT (collection); /* MongoDB has a extra allowance to allow updating 16mb document, * as the update operators would otherwise overflow the 16mb object limit */ #define BSON_OBJECT_ALLOWANCE (16 * 1024) max_bson_obj_size = mongoc_server_stream_max_bson_obj_size (server_stream); max_msg_size = mongoc_server_stream_max_msg_size (server_stream); max_document_count = mongoc_server_stream_max_write_batch_size (server_stream); bson_init (&cmd); _mongoc_write_command_init (&cmd, command, collection); mongoc_cmd_parts_init (&parts, client, database, MONGOC_QUERY_NONE, &cmd); parts.assembled.operation_id = command->operation_id; parts.is_write_command = true; if (!mongoc_cmd_parts_set_write_concern ( &parts, write_concern, server_stream->sd->max_wire_version, error)) { bson_destroy (&cmd); mongoc_cmd_parts_cleanup (&parts); EXIT; } if (parts.assembled.is_acknowledged) { mongoc_cmd_parts_set_session (&parts, cs); } /* Write commands that include multi-document operations are not retryable. * Set this explicitly so that mongoc_cmd_parts_assemble does not need to * inspect the command body later. */ parts.allow_txn_number = (command->flags.has_multi_write || !parts.assembled.is_acknowledged) ? MONGOC_CMD_PARTS_ALLOW_TXN_NUMBER_NO : MONGOC_CMD_PARTS_ALLOW_TXN_NUMBER_YES; BSON_ASSERT (bson_iter_init (&iter, &command->cmd_opts)); if (!mongoc_cmd_parts_append_opts ( &parts, &iter, server_stream->sd->max_wire_version, error)) { bson_destroy (&cmd); mongoc_cmd_parts_cleanup (&parts); EXIT; } if (!mongoc_cmd_parts_assemble (&parts, server_stream, error)) { bson_destroy (&cmd); mongoc_cmd_parts_cleanup (&parts); EXIT; } /* * OP_MSG header == 16 byte * + 4 bytes flagBits * + 1 byte payload type = 1 * + 1 byte payload type = 2 * + 4 byte size of payload * == 26 bytes opcode overhead * + X Full command document {insert: "test", writeConcern: {...}} * + Y command identifier ("documents", "deletes", "updates") ( + \0) */ header = 26 + parts.assembled.command->len + gCommandFieldLens[command->type] + 1; do { memcpy (&len, command->payload.data + payload_batch_size + payload_total_offset, 4); len = BSON_UINT32_FROM_LE (len); if (len > max_bson_obj_size + BSON_OBJECT_ALLOWANCE) { /* Quit if the document is too large */ _mongoc_write_command_too_large_error ( error, index_offset, len, max_bson_obj_size); result->failed = true; break; } else if ((payload_batch_size + header) + len <= max_msg_size) { /* The current batch is still under max batch size in bytes */ payload_batch_size += len; /* If this document filled the maximum document count */ if (++document_count == max_document_count) { ship_it = true; /* If this document is the last document we have */ } else if (payload_batch_size + payload_total_offset == command->payload.len) { ship_it = true; } else { ship_it = false; } } else { ship_it = true; } if (ship_it) { bool is_retryable = parts.is_retryable_write; mongoc_write_err_type_t error_type; /* Seek past the document offset we have already sent */ parts.assembled.payload = command->payload.data + payload_total_offset; /* Only send the documents up to this size */ parts.assembled.payload_size = payload_batch_size; parts.assembled.payload_identifier = gCommandFields[command->type]; /* increment the transaction number for the first attempt of each * retryable write command */ if (is_retryable) { bson_iter_t txn_number_iter; BSON_ASSERT (bson_iter_init_find ( &txn_number_iter, parts.assembled.command, "txnNumber")); bson_iter_overwrite_int64 ( &txn_number_iter, ++parts.assembled.session->server_session->txn_number); } retry: ret = mongoc_cluster_run_command_monitored ( &client->cluster, &parts.assembled, &reply, error); /* Add this batch size so we skip these documents next time */ payload_total_offset += payload_batch_size; payload_batch_size = 0; /* If a retryable error is encountered and the write is retryable, * select a new writable stream and retry. If server selection fails or * the selected server does not support retryable writes, fall through * and allow the original error to be reported. */ error_type = _mongoc_write_error_get_type (ret, error, &reply); if (is_retryable && error_type == MONGOC_WRITE_ERR_RETRY) { bson_error_t ignored_error; /* each write command may be retried at most once */ is_retryable = false; if (retry_server_stream) { mongoc_server_stream_cleanup (retry_server_stream); } retry_server_stream = mongoc_cluster_stream_for_writes ( &client->cluster, cs, NULL, &ignored_error); if (retry_server_stream && retry_server_stream->sd->max_wire_version >= WIRE_VERSION_RETRY_WRITES) { parts.assembled.server_stream = retry_server_stream; bson_destroy (&reply); GOTO (retry); } } if (!ret) { result->failed = true; result->must_stop = true; } /* Result merge needs to know the absolute index for a document * so it can rewrite the error message which contains the relative * document index per batch */ _mongoc_write_result_merge (result, command, &reply, index_offset); index_offset += document_count; document_count = 0; bson_destroy (&reply); } /* While we have more documents to write */ } while (payload_total_offset < command->payload.len); bson_destroy (&cmd); mongoc_cmd_parts_cleanup (&parts); if (retry_server_stream) { mongoc_server_stream_cleanup (retry_server_stream); } if (ret) { /* if a retry succeeded, clear the initial error */ memset (&result->error, 0, sizeof (bson_error_t)); } EXIT; }
static bool txn_finish (mongoc_client_session_t *session, mongoc_txn_intent_t intent, bson_t *reply, bson_error_t *error) { const char *cmd_name; bson_t cmd = BSON_INITIALIZER; bson_t opts = BSON_INITIALIZER; bson_error_t err_local; bson_error_t *err_ptr = error ? error : &err_local; bson_t reply_local = BSON_INITIALIZER; mongoc_write_err_type_t error_type; bool r = false; _mongoc_bson_init_if_set (reply); cmd_name = (intent == TXN_COMMIT ? "commitTransaction" : "abortTransaction"); if (!mongoc_client_session_append (session, &opts, err_ptr)) { GOTO (done); } if (session->txn.opts.write_concern) { if (!mongoc_write_concern_append (session->txn.opts.write_concern, &opts)) { bson_set_error (err_ptr, MONGOC_ERROR_TRANSACTION, MONGOC_ERROR_TRANSACTION_INVALID_STATE, "Invalid transaction write concern"); GOTO (done); } } BSON_APPEND_INT32 (&cmd, cmd_name, 1); /* will be reinitialized by mongoc_client_write_command_with_opts */ bson_destroy (&reply_local); r = mongoc_client_write_command_with_opts ( session->client, "admin", &cmd, &opts, &reply_local, err_ptr); /* Transactions Spec: "Drivers MUST retry the commitTransaction command once * after it fails with a retryable error", same for abort */ error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local); if (error_type == MONGOC_WRITE_ERR_RETRY) { bson_destroy (&reply_local); r = mongoc_client_write_command_with_opts ( session->client, "admin", &cmd, &opts, &reply_local, err_ptr); error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local); } /* Transactions Spec: "add the UnknownTransactionCommitResult error label * when commitTransaction fails with a network error, server selection * error, or write concern failed / timeout." */ if (intent == TXN_COMMIT && reply) { if ((!r && err_ptr->domain == MONGOC_ERROR_SERVER_SELECTION) || error_type == MONGOC_WRITE_ERR_RETRY || error_type == MONGOC_WRITE_ERR_WRITE_CONCERN) { bson_copy_to_excluding_noinit ( &reply_local, reply, "errorLabels", NULL); copy_labels_plus_unknown_commit_result (&reply_local, reply); } else { /* maintain invariants: reply & reply_local are valid until the end */ bson_destroy (reply); bson_steal (reply, &reply_local); bson_init (&reply_local); } } else if (intent == TXN_ABORT && !r) { /* we won't return an error from abortTransaction, so warn */ MONGOC_WARNING ("Error in %s: %s", cmd_name, err_ptr->message); } done: bson_destroy (&reply_local); bson_destroy (&cmd); bson_destroy (&opts); return r; }
static bool txn_commit (mongoc_client_session_t *session, bool explicitly_retrying, bson_t *reply, bson_error_t *error) { bson_t cmd = BSON_INITIALIZER; bson_t opts = BSON_INITIALIZER; bson_error_t err_local; bson_error_t *err_ptr = error ? error : &err_local; bson_t reply_local = BSON_INITIALIZER; mongoc_write_err_type_t error_type; bool r = false; bool retrying_after_error = false; mongoc_write_concern_t *retry_wc = NULL; _mongoc_bson_init_if_set (reply); BSON_APPEND_INT32 (&cmd, "commitTransaction", 1); retry: if (!mongoc_client_session_append (session, &opts, err_ptr)) { GOTO (done); } /* Transactions Spec: "When commitTransaction is retried, either by the * driver's internal retry-once logic or explicitly by the user calling * commitTransaction again, drivers MUST apply w:majority to the write * concern of the commitTransaction command." */ if (!retry_wc && (retrying_after_error || explicitly_retrying)) { retry_wc = create_commit_retry_wc (session->txn.opts.write_concern ? session->txn.opts.write_concern : session->client->write_concern); } if (retry_wc || session->txn.opts.write_concern) { if (!mongoc_write_concern_append ( retry_wc ? retry_wc : session->txn.opts.write_concern, &opts)) { bson_set_error (err_ptr, MONGOC_ERROR_TRANSACTION, MONGOC_ERROR_TRANSACTION_INVALID_STATE, "Invalid transaction write concern"); GOTO (done); } } /* will be reinitialized by mongoc_client_write_command_with_opts */ bson_destroy (&reply_local); r = mongoc_client_write_command_with_opts ( session->client, "admin", &cmd, &opts, &reply_local, err_ptr); /* Transactions Spec: "Drivers MUST retry the commitTransaction command once * after it fails with a retryable error", same for abort */ error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local); if (!retrying_after_error && error_type == MONGOC_WRITE_ERR_RETRY) { retrying_after_error = true; /* retry after error only once */ bson_reinit (&opts); GOTO (retry); } /* Transactions Spec: "add the UnknownTransactionCommitResult error label * when commitTransaction fails with a network error, server selection * error, or write concern failed / timeout." */ if (reply) { if ((!r && err_ptr->domain == MONGOC_ERROR_SERVER_SELECTION) || error_type == MONGOC_WRITE_ERR_RETRY || error_type == MONGOC_WRITE_ERR_WRITE_CONCERN) { bson_copy_to_excluding_noinit ( &reply_local, reply, "errorLabels", NULL); copy_labels_plus_unknown_commit_result (&reply_local, reply); } else { /* maintain invariants: reply & reply_local are valid until the end */ bson_destroy (reply); bson_steal (reply, &reply_local); bson_init (&reply_local); } } done: bson_destroy (&reply_local); bson_destroy (&cmd); bson_destroy (&opts); if (retry_wc) { mongoc_write_concern_destroy (retry_wc); } return r; }