From 6b23bd026bb74b1e44d93951662070076e99b361 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Thu, 3 May 2018 15:19:40 -0400 Subject: SERVER-33909 More detailed error reporting from CAPI The `libmongodbcapi.h` file now contains detailed documentation of all preconditions, postconditions, and behaviors. All operations now take a `libmongodbcapi_status *` argument which will be populated with the failure results of an operation. The internals of how the `libmongodbcapi_` functions are implemented have been rewritten to catch and report nearly all failures, as well as to use more native C++ idioms such as exceptions and RAII. --- .../client/embedded/embedded_transport_layer.cpp | 19 +- .../client/embedded/embedded_transport_layer.h | 11 +- .../embedded/embedded_transport_layer_test.cpp | 35 +- src/mongo/client/embedded/libmongodbcapi.cpp | 614 +++++++++++++----- src/mongo/client/embedded/libmongodbcapi.h | 713 +++++++++++++++++---- src/mongo/client/embedded/libmongodbcapi_test.cpp | 150 +++-- 6 files changed, 1179 insertions(+), 363 deletions(-) (limited to 'src/mongo/client') diff --git a/src/mongo/client/embedded/embedded_transport_layer.cpp b/src/mongo/client/embedded/embedded_transport_layer.cpp index a3a3222daea..3e662efb62b 100644 --- a/src/mongo/client/embedded/embedded_transport_layer.cpp +++ b/src/mongo/client/embedded/embedded_transport_layer.cpp @@ -67,7 +67,7 @@ namespace { struct FreeAndDestroy { void operator()(mongoc_stream_t* x) { auto stream = static_cast(x); - libmongodbcapi_db_client_destroy(stream->clientHandle); + libmongodbcapi_client_destroy(stream->clientHandle, nullptr); stream->~mongoc_stream_embedded_t(); free(stream); } @@ -134,12 +134,12 @@ extern "C" ssize_t mongoc_stream_embedded_writev(mongoc_stream_t* s, // if we found a complete message, send it if (stream->input_length_to_go == 0) { auto input_len = (size_t)(stream->inputBuf.data() - stream->hiddenBuf.get()); - int retVal = - libmongodbcapi_db_client_wire_protocol_rpc(stream->clientHandle, - stream->hiddenBuf.get(), - input_len, - &(stream->libmongo_output), - &(stream->libmongo_output_size)); + int retVal = libmongodbcapi_client_invoke(stream->clientHandle, + stream->hiddenBuf.get(), + input_len, + &(stream->libmongo_output), + &(stream->libmongo_output_size), + nullptr); if (retVal != LIBMONGODB_CAPI_SUCCESS) { return -1; } @@ -221,7 +221,8 @@ extern "C" mongoc_stream_t* embedded_stream_initiator(const mongoc_uri_t* uri, stream_buf.release(); // This must be here so we don't have double ownership stream->state = RPCState::WaitingForMessageLength; // Set up connections to database - stream->clientHandle = libmongodbcapi_db_client_new((libmongodbcapi_db*)user_data); + stream->clientHandle = + libmongodbcapi_client_create(static_cast(user_data), nullptr); // Connect the functions to the stream // type is not relevant for us. Has to be set for the C Driver, but it has to do with picking @@ -247,7 +248,7 @@ struct ClientDeleter { } }; -extern "C" mongoc_client_t* embedded_mongoc_client_new(libmongodbcapi_db* db) try { +extern "C" mongoc_client_t* embedded_mongoc_client_new(libmongodbcapi_instance* db) try { if (!db) { errno = EINVAL; return nullptr; diff --git a/src/mongo/client/embedded/embedded_transport_layer.h b/src/mongo/client/embedded/embedded_transport_layer.h index 10c2aa1e369..96c60fe1937 100644 --- a/src/mongo/client/embedded/embedded_transport_layer.h +++ b/src/mongo/client/embedded/embedded_transport_layer.h @@ -33,14 +33,15 @@ extern "C" { #endif -struct libmongodbcapi_db; +struct libmongodbcapi_instance; -/* Creates a client with the correct stream intiator set +/** + * Creates a client with the correct stream intiator set * @param db must be a valid db handle created by libmongodbcapi - * @return a mongoc client or null on error + * @returns a mongoc client or `NULL` on error */ -mongoc_client_t* embedded_mongoc_client_new(libmongodbcapi_db* db); +mongoc_client_t* embedded_mongoc_client_new(libmongodbcapi_instance* db); #ifdef __cplusplus -} +} // extern "C" #endif diff --git a/src/mongo/client/embedded/embedded_transport_layer_test.cpp b/src/mongo/client/embedded/embedded_transport_layer_test.cpp index 8b5c262a7f2..f0ec982eee8 100644 --- a/src/mongo/client/embedded/embedded_transport_layer_test.cpp +++ b/src/mongo/client/embedded/embedded_transport_layer_test.cpp @@ -46,6 +46,8 @@ namespace moe = mongo::optionenvironment; +libmongodbcapi_lib* global_lib_handle; + namespace { std::unique_ptr globalTempDir; @@ -54,7 +56,7 @@ class MongodbEmbeddedTransportLayerTest : public mongo::unittest::Test { protected: void setUp() { if (!globalTempDir) { - globalTempDir = mongo::stdx::make_unique("embedded_mongo"); + globalTempDir = std::make_unique("embedded_mongo"); } YAML::Emitter yaml; @@ -68,7 +70,7 @@ protected: yaml << YAML::EndMap; - db_handle = libmongodbcapi_db_new(yaml.c_str()); + db_handle = libmongodbcapi_instance_create(global_lib_handle, yaml.c_str(), nullptr); cd_client = embedded_mongoc_client_new(db_handle); mongoc_client_set_error_api(cd_client, 2); @@ -77,7 +79,7 @@ protected: } void tearDown() { - mongoc_collection_drop(cd_collection, NULL); + mongoc_collection_drop(cd_collection, nullptr); if (cd_collection) { mongoc_collection_destroy(cd_collection); } @@ -90,10 +92,10 @@ protected: mongoc_client_destroy(cd_client); } - libmongodbcapi_db_destroy(db_handle); + libmongodbcapi_instance_destroy(db_handle, nullptr); } - libmongodbcapi_db* getDBHandle() { + libmongodbcapi_instance* getDBHandle() { return db_handle; } @@ -109,7 +111,7 @@ protected: private: - libmongodbcapi_db* db_handle; + libmongodbcapi_instance* db_handle; mongoc_database_t* cd_db; mongoc_client_t* cd_client; mongoc_collection_t* cd_collection; @@ -163,6 +165,15 @@ TEST_F(MongodbEmbeddedTransportLayerTest, InsertAndDelete) { ASSERT(0 == mongoc_collection_count(collection, MONGOC_QUERY_NONE, nullptr, 0, 0, NULL, &err)); bson_destroy(doc); } + +struct StatusDestroy { + void operator()(libmongodbcapi_status* const ptr) { + if (!ptr) { + libmongodbcapi_status_destroy(ptr); + } + } +}; +using StatusPtr = std::unique_ptr; } // namespace // Define main function as an entry to these tests. @@ -193,19 +204,19 @@ int main(int argc, char** argv, char** envp) { ::mongo::serverGlobalParams.noUnixSocket = true; ::mongo::unittest::setupTestLogger(); + StatusPtr status(libmongodbcapi_status_create()); mongoc_init(); - int init = libmongodbcapi_init(nullptr); - if (init != LIBMONGODB_CAPI_SUCCESS) { - std::cerr << "libmongodbcapi_init() failed with " << init << std::endl; + global_lib_handle = libmongodbcapi_lib_init(nullptr, status.get()); + if (global_lib_handle == nullptr) { + std::cerr << "Error: " << libmongodbcapi_status_get_explanation(status.get()); return EXIT_FAILURE; } auto result = ::mongo::unittest::Suite::run(std::vector(), "", 1); - int fini = libmongodbcapi_fini(); - if (fini != LIBMONGODB_CAPI_SUCCESS) { - std::cerr << "libmongodbcapi_fini() failed with " << fini << std::endl; + if (libmongodbcapi_lib_fini(global_lib_handle, status.get()) != LIBMONGODB_CAPI_SUCCESS) { + std::cerr << "Error: " << libmongodbcapi_status_get_explanation(status.get()); return EXIT_FAILURE; } diff --git a/src/mongo/client/embedded/libmongodbcapi.cpp b/src/mongo/client/embedded/libmongodbcapi.cpp index bdc9d9f946e..358443aa617 100644 --- a/src/mongo/client/embedded/libmongodbcapi.cpp +++ b/src/mongo/client/embedded/libmongodbcapi.cpp @@ -28,6 +28,7 @@ #include "mongo/client/embedded/libmongodbcapi.h" +#include #include #include #include @@ -40,7 +41,6 @@ #include "mongo/db/service_context.h" #include "mongo/logger/logger.h" #include "mongo/logger/message_event_utf8_encoder.h" -#include "mongo/stdx/memory.h" #include "mongo/stdx/unordered_map.h" #include "mongo/transport/service_entry_point.h" #include "mongo/transport/transport_layer_mock.h" @@ -49,85 +49,221 @@ #include "mongo/util/scopeguard.h" #include "mongo/util/shared_buffer.h" -struct libmongodbcapi_db { - libmongodbcapi_db() = default; +struct libmongodbcapi_status { + libmongodbcapi_status() noexcept = default; + libmongodbcapi_status(const libmongodbcapi_error e, const int ec, std::string w) + : error(e), exception_code(ec), what(std::move(w)) {} - libmongodbcapi_db(const libmongodbcapi_db&) = delete; - libmongodbcapi_db& operator=(const libmongodbcapi_db&) = delete; + void clean() noexcept { + error = LIBMONGODB_CAPI_SUCCESS; + } + + libmongodbcapi_error error = LIBMONGODB_CAPI_SUCCESS; + int exception_code = 0; + std::string what; +}; + +namespace mongo { +namespace { +class MobileException : public std::exception { +public: + explicit MobileException(const libmongodbcapi_error code, std::string m) + : _mesg(std::move(m)), _code(code) {} + + libmongodbcapi_error mobileCode() const noexcept { + return this->_code; + } + + const char* what() const noexcept final { + return this->_mesg.c_str(); + } + +private: + std::string _mesg; + libmongodbcapi_error _code; +}; + +libmongodbcapi_status translateException() try { throw; } catch (const MobileException& ex) { + return {ex.mobileCode(), mongo::ErrorCodes::InternalError, ex.what()}; +} catch (const ExceptionFor& ex) { + return {LIBMONGODB_CAPI_ERROR_REENTRANCY_NOT_ALLOWED, ex.code(), ex.what()}; +} catch (const DBException& ex) { + return {LIBMONGODB_CAPI_ERROR_EXCEPTION, ex.code(), ex.what()}; +} catch (const std::bad_alloc& ex) { + return {LIBMONGODB_CAPI_ERROR_ENOMEM, mongo::ErrorCodes::InternalError, ex.what()}; +} catch (const std::exception& ex) { + return {LIBMONGODB_CAPI_ERROR_UNKNOWN, mongo::ErrorCodes::InternalError, ex.what()}; +} catch (...) { + return {LIBMONGODB_CAPI_ERROR_UNKNOWN, + mongo::ErrorCodes::InternalError, + "Unknown error encountered in performing requested libmongodbcapi operation"}; +} + +std::nullptr_t handleException(libmongodbcapi_status& status) noexcept { + try { + status = translateException(); + } catch (...) { + status.error = LIBMONGODB_CAPI_ERROR_IN_REPORTING_ERROR; + + try { + status.exception_code = -1; + + status.what.clear(); + + // Expected to be small enough to fit in the capacity that string always has. + const char severeErrorMessage[] = "Severe Error"; + + if (status.what.capacity() > sizeof(severeErrorMessage)) { + status.what = severeErrorMessage; + } + } catch (...) /* Ignore any errors at this point. */ + { + } + } + return nullptr; +} + +} // namespace +} // namespace mongo + +struct libmongodbcapi_lib { + ~libmongodbcapi_lib() { + invariant(this->databaseCount.load() == 0); + + if (this->logCallbackHandle) { + using mongo::logger::globalLogDomain; + globalLogDomain()->detachAppender(this->logCallbackHandle); + this->logCallbackHandle.reset(); + } + } + + libmongodbcapi_lib(const libmongodbcapi_lib&) = delete; + void operator=(const libmongodbcapi_lib) = delete; - mongo::ServiceContext* serviceContext = nullptr; - mongo::stdx::unordered_map> - open_clients; + libmongodbcapi_lib() = default; + + mongo::AtomicWord databaseCount; + + mongo::logger::ComponentMessageLogDomain::AppenderHandle logCallbackHandle; + + std::unique_ptr onlyDB; +}; + +namespace mongo { +namespace { +struct ServiceContextDestructor { + void operator()(mongo::ServiceContext* const serviceContext) const noexcept { + ::mongo::embedded::shutdown(serviceContext); + } +}; + +using EmbeddedServiceContextPtr = std::unique_ptr; +} // namespace +} // namespace mongo + +struct libmongodbcapi_instance { + ~libmongodbcapi_instance() { + invariant(this->clientCount.load() == 0); + this->parentLib->databaseCount.subtractAndFetch(1); + } + + libmongodbcapi_instance(const libmongodbcapi_instance&) = delete; + libmongodbcapi_instance& operator=(const libmongodbcapi_instance&) = delete; + + explicit libmongodbcapi_instance(libmongodbcapi_lib* const p, const char* const yaml_config) + : parentLib(p), + serviceContext(::mongo::embedded::initialize(yaml_config)), + // creating mock transport layer to be able to create sessions + transportLayer(std::make_unique()) { + if (!this->serviceContext) { + throw ::mongo::MobileException{ + LIBMONGODB_CAPI_ERROR_DB_INITIALIZATION_FAILED, + "The MongoDB Embedded Library Failed to initialize the Service Context"}; + } + + this->parentLib->databaseCount.addAndFetch(1); + } + + libmongodbcapi_lib* parentLib; + mongo::AtomicWord clientCount; + + mongo::EmbeddedServiceContextPtr serviceContext; std::unique_ptr transportLayer; }; + struct libmongodbcapi_client { - libmongodbcapi_client(libmongodbcapi_db* db) : parent_db(db) {} + ~libmongodbcapi_client() { + this->parent_db->clientCount.subtractAndFetch(1); + } + + explicit libmongodbcapi_client(libmongodbcapi_instance* const db) + : parent_db(db), + client(db->serviceContext->makeClient("embedded", db->transportLayer->createSession())) { + this->parent_db->clientCount.addAndFetch(1); + } libmongodbcapi_client(const libmongodbcapi_client&) = delete; libmongodbcapi_client& operator=(const libmongodbcapi_client&) = delete; - void* client_handle = nullptr; - std::vector output; - libmongodbcapi_db* parent_db = nullptr; + libmongodbcapi_instance* const parent_db; mongo::ServiceContext::UniqueClient client; + + std::vector output; mongo::DbResponse response; }; namespace mongo { namespace { -bool libraryInitialized_ = false; -libmongodbcapi_db* global_db = nullptr; -mongo::logger::ComponentMessageLogDomain::AppenderHandle logCallbackHandle; -thread_local int last_error = LIBMONGODB_CAPI_SUCCESS; -thread_local int callEntryDepth = 0; +std::unique_ptr library; class ReentrancyGuard { +private: + thread_local static bool inLibrary; + public: explicit ReentrancyGuard() { uassert(ErrorCodes::ReentrancyNotAllowed, str::stream() << "Reentry into libmongodbcapi is not allowed", - callEntryDepth == 0); - ++callEntryDepth; + !inLibrary); + inLibrary = true; } ~ReentrancyGuard() { - --callEntryDepth; + inLibrary = false; } ReentrancyGuard(ReentrancyGuard const&) = delete; ReentrancyGuard& operator=(ReentrancyGuard const&) = delete; }; -int register_log_callback(libmongodbcapi_log_callback log_callback, void* log_user_data) { - using namespace logger; - - logCallbackHandle = globalLogDomain()->attachAppender( - std::make_unique>( - log_callback, log_user_data, std::make_unique())); - - return LIBMONGODB_CAPI_SUCCESS; -} - -int unregister_log_callback() { - using namespace logger; +thread_local bool ReentrancyGuard::inLibrary = false; - globalLogDomain()->detachAppender(logCallbackHandle); - logCallbackHandle.reset(); +void registerLogCallback(libmongodbcapi_lib* const lib, + const libmongodbcapi_log_callback logCallback, + void* const logUserData) { + using logger::globalLogDomain; + using logger::MessageEventEphemeral; + using logger::MessageEventUnadornedEncoder; - return LIBMONGODB_CAPI_SUCCESS; + lib->logCallbackHandle = globalLogDomain()->attachAppender( + std::make_unique>( + logCallback, logUserData, std::make_unique())); } -int init(libmongodbcapi_init_params const* params) noexcept try { - using namespace logger; - - ReentrancyGuard guard; +libmongodbcapi_lib* capi_lib_init(libmongodbcapi_init_params const* params, + libmongodbcapi_status& status) try { + if (library) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_LIBRARY_ALREADY_INITIALIZED, + "Cannot initialize the MongoDB Embedded Library when it is already initialized."}; + } - if (libraryInitialized_) - return LIBMONGODB_CAPI_ERROR_LIBRARY_ALREADY_INITIALIZED; + auto lib = std::make_unique(); - int result = LIBMONGODB_CAPI_SUCCESS; + // TODO(adam.martin): Fold all of this log initialization into the ctor of lib. if (params) { + using logger::globalLogManager; // The standard console log appender may or may not be installed here, depending if this is // the first time we initialize the library or not. Make sure we handle both cases. if (params->log_flags & LIBMONGODB_CAPI_LOG_STDOUT) { @@ -139,127 +275,172 @@ int init(libmongodbcapi_init_params const* params) noexcept try { } if ((params->log_flags & LIBMONGODB_CAPI_LOG_CALLBACK) && params->log_callback) { - result = register_log_callback(params->log_callback, params->log_user_data); - if (result != LIBMONGODB_CAPI_SUCCESS) - return result; + registerLogCallback(lib.get(), params->log_callback, params->log_user_data); } } - libraryInitialized_ = true; - return result; -} catch (const std::exception&) { - return LIBMONGODB_CAPI_ERROR_UNKNOWN; -} + library = std::move(lib); -int fini() noexcept try { - ReentrancyGuard guard; + return library.get(); +} catch (...) { + // Make sure that no actual logger is attached if library cannot be initialized. Also prevent + // exception leaking failures here. + []() noexcept { + using logger::globalLogManager; + if (globalLogManager()->isDefaultConsoleAppenderAttached()) + globalLogManager()->detachDefaultConsoleAppender(); + } + (); + throw; +} - if (!libraryInitialized_) - return LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED; +void capi_lib_fini(libmongodbcapi_lib* const lib, libmongodbcapi_status& status) { + if (!lib) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_INVALID_LIB_HANDLE, + "Cannot close a `NULL` pointer referencing a MongoDB Embedded Library Instance"}; + } - if (global_db) - return LIBMONGODB_CAPI_ERROR_DB_OPEN; + if (!library) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot close the MongoDB Embedded Library when it is not initialized"}; + } - int result = LIBMONGODB_CAPI_SUCCESS; - if (logCallbackHandle) { - result = unregister_log_callback(); - if (result != LIBMONGODB_CAPI_SUCCESS) - return result; + if (library.get() != lib) { + throw MobileException{LIBMONGODB_CAPI_ERROR_INVALID_LIB_HANDLE, + "Invalid MongoDB Embedded Library handle."}; } - libraryInitialized_ = false; + // This check is not possible to 100% guarantee. It is a best effort. The documentation of + // this API says that the behavior of closing a `lib` with open handles is undefined, but may + // provide diagnostic errors in some circumstances. + if (lib->databaseCount.load() > 0) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_HAS_DB_HANDLES_OPEN, + "Cannot close the MongoDB Embedded Library when it has database handles still open."}; + } - return result; -} catch (const std::exception&) { - return LIBMONGODB_CAPI_ERROR_UNKNOWN; + library = nullptr; } -libmongodbcapi_db* db_new(const char* yaml_config) noexcept try { - ReentrancyGuard guard; +libmongodbcapi_instance* instance_new(libmongodbcapi_lib* const lib, + const char* const yaml_config, + libmongodbcapi_status& status) { + if (!library) { + throw MobileException{LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot create a new database handle when the MongoDB Embedded " + "Library is not yet initialized."}; + } - last_error = LIBMONGODB_CAPI_SUCCESS; - if (!libraryInitialized_) - throw std::runtime_error("libmongodbcapi_init not called"); - if (global_db) { - throw std::runtime_error("DB already exists"); + if (library.get() != lib) { + throw MobileException{LIBMONGODB_CAPI_ERROR_INVALID_LIB_HANDLE, + "Cannot create a new database handle when the MongoDB Embedded " + "Library is not yet initialized."}; } - global_db = new libmongodbcapi_db; - global_db->serviceContext = embedded::initialize(yaml_config); - if (!global_db->serviceContext) { - delete global_db; - global_db = nullptr; - throw std::runtime_error("Initialization failed"); + if (lib->onlyDB) { + throw MobileException{LIBMONGODB_CAPI_ERROR_DB_MAX_OPEN, + "The maximum number of permitted database handles for the MongoDB " + "Embedded Library have been opened."}; } - // creating mock transport layer to be able to create sessions - global_db->transportLayer = stdx::make_unique(); + lib->onlyDB = std::make_unique(lib, yaml_config); - return global_db; -} catch (const std::exception&) { - last_error = LIBMONGODB_CAPI_ERROR_UNKNOWN; - return nullptr; + return lib->onlyDB.get(); } -int db_destroy(libmongodbcapi_db* db) noexcept { - if (!db->open_clients.empty()) { - last_error = LIBMONGODB_CAPI_ERROR_UNKNOWN; - return last_error; +void instance_destroy(libmongodbcapi_instance* const db, libmongodbcapi_status& status) { + if (!library) { + throw MobileException{LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot destroy a database handle when the MongoDB Embedded Library " + "is not yet initialized."}; } - embedded::shutdown(global_db->serviceContext); + if (!db) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_INVALID_DB_HANDLE, + "Cannot close a `NULL` pointer referencing a MongoDB Embedded Database"}; + } - delete db; - invariant(!db || db == global_db); - if (db) { - global_db = nullptr; + if (db != library->onlyDB.get()) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_INVALID_DB_HANDLE, + "Cannot close the specified MongoDB Embedded Database, as it is not a valid instance."}; } - last_error = LIBMONGODB_CAPI_SUCCESS; - return last_error; -} -int db_pump(libmongodbcapi_db* db) noexcept try { - ReentrancyGuard guard; + if (db->clientCount.load() > 0) { + throw MobileException{ + LIBMONGODB_CAPI_ERROR_DB_CLIENTS_OPEN, + "Cannot close a MongoDB Embedded Database instance while it has open clients"}; + } - return LIBMONGODB_CAPI_SUCCESS; -} catch (const std::exception&) { - return LIBMONGODB_CAPI_ERROR_UNKNOWN; + library->onlyDB = nullptr; } -libmongodbcapi_client* client_new(libmongodbcapi_db* db) noexcept try { - ReentrancyGuard guard; +libmongodbcapi_client* client_new(libmongodbcapi_instance* const db, + libmongodbcapi_status& status) { + if (!library) { + throw MobileException{LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot create a new client handle when the MongoDB Embedded Library " + "is not yet initialized."}; + } - auto new_client = stdx::make_unique(db); - libmongodbcapi_client* rv = new_client.get(); - db->open_clients.insert(std::make_pair(rv, std::move(new_client))); + if (!db) { + throw MobileException{LIBMONGODB_CAPI_ERROR_INVALID_DB_HANDLE, + "Cannot use a `NULL` pointer referencing a MongoDB Embedded Database " + "when creating a new client"}; + } - auto session = global_db->transportLayer->createSession(); - rv->client = global_db->serviceContext->makeClient("embedded", std::move(session)); + if (db != library->onlyDB.get()) { + throw MobileException{LIBMONGODB_CAPI_ERROR_INVALID_DB_HANDLE, + "The specified MongoDB Embedded Database instance cannot be used to " + "create a new client because it is invalid."}; + } - last_error = LIBMONGODB_CAPI_SUCCESS; - return rv; -} catch (const std::exception&) { - last_error = LIBMONGODB_CAPI_ERROR_UNKNOWN; - return nullptr; + return new libmongodbcapi_client(db); } -void client_destroy(libmongodbcapi_client* client) noexcept { - last_error = LIBMONGODB_CAPI_SUCCESS; +void client_destroy(libmongodbcapi_client* const client, libmongodbcapi_status& status) { + if (!library) { + throw MobileException(LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot destroy a database handle when the MongoDB Embedded Library " + "is not yet initialized."); + } + if (!client) { - return; + throw MobileException{ + LIBMONGODB_CAPI_ERROR_INVALID_CLIENT_HANDLE, + "Cannot destroy a `NULL` pointer referencing a MongoDB Embedded Database Client"}; } - client->parent_db->open_clients.erase(client); + + delete client; } -int client_wire_protocol_rpc(libmongodbcapi_client* client, - const void* input, - size_t input_size, - void** output, - size_t* output_size) noexcept try { - ReentrancyGuard reentry_guard; +class ClientGuard { + ClientGuard(const ClientGuard&) = delete; + void operator=(const ClientGuard&) = delete; + +public: + explicit ClientGuard(libmongodbcapi_client* const client) : _client(client) { + mongo::Client::setCurrent(std::move(client->client)); + } + + ~ClientGuard() { + _client->client = mongo::Client::releaseCurrent(); + } + +private: + libmongodbcapi_client* const _client; +}; - mongo::Client::setCurrent(std::move(client->client)); - const auto guard = mongo::MakeGuard([&] { client->client = mongo::Client::releaseCurrent(); }); +void client_wire_protocol_rpc(libmongodbcapi_client* const client, + const void* input, + const size_t input_size, + void** const output, + size_t* const output_size, + libmongodbcapi_status& status) { + ClientGuard clientGuard(client); auto opCtx = cc().makeOperationContext(); auto sep = client->parent_db->serviceContext->getServiceEntryPoint(); @@ -275,58 +456,183 @@ int client_wire_protocol_rpc(libmongodbcapi_client* client, outMessage.setId(nextMessageId()); outMessage.setResponseToMsgId(msg.header().getId()); - *output_size = client->response.response.size(); - *output = (void*)client->response.response.buf(); + // The results of the computations used to fill out-parameters need to be captured and processed + // before setting the output parameters themselves, in order to maintain the strong-guarantee + // part of the contract of this function. + auto outParams = + std::make_tuple(client->response.response.size(), client->response.response.buf()); + + // We force the output parameters to be set in a `noexcept` enabled way. If the operation + // itself + // is safely noexcept, we just run it, otherwise we force a `noexcept` over it to catch errors. + if (noexcept(std::tie(*output_size, *output) = std::move(outParams))) { + std::tie(*output_size, *output) = std::move(outParams); + } else { + // Assigning primitives in a tied tuple should be noexcept, so we force it to be so, for + // our purposes. This facilitates a runtime check should something WEIRD happen. + [ output, output_size, &outParams ]() noexcept { + std::tie(*output_size, *output) = std::move(outParams); + } + (); + } +} + +int capi_status_get_error(const libmongodbcapi_status* const status) noexcept { + invariant(status); + return status->error; +} - return LIBMONGODB_CAPI_SUCCESS; -} catch (const std::exception&) { - return LIBMONGODB_CAPI_ERROR_UNKNOWN; +const char* capi_status_get_what(const libmongodbcapi_status* const status) noexcept { + invariant(status); + return status->what.c_str(); } -int get_last_capi_error() noexcept { - return last_error; +int capi_status_get_code(const libmongodbcapi_status* const status) noexcept { + invariant(status); + return status->exception_code; } + +template ()(*std::declval()))> +struct enterCXXImpl; + +template +struct enterCXXImpl { + template + static int call(Callable&& function, libmongodbcapi_status& status) noexcept { + try { + ReentrancyGuard singleEntrant; + function(status); + } catch (...) { + handleException(status); + } + return status.error; + } +}; + + +template +struct enterCXXImpl { + template + static Pointer* call(Callable&& function, libmongodbcapi_status& status) noexcept try { + ReentrancyGuard singleEntrant; + return function(status); + } catch (...) { + return handleException(status); + } +}; } // namespace } // namespace mongo +namespace { +struct StatusGuard { +private: + libmongodbcapi_status* status; + libmongodbcapi_status fallback; + +public: + explicit StatusGuard(libmongodbcapi_status* const statusPtr) noexcept : status(statusPtr) { + if (status) + status->clean(); + } + + libmongodbcapi_status& get() noexcept { + return status ? *status : fallback; + } + + const libmongodbcapi_status& get() const noexcept { + return status ? *status : fallback; + } + + operator libmongodbcapi_status&() & noexcept { + return this->get(); + } + operator libmongodbcapi_status&() && noexcept { + return this->get(); + } +}; + +template +auto enterCXX(libmongodbcapi_status* const statusPtr, Callable&& c) noexcept + -> decltype(mongo::enterCXXImpl::call(std::forward(c), *statusPtr)) { + StatusGuard status(statusPtr); + return mongo::enterCXXImpl::call(std::forward(c), status); +} +} // namespace + extern "C" { -int libmongodbcapi_init(const libmongodbcapi_init_params* params) { - return mongo::init(params); +libmongodbcapi_lib* libmongodbcapi_lib_init(const libmongodbcapi_init_params* const params, + libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, [&](libmongodbcapi_status& status) { + return mongo::capi_lib_init(params, status); + }); +} + +int libmongodbcapi_lib_fini(libmongodbcapi_lib* const lib, libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, [&](libmongodbcapi_status& status) { + return mongo::capi_lib_fini(lib, status); + }); } -int libmongodbcapi_fini() { - return mongo::fini(); +libmongodbcapi_instance* libmongodbcapi_instance_create(libmongodbcapi_lib* lib, + const char* const yaml_config, + libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, [&](libmongodbcapi_status& status) { + return mongo::instance_new(lib, yaml_config, status); + }); } -libmongodbcapi_db* libmongodbcapi_db_new(const char* yaml_config) { - return mongo::db_new(yaml_config); +int libmongodbcapi_instance_destroy(libmongodbcapi_instance* const db, + libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, [&](libmongodbcapi_status& status) { + return mongo::instance_destroy(db, status); + }); } -int libmongodbcapi_db_destroy(libmongodbcapi_db* db) { - return mongo::db_destroy(db); +libmongodbcapi_client* libmongodbcapi_client_create(libmongodbcapi_instance* const db, + libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, + [&](libmongodbcapi_status& status) { return mongo::client_new(db, status); }); } -int libmongodbcapi_db_pump(libmongodbcapi_db* p) { - return mongo::db_pump(p); +int libmongodbcapi_client_destroy(libmongodbcapi_client* const client, + libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, [&](libmongodbcapi_status& status) { + return mongo::client_destroy(client, status); + }); } -libmongodbcapi_client* libmongodbcapi_db_client_new(libmongodbcapi_db* db) { - return mongo::client_new(db); +int libmongodbcapi_client_invoke(libmongodbcapi_client* const client, + const void* input, + const size_t input_size, + void** const output, + size_t* const output_size, + libmongodbcapi_status* const statusPtr) { + return enterCXX(statusPtr, [&](libmongodbcapi_status& status) { + return mongo::client_wire_protocol_rpc( + client, input, input_size, output, output_size, status); + }); } -void libmongodbcapi_db_client_destroy(libmongodbcapi_client* client) { - return mongo::client_destroy(client); +int libmongodbcapi_status_get_error(const libmongodbcapi_status* const status) { + return mongo::capi_status_get_error(status); } -int libmongodbcapi_db_client_wire_protocol_rpc(libmongodbcapi_client* client, - const void* input, - size_t input_size, - void** output, - size_t* output_size) { - return mongo::client_wire_protocol_rpc(client, input, input_size, output, output_size); +const char* libmongodbcapi_status_get_explanation(const libmongodbcapi_status* const status) { + return mongo::capi_status_get_what(status); } -int libmongodbcapi_get_last_error() { - return mongo::get_last_capi_error(); +int libmongodbcapi_status_get_code(const libmongodbcapi_status* const status) { + return mongo::capi_status_get_code(status); } + +libmongodbcapi_status* libmongodbcapi_status_create(void) { + return new libmongodbcapi_status; +} + +void libmongodbcapi_status_destroy(libmongodbcapi_status* const status) { + delete status; } + +} // extern "C" diff --git a/src/mongo/client/embedded/libmongodbcapi.h b/src/mongo/client/embedded/libmongodbcapi.h index a1be275a31c..266013f2d09 100644 --- a/src/mongo/client/embedded/libmongodbcapi.h +++ b/src/mongo/client/embedded/libmongodbcapi.h @@ -1,4 +1,4 @@ -/** +/*- * Copyright (C) 2017 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify @@ -7,11 +7,11 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain @@ -31,16 +31,282 @@ #include #include +#ifdef _DOXYGEN +/** + * Embeddable MongoDB Library. + * + * @invariant All functions in this library (those `extern "C"` functions starting with + * `libmongodbcapi_` in their names) have undefined behavior unless their thread safety requirements + * are met. + * + * We define "Thread Safety" to mean that a program will not exhibit undefined behavior in multiple + * concurrent execution contexts over this library. Please note, however, that values returned from + * a function may be stale, if the parameter objects passed to that function are subsequently passed + * to any function in another thread. Although the library will not exhibit undefined behavior, the + * program may not function as desired. + * + * @note The definition of "undefined behavior" with respect to this library includes any + * undocumented result up to and including undefined behavior of the entire program under the C and + * C++ language standards. + * @note The specification of post-conditions in this library only holds if undefined behavior does + * not occur. + * @note Some functions provide runtime diagnostics for some violations of their preconditions -- + * this behavior is not guaranteed and is provided as a convenience for both debugging and + * protection of data integrity. Some of these diagnostics are documented in the return-value + * specifications for these functions; however, if such a return value would be returned as the + * result of a violation of a precondition, then this return value is not guaranteed in this or any + * future release. + */ +namespace LibMongoDBCAPI { +// Doxygen requires a namespace when processing global scope functions, in order to generate +// documentation. We also use it as a hook to provide library-wide documentation. +#endif + #ifdef __cplusplus extern "C" { #endif -typedef struct libmongodbcapi_db libmongodbcapi_db; -typedef struct libmongodbcapi_client libmongodbcapi_client; +/** + * An object which describes the details of the failure of an operation. + * + * The Embedded MongoDB Library (most `libmongodbcapi_` prefixed functions) uses allocated objects + * of this type to report the details of any failure, when an operation cannot be completed. + * Several `libmongodbcapi_status` functions are provided which permit observing the details of + * these failures. Further a construction function and a destruction function for these objects are + * also provided. + * + * @invariant The use of `libmongodbcapi_status` objects from multiple threads is not threadsafe + * unless all of the threads accessing a single `libmongodbcapi_status` object are passing that + * object as a const-qualified (`const libmongodbcapi_status *`) pointer. If a single thread is + * passing a `libmongodbcapi_status` object a function taking it by non-const-qualified + * (`libmongodbcapi_status *`) pointer, then no other thread may access the `libmongodbcapi_status` + * object. + * + * @note All `libmongodbcapi_` functions which take a `status` object may be passed a `NULL` + * pointer. In that case the function will not be able to report detailed status information; + * however, that function may still be called. + * + * @note All `libmongodbcapi_status` functions can be used before the `libmongodbcapi` library is + * initialized. This facilitates detailed error reporting from all library functions. + */ +typedef struct libmongodbcapi_status libmongodbcapi_status; + +/** + * Allocate and construct an API-return-status buffer object of type `libmongodbcapi_status`. + * + * All `libmongodbcapi_` functions outside of the `libmongodbcapi_status` family accept pointers to + * these objects (specifically a parameter of type `libmongodbcapi_status *`). These functions use + * that output-parameter as a mechanism for detailed error reporting. If a `NULL` pointer is + * passed, then these functions will not be able to report the details of their error. + * + * @pre None. + * + * @returns A pointer to a newly allocated `libmongodbcapi_status` object which will hold details of + * any failures of operations to which it was passed. + * @returns `NULL` when construction of a `libmongodbcapi_status` object fails. + * + * @invariant This function is completely threadsafe. + * + * @note It is possible to use the rest of the `libmongodbcapi` functions without status objects if + * detailed error reporting is unnecessary; however, if allocation of status objects fails it is + * likely that all other `libmongodbcapi` operations will fail as well. + * @note Allocation of an Embedded MongoDB Status buffer should rarely fail, except for + * out-of-memory reasons. + * + * @note This function may be called before `libmongodbcapi_lib_init`. + */ +libmongodbcapi_status* libmongodbcapi_status_create(void); + +/** + * Destroys a valid `libmongodbcapi_status` object. + * + * Frees the storage associated with a valid `libmongodbcapi_status` object including all shared + * observable storage, such as strings. The only way that a `libmongodbcapi_status` can be validly + * created is via `libmongodbcapi_status_create`, therefore the object being destroyed must have + * been created using that function. + * + * @pre The specified `status` object must not be `NULL`. + * @pre The specified `status` object must be a valid `libmongodbcapi_status` object. + * + * @param status The `libmongodbcapi_status` object to release. + * + * @invariant This function is not threadsafe unless the specified `status` object is not passed + * concurrently to any other function. It is safe to destroy distinct `libmongodbcapi_status` + * objects on distinct threads. + * + * @note This function does not report failures. + * @note This behavior of this function is undefined unless its preconditions are met. + * + * @note This function may be called before `libmongodbcapi_lib_init`. + * + * @note This function causes all storage associated with the specified `status` to be released, + * including the storage referenced by functions that returned observable storage buffers from this + * status, such as strings. + */ +void libmongodbcapi_status_destroy(libmongodbcapi_status* status); + +/** + * The error codes reported by `libmongodbcapi` functions will be given the symbolic names as mapped + * by this enum. + * + * When a `libmongdbcapi` function fails (and it has been documented report errors) it will report + * that error in the form of an `int` status code. That status code will always be returned as the + * type `int`; however, the values in this enum can be used to classify the failure. + */ +typedef enum { + LIBMONGODB_CAPI_ERROR_IN_REPORTING_ERROR = -2, + LIBMONGODB_CAPI_ERROR_UNKNOWN = -1, + LIBMONGODB_CAPI_SUCCESS = 0, + + LIBMONGODB_CAPI_ERROR_ENOMEM = 1, + LIBMONGODB_CAPI_ERROR_EXCEPTION = 2, + LIBMONGODB_CAPI_ERROR_LIBRARY_ALREADY_INITIALIZED = 3, + LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED = 4, + LIBMONGODB_CAPI_ERROR_INVALID_LIB_HANDLE = 5, + LIBMONGODB_CAPI_ERROR_DB_INITIALIZATION_FAILED = 6, + LIBMONGODB_CAPI_ERROR_INVALID_DB_HANDLE = 7, + LIBMONGODB_CAPI_ERROR_HAS_DB_HANDLES_OPEN = 8, + LIBMONGODB_CAPI_ERROR_DB_MAX_OPEN = 9, + LIBMONGODB_CAPI_ERROR_DB_CLIENTS_OPEN = 10, + LIBMONGODB_CAPI_ERROR_INVALID_CLIENT_HANDLE = 11, + LIBMONGODB_CAPI_ERROR_REENTRANCY_NOT_ALLOWED = 12, +} libmongodbcapi_error; + +/** + * Gets an error code from a `libmongodbcapi_status` object. + * + * When a `libmongodbcapi` function fails (and it has been documented to report errors) it will + * report its error in the form of an `int` status code which is stored into a supplied + * `libmongodbcapi_status` object, if provided. Some of these functions may also report extra + * information, which will be reported by other observer functions. Every `libmongodbcapi` function + * which reports errors will always update the `Error` code stored in a `libmongodbcapi_status` + * object, even upon success. + * + * @pre The specified `status` object must not be `NULL`. + * @pre The specified `status` object must be a valid `libmongodbcapi_status` object. + * @pre The specified `status` object must have been passed to a `libmongodbcapi` function. + * + * @param status The `libmongodbcapi_status` object from which to get an associated error code. + * + * @returns `LIBMONGODB_CAPI_SUCCESS` if the last function to which `status` was passed succeeded. + * @returns The `libmongodbcapi_error` code associated with the `status` parameter. + * + * @invariant This function is thread-safe, if the thread safety requirements specified by + * `libmongodbcapi_status`'s invariants are met. + * + * @note This function will report the `libmongodbcapi_error` value for the failure associated with + * `status`, therefore if the failing function returned a `libmongodbcapi_error` value itself, then + * calling this function is superfluous. + * + * @note This function does not report its own failures. + * @note This behavior of this function is undefined unless its preconditions are met. + */ +int libmongodbcapi_status_get_error(const libmongodbcapi_status* status); + +/** + * Gets a descriptive error message from a `libmongodbcapi_status` object. + * + * Any `libmongodbcapi` function which reports failure must, when it fails, update the specified + * `libmongodbcapi_status` object, if it exists, to contain a string indicating a user-readable + * description of the failure. This error message string is dependent upon the kind of error + * encountered and may contain dynamically generated information describing the specifics of the + * failure. + * + * @pre The specified `status` must not be `NULL`. + * @pre The specified `status` must be a valid `libmongodbcapi_status` object. + * @pre The specified `status` must have been passed to a `libmongodbcapi` function. + * @pre The function to which the specified `status` was passed must not have returned + * `LIBMONGODB_CAPI_SUCCESS` as its error code. + * + * @param status The `libmongodbcapi_status` object from which to get an associated error message. + * + * @returns A null-terminated string containing an error message. This string will be valid until + * the next time that the specified `status` is passed to any other `libmongodbcapi` function + * (including those in the `libmongodbcapi_status` family). + * + * @invariant This function is thread-safe, if the thread safety requirements specified by + * `libmongodbcapi_status`'s invariants are met; however, the pointer returned by this function is + * considered to be part of the specified `status` object for the purposes of thread safety. If the + * `libmongodbcapi_status` is changed, by any thread, it will invalidate the string returned by this + * function. + * + * @note For failures where the `libmongodbcapi_status_cet_error( status ) == + * LIBMONGODB_CAPI_ERROR_EXCEPTION`, this returns a string representation of the internal C++ + * exception. + * + * @note The storage for the returned string is associated with the specified `status` object, and + * therefore it will be deallocated when the `status` is destroyed using + * `libmongodbcapi_destroy_status`. + * + * @note This function does not report its own failures. + * @note This behavior of this function is undefined unless its preconditions are met. + */ +const char* libmongodbcapi_status_get_explanation(const libmongodbcapi_status* status); /** - * Log callback. For details on what the parameters mean, - * see the documentation at https://docs.mongodb.com/manual/reference/log-messages/ + * Gets a status code from a `libmongodbcapi_status` object. + * + * Any `libmongodbcapi` function which reports failure must, when it fails, update the specified + * `libmongodbcapi_status` object, if it exists, to contain a numeric code indicating a sub-category + * of failure. This error code is one specified by the normal MongoDB Driver interface, if + * `libmongodbcapi_error == LIBMONGODB_CAPI_ERROR_EXCEPTION`. + * + * @pre The specified `status` must not be `NULL`. + * @pre The specified `status` must be a valid `libmongodbcapi_status` object. + * @pre The specified `status` must have been passed to a `libmongodbcapi` function. + * @pre The function to which the specified `status` was passed must not have returned + * `LIBMONGODB_CAPI_SUCCESS` as its error code. + * + * @param status The `libmongodbcapi_status` object from which to get an associated status code. + * + * @returns A numeric status code associated with the `status` parameter which indicates a + * sub-category of failure. + * + * @invariant This function is thread-safe, if the thread safety requirements specified by + * `libmongodbcapi_status`'s invariants are met. + * + * @note For failures where the `libmongodbcapi_error == LIBMONGODB_CAPI_ERROR_EXCEPTION` and the + * exception was of type `mongo::DBException`, this returns the numeric code indicating which + * specific `mongo::DBException` was thrown. + * + * @note For failures where the `libmongodbcapi_error != LIBMONGODB_CAPI_ERROR_EXCEPTION` the value + * of this code is unspecified. + * + * @note This function does not report its own failures. + * @note This behavior of this function is undefined unless its preconditions are met. + */ +int libmongodbcapi_status_get_code(const libmongodbcapi_status* status); + + +/** + * An object which describes the runtime state of the Embedded MongoDB Library. + * + * The `libmongodbcapi` library uses allocated objects of this type to indicate the present state of + * the library. Some operations which the library provides need access to this object. Further a + * construction function and a destruction function for these objects are also provided. No more + * than a single object instance of this type may exist at any given time. + * + * @invariant The use of `libmongodbcapi_lib` objects from multiple threads is not threadsafe unless + * all of the threads accessing a single `libmongodbcapi_lib` object are not destroying this object. + * If a single thread is passing a `libmongodbcapi_lib` to its destruction function, then no other + * thread may access the `libmongodbcapi_status` object. + */ +typedef struct libmongodbcapi_lib libmongodbcapi_lib; + +/** + * An object which describes the runtime state of the Embedded MongoDB Library. + * + * The `libmongodbcapi` library uses structures of this type to indicate the desired configuration + * of the library. + * + * @invariant Because the library is only initialized once, in a single-threaded fashion, there are + * no thread-safety requirements on this type. + */ +typedef struct libmongodbcapi_init_params libmongodbcapi_init_params; + +/** + * Log callback. For details on what the parameters mean, see the documentation at + * https://docs.mongodb.com/manual/reference/log-messages/ * * Severity values, lower means more severe. * Severe/Fatal = -4 @@ -53,18 +319,9 @@ typedef struct libmongodbcapi_client libmongodbcapi_client; typedef void (*libmongodbcapi_log_callback)( void* user_data, const char* message, const char* component, const char* context, int severity); -typedef enum { - LIBMONGODB_CAPI_ERROR_UNKNOWN = -1, - LIBMONGODB_CAPI_SUCCESS = 0, - - LIBMONGODB_CAPI_ERROR_LIBRARY_ALREADY_INITIALIZED, - LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED, - LIBMONGODB_CAPI_ERROR_DB_OPEN, -} libmongodbcapi_error; - /** - Valid bits for the log_flags bitfield in libmongodbcapi_init_params. -*/ + * Valid bits for the log_flags bitfield in libmongodbcapi_init_params. + */ typedef enum { /** Placeholder for no logging */ LIBMONGODB_CAPI_LOG_NONE = 0, @@ -79,7 +336,8 @@ typedef enum { LIBMONGODB_CAPI_LOG_CALLBACK = 4 } libmongodbcapi_log_flags; -typedef struct { +// See the documentation of this object on the comments above its forward declaration +struct libmongodbcapi_init_params { /** * Optional null-terminated YAML formatted MongoDB configuration string. * See documentation for valid options. @@ -102,120 +360,313 @@ typedef struct { * Optional user data to be returned in the log callback. */ void* log_user_data; -} libmongodbcapi_init_params; - -/** -* Initializes the mongodbcapi library, required before any other call. Cannot be called again -* without libmongodbcapi_fini() being called first. -* -* @param params pointer to libmongodbcapi_init_params containing library initialization parameters. -* Allowed to be NULL. -* -* @note This function is not thread safe. -* -* @return Returns LIBMONGODB_CAPI_SUCCESS on success. -* @return Returns LIBMONGODB_CAPI_ERROR_LIBRARY_ALREADY_INITIALIZED if libmongodbcapi_init() has -* already been called without an intervening call to libmongodbcapi_fini(). -*/ -int libmongodbcapi_init(const libmongodbcapi_init_params* params); - -/** -* Tears down the state of the library, all databases must be closed before calling this. -* -* @note This function is not thread safe. -* -* @return Returns LIBMONGODB_CAPI_SUCCESS on success. -* @return Returns LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED if libmongodbcapi_init() has not -* been called previously. -* @return Returns LIBMONGODB_CAPI_ERROR_DB_OPEN if there are open databases that haven't been closed -* with libmongodbcapi_db_destroy(). -* @return Returns LIBMONGODB_CAPI_ERROR_UNKNOWN for any other unspecified errors. -*/ -int libmongodbcapi_fini(); - -/** -* Starts the database and returns a handle with the service context. -* -* @param config null-terminated YAML formatted MongoDB configuration. See documentation for valid -* options. -* -* @return A pointer to a db handle or null on error -*/ -libmongodbcapi_db* libmongodbcapi_db_new(const char* yaml_config); - -/** -* Shuts down the database -* -* @param -* A pointer to a db handle to be destroyed -* -* @return A libmongo error code -*/ -int libmongodbcapi_db_destroy(libmongodbcapi_db* db); - -/** -* Let the database do background work. Returns an int from the error enum -* -* @param -* The database that has work that needs to be done -* -* @return Returns LIBMONGODB_CAPI_SUCCESS on success, or an error code from libmongodbcapi_error on -* failure. -*/ -int libmongodbcapi_db_pump(libmongodbcapi_db* db); - -/** -* Creates a new clienst and retuns it so the caller can do operation -* A client will be destroyed when the owning db is destroyed -* -* @param db -* The datadase that will own this client and execute its RPC calls -* -* @return A pointer to a client or null on error -*/ -libmongodbcapi_client* libmongodbcapi_db_client_new(libmongodbcapi_db* db); - -/** -* Destroys a client and removes it from the db/service context -* Cannot be called after the owning db is destroyed -* -* @param client -* A pointer to the client to be destroyed -*/ -void libmongodbcapi_db_client_destroy(libmongodbcapi_client* client); - -/** -* Makes an RPC call to the database -* -* @param client -* The client that will be performing the query on the database -* @param input -* The query to be sent to and then executed by the database -* @param input_size -* The size (number of bytes) of the input query -* @param output -* A pointer to a void * where the database can write the location of the output. -* The library will manage the memory pointer to by *output. -* @TODO document lifetime of this buffer -* @param output_size -* A pointer to a location where this function will write the size (number of bytes) -* of the output -* -* @return Returns LIBMONGODB_CAPI_SUCCESS on success, or an error code from libmongodbcapi_error on -* failure. -*/ -int libmongodbcapi_db_client_wire_protocol_rpc(libmongodbcapi_client* client, - const void* input, - size_t input_size, - void** output, - size_t* output_size); -/** -* @return a per-thread value indicating the last error -*/ -int libmongodbcapi_get_last_error(); +}; + +/** + * Initializes the mongodbcapi library, required before any other call. + * + * The Embedded MongoDB Library must be initialized before it can be used. However, it is + * permissible to create and destroy `libmongodbcapi_status` objects without the library having been + * initialized. Initializing the library sets up internal state for all Embedded MongoDB Library + * operations, including creating embedded "server-like" instances and creating clients. + * + * @pre The specified `params` object must either be a valid `libmongodbcapi_lib_params` object (in + * a valid state) or `NULL`. + * @pre The specified `status` object must either be a valid `libmongodbcapi_status` object or + * `NULL`. + * @pre Either `libmongodbcapi_fini` must have never been called in this process, or it was called + * and returned success and `libmongodbcapi_lib_init` was not called after this. + * @pre Either `libmongodbcapi_init` must have never been called in this process, or it was called + * and then the embedded library was terminated by a successful call to `libmongodbcapi_lib_fini`. + * @pre No valid `libmongodbcapi_lib` must exist. + * + * @param params A pointer to libmongodbcapi_init_params containing library initialization + * parameters. A default configuration will be used if `params == NULL`. + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @post Either the Embedded MongoDB Library will be initialized, or an error will be reported. + * + * @returns A pointer to a `libmongodbcapi_lib` object on success. + * @returns `NULL` and modifies `status` on failure. + * + * @invariant This function is not thread safe. It must be called and have completed before any + * other non-`libmongodbcapi_status` operations can be called on any thread. + * + * @note This function exhibits undefined behavior unless its preconditions are met. + * @note This function may return diagnosic errors for violations of its preconditions, but this + * behavior is not guaranteed. + */ +libmongodbcapi_lib* libmongodbcapi_lib_init(const libmongodbcapi_init_params* params, + libmongodbcapi_status* status); + +/** + * Tears down the state of the library, all databases must be closed before calling this. + * + * The Embedded MongoDB Library must be quiesced before the containg process can be safely + * terminated. Dataloss is not a risk; however, some database repair routines may be executed at + * next initialization if the library is not properly quiesced. It is permissible to create and + * destroy `libmongodbcapi_status` objects after the library has been quiesced. The library may be + * re-initialized with a potentially different configuration after it has been queisced. + * + * @pre All `libmongodbcapi_instance` objects associated with this library handle must be destroyed. + * @pre The specified `lib` object must not be `NULL`. + * @pre The specified `lib` object must be a valid `libmongodbcapi_lib` object. + * @pre The specified `status` object must either be a valid `libmongodbcapi_status` object or + * `NULL`. + * + * @param lib A pointer to a `libmongodbcapi_lib` handle which represents this library. + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @post Either the Embedded MongoDB Library will be deinitialized, or an error will be reported. + * + * @returns Returns `LIBMONGODB_CAPI_SUCCESS` on success. + * @returns Returns `LIBMONGODB_CAPI_ERROR_LIBRARY_NOT_INITIALIZED` and modifies `status` if + * libmongodbcapi_lib_init() has not been called previously. + * @returns Returns `LIBMONGODB_CAPI_ERROR_HAS_DB_HANDLES_OPEN` and modifies `status` if there are + * open databases that haven't been closed with `libmongodbcapi_instance_create()`. + * @returns Returns `LIBMONGODB_CAPI_ERROR_EXCEPTION` and modifies `status` for errors that resulted + * in an exception. Details can be retrived via `libmongodbcapi_process_get_status()`. + * + * @invariant This function is not thread safe. It cannot be called concurrently with any other + * non-`libmongodbcapi_status` operation. + * + * @note This function exhibits undefined behavior unless its preconditions are met. + * @note This function may return diagnosic errors for violations of its preconditions, but this + * behavior is not guaranteed. + */ +int libmongodbcapi_lib_fini(libmongodbcapi_lib* lib, libmongodbcapi_status* status); + +/** + * An object which represents an instance of an Embedded MongoDB Server. + * + * The Embedded MongoDB Library uses allocated objects of this type (`libmongodbcapi_instance`) to + * indicate the present state of a single "server-like" MongoDB instance. Some operations which the + * library provides need access to this object. Further a construction function and a destruction + * function for these objects are also provided. No more than a single object instance of this type + * may exist at any given time. + * + * @invariant The use of `libmongodbcapi_instance` objects from multiple threads is not threadsafe + * unless all of the threads accessing a single `libmongodbcapi_instance` object are not destroying + * this object. If a single thread is passing a `libmongodbcapi_instance` to its destruction + * function, then no other thread may access the `libmongodbcapi_instance` object. + */ +typedef struct libmongodbcapi_instance libmongodbcapi_instance; + +/** + * Creates an embedded MongoDB instance and returns a handle with the service context. + * + * A `libmongodbcapi_instance` object which represents a single embedded "server-like" context is + * created and returned by this function. At present, only a single server-like instance is + * supported; however, multiple concurrent "server-like" instances may be permissible in future + * releases. + * + * @pre The specified `lib` object must not be `NULL` + * @pre The specified `lib` object must be a valid `libmongodbcapi_lib` object. + * @pre The specified `yaml_config` string must either point to an ASCII null-terminated string or + * be `NULL`. + * @pre The specified `status` object must be either a valid `libmongodbcapi_status` object or + * `NULL`. + * + * @param lib A pointer to a `libmongodbcapi_lib` handle which represents the Embedded MongoDB + * Library. + * + * @param yaml_config A null-terminated YAML formatted Embedded MongoDB Instance configuration. See + * documentation for valid options. + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @post Either a new Embedded MongoDB Server will be created, or an error will be reported. + * + * @return A pointer to a newly constructed, valid `libmongdbcapi_instance`. + * @return `NULL` and modifies `status` on failure. + * + * @invariant This function is completely threadsafe, as long as its preconditions are met. + * + * @note This function exhibits undefined behavior unless its preconditions are met. + * @note This function may return diagnosic errors for violations of its preconditions, but this + * behavior is not guaranteed. + */ +libmongodbcapi_instance* libmongodbcapi_instance_create(libmongodbcapi_lib* lib, + const char* yaml_config, + libmongodbcapi_status* status); + +/** + * Shuts down an embedded MongoDB instance. + * + * A `libmongodbcapi_instance` embedded "server-like" instance can be terminated by this function. + * All resources used by this instance will be released, and all background tasks associated with it + * will be terminated. + * + * @pre The specified `instance` object must not be `NULL`. + * @pre The specified `instance` object must be a valid `libmongodbcapi_instance` object. + * @pre The specified `status` object must be either a valid `libmongodbcapi_status` object or + * `NULL`. + * @pre All `libmongodbcapi_client` objects associated with this database must be destroyed. + * + * @param instance A pointer to a valid `libmongodbcapi_instance` instance to be destroyed. + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @post Either the specified Embedded MongoDB Server will be destroyed, or an error will be + * reported. + * + * @returns `LIBMONGODB_CAPI_SUCCESS` on success. + * @returns `LIBMONGODB_CAPI_ERROR_DB_CLIENTS_OPEN` and modifies `status` if there are + * `libmongodbcapi_client` objects still open attached to the `instance`. + * @returns `LIBMONGODB_CAPI_ERROR_EXCEPTION`and modifies `status` for other unspecified errors. + * + * @invariant This function is not threadsafe unless the specified `db` object is not passed + * concurrently to any other function. It is safe to destroy distinct `libmongodbcapi_instance` + * objects on distinct threads. + * + * @note This function exhibits undefined behavior unless its preconditions are met. + * @note This function may return diagnosic errors for violations of its precondition, but this + * behavior is not guaranteed. + */ +int libmongodbcapi_instance_destroy(libmongodbcapi_instance* instance, + libmongodbcapi_status* status); + +/** + * An object which represents "client connection" to an Embedded MongoDB Server. + * + * A `libmongodbcapi_client` connection object is necessary to perform most database operations, + * such as queries. Some operations which the library provides need access to this object. Further + * a construction function and a destruction function for these objects are also provided. Multiple + * object instances of this type may exist at any given time. + * + * @invariant The use of `libmongodbcapi_client` objects from multiple threads is not threadsafe. + */ +typedef struct libmongodbcapi_client libmongodbcapi_client; + +/** + * Creates a new client and returns it. + * + * A client must be created in order to perform database operations. + * + * @pre The specified `instance` object must not be `NULL` + * @pre The specified `instance` object must be a valid `libmongodbcapi_instance` object. + * @pre The specified `status` object must be either a valid `libmongodbcapi_status` object or + * `NULL`. + * + * @post Either a new Embedded MongoDB Client will be created, or an error will be reported. + * + * @param instance The Embedded MongoDB Server instance that will be attached to this client and + * execute its RPC calls + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @return A pointer to a newly constructed, valid `libmongodbcapi_client`. + * @return `NULL` on error, and modifies `status` on failure. + * + * @invariant This function is completely threadsafe, as long as its preconditions are met. + */ +libmongodbcapi_client* libmongodbcapi_client_create(libmongodbcapi_instance* instance, + libmongodbcapi_status* status); + +/** + * Destroys an Embedded MongoDB Client. + * + * A client must be destroyed before the owning db is destroyed. Database clients must be destroyed + * before the instance associated with them can be destroyed. Further, any resources associated + * with client requests will be relinquished after this call completes. + * + * @pre The specified `client` object must not be `NULL`. + * @pre The specified `client` object must be a valid `libmongodbcapi_client` object. + * @pre The specified `status` object must be either a valid `libmongodbcapi_status` object or + * `NULL`. + * + * @param client A pointer to the client to be destroyed + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @post Either the specified Embedded MongoDB Client will be destroyed, or an error will be + * reported. + * + * @returns `LIBMONGODB_CAPI_SUCCESS` on success. + * @returns An error code and modifies the specified `status` object on failure. + * + * @invariant This function is not threadsafe unless the specified `client` object is not passed + * concurrently to any other function. It is safe to destroy distinct `libmongodbcapi_client` + * objects on distinct threads. + * + * @note This function exhibits undefined behavior unless its preconditions are met. + * @note This function may return diagnosic errors for violations of its precondition, but this + * behavior is not guaranteed. + */ +int libmongodbcapi_client_destroy(libmongodbcapi_client* client, libmongodbcapi_status* status); + +/** + * Makes an RPC call to the database. + * + * A MongoDB client operation is performed according to the provided BSON object specified by + * `input` and `input_size`. + * + * @pre The specified `client` object must not be `NULL`. + * @pre The specified `client` object must be a valid `libmongodbcapi_client` object. + * @pre The specified `input` buffer must not be `NULL`. + * @pre The specified `input` buffer must be a valid BSON request. + * @pre The specified `output` pointer must not be `NULL` + * @pre The specified `output` pointer must point to a valid, non-const `void *` variable. + * @pre The specified `output_size` pointer must not be `NULL` + * @pre The specified `output` pointer must point to a valid, non-const `size_t` variable. + * @pre The specified `status` object must be either a valid `libmongodbcapi_status` object or + * `NULL`. + * + * @param client The client that will be performing the query on the database + * + * @param input The query to be sent to and then executed by the database + * + * @param input_size The size (number of bytes) of the input query + * + * @param output A pointer to a `void *` where the database can write the location of the output. + * The library will manage the memory pointed to by * `output`. + * + * @param output_size A pointer to a location where this function will write the size (number of + * bytes) of the `output` buffer. + * + * @param status A pointer to a `libmongodbcapi_status` object which will not be modified unless + * this function reports a failure. + * + * @post Either the requested database operation will have been performed, or an error will be + * reported. + * + * @return Returns LIBMONGODB_CAPI_SUCCESS on success. + * @return An error code and modifies `status` on failure + * + * @invariant This function is not thread-safe unless its preconditions are met, and the specified + * `libmongodbcapi_client` object is not concurrently accessed by any other thread until after this + * call has completed. + * + * @note The `output` and `output_size` parameters will not be modified unless the function + * succeeds. + * @note The storage associated with `output` will be valid until the next call to + * `libmongodbcapi_client_wire_protocol_rpc` on the specified `client` object, or the `client` is + * destroyed using `libmongodbcapi_client_destroy`. + * @note That the storage which is referenced by `output` upon successful completion is considered + * to be part of the specified `client` object for the purposes of thread-safety and undefined + * behavior. + */ +int libmongodbcapi_client_invoke(libmongodbcapi_client* client, + const void* input, + size_t input_size, + void** output, + size_t* output_size, + libmongodbcapi_status* status); #ifdef __cplusplus -} +} // extern "C" +#endif + +#ifdef _DOXYGEN +} // namespace LibMongoDBCAPI #endif #endif diff --git a/src/mongo/client/embedded/libmongodbcapi_test.cpp b/src/mongo/client/embedded/libmongodbcapi_test.cpp index 625c64a62f1..faa435685be 100644 --- a/src/mongo/client/embedded/libmongodbcapi_test.cpp +++ b/src/mongo/client/embedded/libmongodbcapi_test.cpp @@ -29,6 +29,7 @@ #include "mongo/client/embedded/libmongodbcapi.h" +#include #include #include @@ -36,7 +37,6 @@ #include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/json.h" #include "mongo/db/server_options.h" -#include "mongo/stdx/memory.h" #include "mongo/stdx/thread.h" #include "mongo/unittest/temp_dir.h" #include "mongo/unittest/unittest.h" @@ -51,27 +51,60 @@ namespace moe = mongo::optionenvironment; +libmongodbcapi_lib* global_lib_handle; + namespace { std::unique_ptr globalTempDir; -struct MongodCapiCleaner { - void operator()(libmongodbcapi_client* const p) { - if (p) { - libmongodbcapi_db_client_destroy(p); +struct StatusDestructor { + void operator()(libmongodbcapi_status* const p) const noexcept { + if (p) + libmongodbcapi_status_destroy(p); + } +}; + +using CapiStatusPtr = std::unique_ptr; + +CapiStatusPtr makeStatusPtr() { + return CapiStatusPtr{libmongodbcapi_status_create()}; +} + +struct ClientDestructor { + void operator()(libmongodbcapi_client* const p) const noexcept { + if (!p) + return; + + auto status = makeStatusPtr(); + if (libmongodbcapi_client_destroy(p, status.get()) != LIBMONGODB_CAPI_SUCCESS) { + std::cerr << "libmongodb_capi_client_destroy failed." << std::endl; + if (status) { + std::cerr << "Error code: " << libmongodbcapi_status_get_error(status.get()) + << std::endl; + std::cerr << "Error message: " + << libmongodbcapi_status_get_explanation(status.get()) << std::endl; + } } } }; -using MongoDBCAPIClientPtr = std::unique_ptr; +using MongoDBCAPIClientPtr = std::unique_ptr; class MongodbCAPITest : public mongo::unittest::Test { protected: void setUp() { + status = libmongodbcapi_status_create(); + ASSERT(status != nullptr); + if (!globalTempDir) { - globalTempDir = mongo::stdx::make_unique("embedded_mongo"); + globalTempDir = std::make_unique("embedded_mongo"); } + libmongodbcapi_init_params params; + params.log_flags = 0; + params.log_callback = nullptr; + params.log_user_data = nullptr; + YAML::Emitter yaml; yaml << YAML::BeginMap; @@ -83,23 +116,30 @@ protected: yaml << YAML::EndMap; - db = libmongodbcapi_db_new(yaml.c_str()); - ASSERT(db != nullptr); + params.yaml_config = yaml.c_str(); + + lib = libmongodbcapi_lib_init(¶ms, status); + ASSERT(lib != nullptr) << libmongodbcapi_status_get_explanation(status); + + db = libmongodbcapi_instance_create(lib, yaml.c_str(), status); + ASSERT(db != nullptr) << libmongodbcapi_status_get_explanation(status); } void tearDown() { - libmongodbcapi_db_destroy(db); - ASSERT_EQUALS(libmongodbcapi_get_last_error(), LIBMONGODB_CAPI_SUCCESS); + ASSERT_EQUALS(libmongodbcapi_instance_destroy(db, status), LIBMONGODB_CAPI_SUCCESS) + << libmongodbcapi_status_get_explanation(status); + ASSERT_EQUALS(libmongodbcapi_lib_fini(lib, status), LIBMONGODB_CAPI_SUCCESS) + << libmongodbcapi_status_get_explanation(status); + libmongodbcapi_status_destroy(status); } - libmongodbcapi_db* getDB() { + libmongodbcapi_instance* getDB() const { return db; } - MongoDBCAPIClientPtr createClient() { - MongoDBCAPIClientPtr client(libmongodbcapi_db_client_new(db)); - ASSERT(client != nullptr); - ASSERT_EQUALS(libmongodbcapi_get_last_error(), LIBMONGODB_CAPI_SUCCESS); + MongoDBCAPIClientPtr createClient() const { + MongoDBCAPIClientPtr client(libmongodbcapi_client_create(db, status)); + ASSERT(client.get() != nullptr) << libmongodbcapi_status_get_explanation(status); return client; } @@ -118,8 +158,8 @@ protected: size_t outputSize; // call the wire protocol - int err = libmongodbcapi_db_client_wire_protocol_rpc( - client.get(), inputMessage.buf(), inputMessage.size(), &output, &outputSize); + int err = libmongodbcapi_client_invoke( + client.get(), inputMessage.buf(), inputMessage.size(), &output, &outputSize, status); ASSERT_EQUALS(err, LIBMONGODB_CAPI_SUCCESS); // convert the shared buffer to a mongo::message and ensure that it is valid @@ -134,8 +174,10 @@ protected: } -private: - libmongodbcapi_db* db; +protected: + libmongodbcapi_lib* lib; + libmongodbcapi_instance* db; + libmongodbcapi_status* status; }; TEST_F(MongodbCAPITest, CreateAndDestroyDB) { @@ -149,7 +191,7 @@ TEST_F(MongodbCAPITest, CreateAndDestroyDBAndClient) { // This test is to make sure that destroying the db will fail if there's remaining clients left. TEST_F(MongodbCAPITest, DoNotDestroyClient) { auto client = createClient(); - ASSERT(libmongodbcapi_db_destroy(getDB()) != LIBMONGODB_CAPI_SUCCESS); + ASSERT(libmongodbcapi_instance_destroy(getDB(), nullptr) != LIBMONGODB_CAPI_SUCCESS); } TEST_F(MongodbCAPITest, CreateMultipleClients) { @@ -164,12 +206,6 @@ TEST_F(MongodbCAPITest, CreateMultipleClients) { ASSERT_EQUALS(static_cast(clients.size()), numClients); } -TEST_F(MongodbCAPITest, DBPump) { - libmongodbcapi_db* db = getDB(); - int err = libmongodbcapi_db_pump(db); - ASSERT_EQUALS(err, LIBMONGODB_CAPI_SUCCESS); -} - TEST_F(MongodbCAPITest, IsMaster) { // create the client object auto client = createClient(); @@ -485,9 +521,11 @@ TEST_F(MongodbCAPITest, InsertAndUpdate) { // This test is temporary to make sure that only one database can be created // This restriction may be relaxed at a later time TEST_F(MongodbCAPITest, CreateMultipleDBs) { - libmongodbcapi_db* db2 = libmongodbcapi_db_new(nullptr); + auto status = makeStatusPtr(); + ASSERT(status.get()); + libmongodbcapi_instance* db2 = libmongodbcapi_instance_create(lib, nullptr, status.get()); ASSERT(db2 == nullptr); - ASSERT_EQUALS(libmongodbcapi_get_last_error(), LIBMONGODB_CAPI_ERROR_UNKNOWN); + ASSERT_EQUALS(libmongodbcapi_status_get_error(status.get()), LIBMONGODB_CAPI_ERROR_DB_MAX_OPEN); } } // namespace @@ -495,16 +533,16 @@ TEST_F(MongodbCAPITest, CreateMultipleDBs) { // These test functions cannot use the main() defined for unittests because they // call runGlobalInitializers(). The embedded C API calls mongoDbMain() which // calls runGlobalInitializers(). -int main(int argc, char** argv, char** envp) { - moe::OptionsParser parser; +int main(const int argc, const char* const* const argv) { moe::Environment environment; moe::OptionSection options; - std::map env; options.addOptionChaining( "tempPath", "tempPath", moe::String, "directory to place mongo::TempDir subdirectories"); - std::vector argVector(argv, argv + argc); - mongo::Status ret = parser.run(options, argVector, env, &environment); + + std::map env; + mongo::Status ret = moe::OptionsParser().run( + options, std::vector(argv, argv + argc), env, &environment); if (!ret.isOK()) { std::cerr << options.helpString(); return EXIT_FAILURE; @@ -518,25 +556,30 @@ int main(int argc, char** argv, char** envp) { ::mongo::serverGlobalParams.noUnixSocket = true; ::mongo::unittest::setupTestLogger(); + // Allocate an error descriptor for use in non-configured tests + const auto status = makeStatusPtr(); + mongo::setTestCommandsEnabled(true); // Check so we can initialize the library without providing init params - int init = libmongodbcapi_init(nullptr); - if (init != LIBMONGODB_CAPI_SUCCESS) { - std::cerr << "libmongodbcapi_init() failed with " << init << std::endl; + libmongodbcapi_lib* lib = libmongodbcapi_lib_init(nullptr, status.get()); + if (lib == nullptr) { + std::cerr << "libmongodbcapi_init() failed with " + << libmongodbcapi_status_get_error(status.get()) << ": " + << libmongodbcapi_status_get_explanation(status.get()) << std::endl; return EXIT_FAILURE; } - int fini = libmongodbcapi_fini(); - if (fini != LIBMONGODB_CAPI_SUCCESS) { - std::cerr << "libmongodbcapi_fini() failed with " << fini << std::endl; + if (libmongodbcapi_lib_fini(lib, status.get()) != LIBMONGODB_CAPI_SUCCESS) { + std::cerr << "libmongodbcapi_fini() failed with " + << libmongodbcapi_status_get_error(status.get()) << ": " + << libmongodbcapi_status_get_explanation(status.get()) << std::endl; return EXIT_FAILURE; } // Initialize the library with a log callback and test so we receive at least one callback // during the lifetime of the test - libmongodbcapi_init_params params; - memset(¶ms, 0, sizeof(params)); + libmongodbcapi_init_params params{}; bool receivedCallback = false; params.log_flags = LIBMONGODB_CAPI_LOG_STDOUT | LIBMONGODB_CAPI_LOG_CALLBACK; @@ -551,21 +594,24 @@ int main(int argc, char** argv, char** envp) { }; params.log_user_data = &receivedCallback; - init = libmongodbcapi_init(¶ms); - if (init != LIBMONGODB_CAPI_SUCCESS) { - std::cerr << "libmongodbcapi_init() failed with " << init << std::endl; - return EXIT_FAILURE; + lib = libmongodbcapi_lib_init(¶ms, nullptr); + if (lib == nullptr) { + std::cerr << "libmongodbcapi_init() failed with " + << libmongodbcapi_status_get_error(status.get()) << ": " + << libmongodbcapi_status_get_explanation(status.get()) << std::endl; } - ::mongo::unittest::Suite::run(std::vector(), "", 1); + if (libmongodbcapi_lib_fini(lib, nullptr) != LIBMONGODB_CAPI_SUCCESS) { + std::cerr << "libmongodbcapi_fini() failed with " + << libmongodbcapi_status_get_error(status.get()) << ": " + << libmongodbcapi_status_get_explanation(status.get()) << std::endl; + } - fini = libmongodbcapi_fini(); - if (fini != LIBMONGODB_CAPI_SUCCESS) { - std::cerr << "libmongodbcapi_fini() failed with " << fini << std::endl; - return EXIT_FAILURE; + if (!receivedCallback) { + std::cerr << "Did not get a log callback." << std::endl; } - ASSERT(receivedCallback); + ::mongo::unittest::Suite::run(std::vector(), "", 1); globalTempDir.reset(); } -- cgit v1.2.1