-
Notifications
You must be signed in to change notification settings - Fork 547
CXX-3320 migrate instance and logger to mongocxx::v1 #1469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| instance& operator=(instance const&) = delete; | ||
|
|
||
| /// | ||
| /// Initialize the mongoc library with unstructured log messages disabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated documentation to clarify that this all applies to "unstructured log handling" to leave open the design space for "structured log handling" support by the C++ Driver.
Documentation also increased references to specific mongoc API to clarify behavior + defer much of the behavioral documentation to mongoc.
| /// | ||
| /// This class is not moveable. | ||
| /// | ||
| instance(instance&&) = delete; | ||
|
|
||
| /// | ||
| /// This class is not moveable. | ||
| /// | ||
| instance& operator=(instance&&) = delete; | ||
|
|
||
| /// | ||
| /// This class is not copyable. | ||
| /// | ||
| instance(instance const&) = delete; | ||
|
|
||
| /// | ||
| /// This class is not copyable. | ||
| /// | ||
| instance& operator=(instance const&) = delete; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relative breaking change: instance is made immovable as well as uncopyable. Allowing instance to be moved-from does not seem to be well-motivated.
| mongoc_log_level_t log_level, | ||
| char const* domain, | ||
| char const* message, | ||
| void* user_data) noexcept { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small relative change: added noexcept for consistency with other C API callback functions.
| "mongocxx", | ||
| MONGOCXX_VERSION_STRING, | ||
| "CXX=" MONGOCXX_COMPILER_ID " " MONGOCXX_COMPILER_VERSION " stdcxx=" STDCXX " / "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor optimization: avoid allocations, given all fields are literals, by concatenating everything into a single string literal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with minor comments. I like the new overload to simplify enabling default logging.
how important are log messages which may be emitted by
mongoc_init()?
I see two possible logs:
I expect neither is likely. (2) might be improved by making handshake non-global (CDRIVER-4142) (errors could be returned on the first client operation).
For backward compatibility,
v_noabi::instance's custom log handler constructor preserves the "register handler aftermongoc_init()" behavior.
If it helps simplify: I expect having v_noabi register the log handler before mongoc_init (to match v1) is unlikely to harm consumers (some added unlikely-to-occur logs)
src/mongocxx/test/subprocess.hh
Outdated
| // @returns The exit code of the subprocess (`*is_signal` is `false`) or the signal used to kill the subprocess | ||
| // (`*is_signal` is `true`) . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // @returns The exit code of the subprocess (`*is_signal` is `false`) or the signal used to kill the subprocess | |
| // (`*is_signal` is `true`) . | |
| // @returns The exit code of the subprocess (`*is_signal_ptr` is `false`) or the signal used to kill the subprocess | |
| // (`*is_signal_ptr` is `true`) . |
src/mongocxx/test/subprocess.cpp
Outdated
| CHECK_SUBPROCESS([] { | ||
| // Try to silence noisy Catch2 output. | ||
| (void)::close(1); // stdout | ||
| (void)::close(2); // stderr | ||
|
|
||
| SKIP("subprocess"); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| CHECK_SUBPROCESS([] { | |
| // Try to silence noisy Catch2 output. | |
| (void)::close(1); // stdout | |
| (void)::close(2); // stderr | |
| SKIP("subprocess"); | |
| }); | |
| CHECK_SUBPROCESS([] { | |
| // Try to silence noisy Catch2 output. | |
| (void)::close(1); // stdout | |
| (void)::close(2); // stderr | |
| SKIP("subprocess"); | |
| }, &is_signal); |
src/mongocxx/test/v1/logger.cpp
Outdated
|
|
||
| } // namespace | ||
|
|
||
| // mongocxx::v1::logger must not unnecessarily mpose special requirements on derived classes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // mongocxx::v1::logger must not unnecessarily mpose special requirements on derived classes. | |
| // mongocxx::v1::logger must not unnecessarily impose special requirements on derived classes. |
| /// | ||
| /// Cleanup the mongocxx (and mongoc) library. | ||
| /// | ||
| /// Calls [`mongoc_init()`](https://mongoc.org/libmongoc/current/mongoc_cleanup.html). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// Calls [`mongoc_init()`](https://mongoc.org/libmongoc/current/mongoc_cleanup.html). | |
| /// Calls [`mongoc_cleanup()`](https://mongoc.org/libmongoc/current/mongoc_cleanup.html). |
| static_assert(!std::is_move_constructible<instance>::value, "bsoncxx::v1::instance must be non-moveable"); | ||
| static_assert(!std::is_copy_constructible<instance>::value, "bsoncxx::v1::instance must be non-copyable"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| static_assert(!std::is_move_constructible<instance>::value, "bsoncxx::v1::instance must be non-moveable"); | |
| static_assert(!std::is_copy_constructible<instance>::value, "bsoncxx::v1::instance must be non-copyable"); | |
| static_assert(!std::is_move_constructible<instance>::value, "mongocxx::v1::instance must be non-moveable"); | |
| static_assert(!std::is_copy_constructible<instance>::value, "mongocxx::v1::instance must be non-copyable"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it helps simplify: I expect having v_noabi register the log handler before mongoc_init (to match v1) is unlikely to harm consumers (some added unlikely-to-occur logs)
Simplified the v_noabi implementation as suggested.
|
|
||
| // Inform the user that a custom log handler has been registered. | ||
| // Cannot use mocked `libmongoc::log()` due to varargs. | ||
| mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to keep this informational log message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am slightly in favor of keeping for v_noabi. The API examples expect it, and it might avoid a surprise to consumers. I expect it is not much burden for us to keep it in v_noabi. But I do not think it is needed for v1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with only a few minor comments.
| /// | ||
| enum class source_errc {}; | ||
| enum class source_errc { | ||
| zero, ///< Zero. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be zero, or something like okay? Alternatively: set mongocxx = 1, give the enum a base type like int, and then have source_errc{} as the stand-in for zero, which is the non-error syntax for std::errc.
(I don't feel strongly about any of the above options, just something to consider.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
zero, or something likeokay? Alternatively: set `mongocxx = 1 [...]
My reason for including the zero enumerator was twofold:
- Avoid the need for explicit
= 1in the otherwise=-less list of enumerators (an inconsistency). - Force explicit handling of the
zerocase in switch cases via-Wswitch/-Wswitch-enum.
I opted for zero instead of okay to avoid implying anything beyond a description of the value itself, since "not zero" does not necessarily imply "not okay" or "not valid" given certain error code schemes (i.e. "HTTP status code 200").
give the enum a base type like
int
The default underlying type for a scoped enumeration is int, so there is no need for explicit : int given enum class E.
| return instance.value(); | ||
| } | ||
|
|
||
| exception::~exception() = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a benefit to providing the defaulted destructor out-of-line? This inhibits inlining of the exception destructor without LTO enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the key function for v1::exception in lieu of any other virtual functions:
[...] if the class has a key function (see below), the tables are emitted in the object for the translation unit containing the definition of the key function. This is unique if the key function is not inline. Otherwise, the tables are emitted in every object that references any of them.
and:
The key function is the first non-pure virtual function that is not inline at the point of class definition. [...] Note that if the key function is not inline in the class definition, but its later definition is inline, it will be emitted in every object containing the definition.
The absence of a key function triggers the -Wweak-vtables Clang warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, it may be preferable to use a private virtual .key_function() for the sole purpose of acting as the key function instead of the destructor, which has the additional benefit of avoiding Ro5 boilerplate:
class exception : public std::system_error {
public:
using std::system_error::system_error;
private:
MONGOCXX_ABI_NO_EXPORT virtual void key_function() const;
};Will propose this pattern for review in a followup PR.
| namespace std { | ||
|
|
||
| template <> | ||
| struct is_error_condition_enum<mongocxx::v1::source_errc> : true_type {}; | ||
|
|
||
| template <> | ||
| struct is_error_condition_enum<mongocxx::v1::type_errc> : true_type {}; | ||
|
|
||
| } // namespace std |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor tweak option: You can omit the outer namespace std and do:
template <>
struct std::is_error_condition_enum<...>with the std:: qualifier on the struct name. This isn't strictly better, but may reduce the amount of visual noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially attempted this pattern as well, but encountered compilation errors with GCC versions older than 7.1 (possibly related: CWG 275). However, we now require GCC 8 or newer as of #1415, so I think the suggestion is now viable:
mongo-cxx-driver/CMakeLists.txt
Lines 28 to 33 in cbb70da
| # https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html | |
| # https://gcc.gnu.org/projects/cxx-status.html | |
| # https://gcc.gnu.org/releases.html | |
| if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8.1") | |
| message(FATAL_ERROR "GCC 8.1 or newer is required") | |
| endif() |
| virtual void operator()( | ||
| log_level level, | ||
| bsoncxx::v1::stdx::string_view domain, | ||
| bsoncxx::v1::stdx::string_view message) noexcept = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be beyond the scope of this PR, but we may want to defer the creation of message based on whether level is enabled in the logger, otherwise the user pays for the string-formatting of every message for every level, even if they e.g. don't have trace enabled. (mongoc already does this, but only for the trace level.)
Also recommend doing the "private virtual" idiom so that the base class can have logic (i.e. conditional formatting) in operator(), and send the message on to a private virtual like do_write_log(level, domain, msg).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we may want to defer the creation of
messagebased on whetherlevelis enabled in the logger
I believe it would be more appropriate to implement this in mongoc (as being done for the trace level) rather than in mongocxx. The message parameter is merely a view of whatever mongoc_log() has already parsed and formatted.
recommend doing the "private virtual" idiom
This would will force incompatibility with the current v_noabi API, making the using v1::logger; alias within mongocxx::v_noabi unviable. This may be better suited as a followup extension to v1 API (i.e. part of CXX-2586).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it would be more appropriate to implement this in mongoc (as being done for the trace level) rather than in mongocxx
May need to do both, as (IIUC) there is no mongocxx API that sends down a va_list to mongoc, and there is no mongoc API that accepts a va_list for logging.
Alternatively: I'm misremembering things and this is a non-issue 🙃
Related to CXX-3320. Due to the unique nature of
mongocxx::v_noabi::instanceas a global singleton-like object, this class is migrated in full fromv_noabitov1as a single, standalone PR. Related typesv_noabi::loggerandv_noabi::exceptionare also minimally migrated as part of this PR to support theinstanceclass API.First, concerning
v_noabi::logger: due to having no observable difference in behavior inv1,v_noabi::loggeris merely a redeclaration ofv1::loggerin thev_noabinamespace! 🎉 This also avoids complications with trying to support compatibility between both av_noabi::loggerandv1::loggerclass given their polymorphic nature.The
v_noabi::instanceclass is not so fortunate. Its migration is complicated primary by the realization that there are two unresolvable deficiencies with thev_noabi::instanceAPI:mongoc_init()has already been invoked.As mentioned by mongoc docs:
That is, the initialization behavior is currently defined as follows (pseudocode):
The
v1::instanceobject proposes fixing these issues as follows:v1::default_handlertag type by which the user can request using the mongoc default log handler.mongoc_init()so that all log messages are handled properly.That is, the initialization behavior is changed to the following:
For backward compatibility,Update: per feedback, simplified the v_noabi implementation to also register the custom unstructured log handler before mongoc initialization.v_noabi::instance's custom log handler constructor preserves the "register handler aftermongoc_init()" behavior. If we do not consider this behavior to be worth preserving (how important are log messages which may be emitted bymongoc_init()?), we can simplify the implementation ofv_noabi::instance's initialization. (Related: CXX-1029)The
v_noabi::instancestill supports the (deprecated)::instance()helper function for backward compatibility. The implementation of::instance()remains specific tov_noabi: the implementationv1::instanceis entirely unaware of::instance()support. In place of thecurrent_instance"sentinel" value,v1::instanceinstead utilizes a simplestd::atomic_intcounter to track and report exceptions given multiple instance objects.Much of the rest of this PR involves refactors to the test suite for the
instanceandloggerclasses. Thetest_loggingexecutable is removed in favor of grouping allinstanceobject testing totest_instance. All other test executables can (in theory) be grouped into a single test executable without concern for "multiple instance object" exceptions.The fork-based pattern to test
instanceobjects used by the examples API runner (#1216) is repurposed here for compatibility with the Catch2 test suite asmongocxx::test::subprocess(op). This helper function accepts a singleop: void()invocable that is executed within a forked subprocess. The result of the subprocess, including termination, is translated into well-behaved Catch test assertions. The Catch test suite reporter is reused by the subprocess to support capturing values and evaluating assertions. However, usingSECTION()is not supported due to non-trivial control flow, either withinopor in the parentTEST_CASE().This enabled writing test cases asserting proper log message handling behavior, including default log message handler behavior, by capturing log output emitted by the subprocess (using POSIX pipe API in the
capture_stderrclass). Note this is only supported on non-Windows platforms. Unfortunately, the use ofstd::exit()andstd::terminate()in the subprocess confusesvalgrind, which (understandably) reports memory leaks due to the abnormal process termination method. Therefore, leak detection in subprocesses is disabled for thetest_instanceexecutable using--trace-children=no.