summaryrefslogtreecommitdiff
path: root/src/mongo/client
diff options
context:
space:
mode:
authorADAM David Alan Martin <adam.martin@10gen.com>2018-05-03 15:19:40 -0400
committerADAM David Alan Martin <adam.martin@10gen.com>2018-05-03 16:17:11 -0400
commit6b23bd026bb74b1e44d93951662070076e99b361 (patch)
tree2cf83635a86435c920fa7282aaa391f87ef16abe /src/mongo/client
parent397af5415328cbf53c7d70b514d25d32c7b8c3c6 (diff)
downloadmongo-6b23bd026bb74b1e44d93951662070076e99b361.tar.gz
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.
Diffstat (limited to 'src/mongo/client')
-rw-r--r--src/mongo/client/embedded/embedded_transport_layer.cpp19
-rw-r--r--src/mongo/client/embedded/embedded_transport_layer.h11
-rw-r--r--src/mongo/client/embedded/embedded_transport_layer_test.cpp35
-rw-r--r--src/mongo/client/embedded/libmongodbcapi.cpp614
-rw-r--r--src/mongo/client/embedded/libmongodbcapi.h713
-rw-r--r--src/mongo/client/embedded/libmongodbcapi_test.cpp150
6 files changed, 1179 insertions, 363 deletions
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<mongoc_stream_embedded_t*>(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<libmongodbcapi_instance*>(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<mongo::unittest::TempDir> globalTempDir;
@@ -54,7 +56,7 @@ class MongodbEmbeddedTransportLayerTest : public mongo::unittest::Test {
protected:
void setUp() {
if (!globalTempDir) {
- globalTempDir = mongo::stdx::make_unique<mongo::unittest::TempDir>("embedded_mongo");
+ globalTempDir = std::make_unique<mongo::unittest::TempDir>("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<libmongodbcapi_status, StatusDestroy>;
} // 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<std::string>(), "", 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 <cstring>
#include <exception>
#include <thread>
#include <unordered_map>
@@ -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<ErrorCodes::ReentrancyNotAllowed>& 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<libmongodbcapi_client*, std::unique_ptr<libmongodbcapi_client>>
- open_clients;
+ libmongodbcapi_lib() = default;
+
+ mongo::AtomicWord<int> databaseCount;
+
+ mongo::logger::ComponentMessageLogDomain::AppenderHandle logCallbackHandle;
+
+ std::unique_ptr<libmongodbcapi_instance> onlyDB;
+};
+
+namespace mongo {
+namespace {
+struct ServiceContextDestructor {
+ void operator()(mongo::ServiceContext* const serviceContext) const noexcept {
+ ::mongo::embedded::shutdown(serviceContext);
+ }
+};
+
+using EmbeddedServiceContextPtr = std::unique_ptr<mongo::ServiceContext, ServiceContextDestructor>;
+} // 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<mongo::transport::TransportLayerMock>()) {
+ 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<int> clientCount;
+
+ mongo::EmbeddedServiceContextPtr serviceContext;
std::unique_ptr<mongo::transport::TransportLayerMock> 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<unsigned char> output;
- libmongodbcapi_db* parent_db = nullptr;
+ libmongodbcapi_instance* const parent_db;
mongo::ServiceContext::UniqueClient client;
+
+ std::vector<unsigned char> 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<libmongodbcapi_lib> 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<embedded::EmbeddedLogAppender<MessageEventEphemeral>>(
- log_callback, log_user_data, std::make_unique<MessageEventUnadornedEncoder>()));
-
- 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<embedded::EmbeddedLogAppender<MessageEventEphemeral>>(
+ logCallback, logUserData, std::make_unique<MessageEventUnadornedEncoder>()));
}
-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<libmongodbcapi_lib>();
- 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<transport::TransportLayerMock>();
+ lib->onlyDB = std::make_unique<libmongodbcapi_instance>(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<libmongodbcapi_client>(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 <typename Function,
+ typename ReturnType =
+ decltype(std::declval<Function>()(*std::declval<libmongodbcapi_status*>()))>
+struct enterCXXImpl;
+
+template <typename Function>
+struct enterCXXImpl<Function, void> {
+ template <typename Callable>
+ static int call(Callable&& function, libmongodbcapi_status& status) noexcept {
+ try {
+ ReentrancyGuard singleEntrant;
+ function(status);
+ } catch (...) {
+ handleException(status);
+ }
+ return status.error;
+ }
+};
+
+
+template <typename Function, typename Pointer>
+struct enterCXXImpl<Function, Pointer*> {
+ template <typename Callable>
+ 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 <typename Callable>
+auto enterCXX(libmongodbcapi_status* const statusPtr, Callable&& c) noexcept
+ -> decltype(mongo::enterCXXImpl<Callable>::call(std::forward<Callable>(c), *statusPtr)) {
+ StatusGuard status(statusPtr);
+ return mongo::enterCXXImpl<Callable>::call(std::forward<Callable>(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 <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* 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 <stddef.h>
#include <stdint.h>
+#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 <memory>
#include <set>
#include <yaml-cpp/yaml.h>
@@ -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<mongo::unittest::TempDir> 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<libmongodbcapi_status, StatusDestructor>;
+
+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<libmongodbcapi_client, MongodCapiCleaner>;
+using MongoDBCAPIClientPtr = std::unique_ptr<libmongodbcapi_client, ClientDestructor>;
class MongodbCAPITest : public mongo::unittest::Test {
protected:
void setUp() {
+ status = libmongodbcapi_status_create();
+ ASSERT(status != nullptr);
+
if (!globalTempDir) {
- globalTempDir = mongo::stdx::make_unique<mongo::unittest::TempDir>("embedded_mongo");
+ globalTempDir = std::make_unique<mongo::unittest::TempDir>("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(&params, 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<int>(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<std::string, std::string> env;
options.addOptionChaining(
"tempPath", "tempPath", moe::String, "directory to place mongo::TempDir subdirectories");
- std::vector<std::string> argVector(argv, argv + argc);
- mongo::Status ret = parser.run(options, argVector, env, &environment);
+
+ std::map<std::string, std::string> env;
+ mongo::Status ret = moe::OptionsParser().run(
+ options, std::vector<std::string>(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(&params, 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(&params);
- if (init != LIBMONGODB_CAPI_SUCCESS) {
- std::cerr << "libmongodbcapi_init() failed with " << init << std::endl;
- return EXIT_FAILURE;
+ lib = libmongodbcapi_lib_init(&params, 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<std::string>(), "", 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<std::string>(), "", 1);
globalTempDir.reset();
}