Skip to content

Conversation

@eramongodb
Copy link
Contributor

@eramongodb eramongodb commented Dec 2, 2025

Resolves CXX-3237 and CXX-3238 for the v1::apm and v1::events::* components.

Due to the T const& accessor problem, the v_noabi::options::apm class only implements support for v_noabi <-> v1 conversion and ABI footprint reduction. However, because the v_noabi::events::* classes are (mostly) non-owning read-only views of underlying mongoc_apm_* objects owned by mongoc, each v_noabi::events::* class is implemented in terms of the equivalent v1::events::* class (except for v1::events::server_description).

Due to the v1::events::* classes being uniquely non-owning view-like API for mongoc-owned event objects, their tests are implemented together in test/v1/events.cpp to consolidate and reuse the mock patterns necessary to construct and test the event class API. Note that, whenever able, these mocked tests compare the identity (address) of values passed to and returned from mongoc functions rather than their individual values (not relevant to the behavior being tested), hence the plethora of explicit static and reinterpret casts.


Additional notes:

  • CXX-3236 add mongocxx v1 declarations #1482 overlooked renaming the v1::apm::server_heartbeat_() accessors to match the renamed event classes + some missing export macros in v1::events::server_description. These issues are fixed by this PR.
  • Due to being long-standing API, the v_noabi::events::* class' public void const* ctors are now explicitly documented as "internal use only" rather than removed. To avoid potential confusion due to appearing to be part of the class public API, an explicit MONGOCXX_ABI_NO_EXPORT macro is also applied (successor to the old MONGOCXX_PRIVATE macro).
  • mongoc_topology_description_*() has not required non-const argument since CDRIVER-4114 (see also CDRIVER-2809), so the topology_description-related implementation no longer needs to use const_cast. The void* ctor is also updated to void const* accordingly (not an API or ABI breaking change).
  • Because v_noabi::events::server_description may also exhibit the "transitive view" problem in contexts where ownership may escape the callback function (e.g. return value of topology_description::servers()), the conversion from v1::events::server_description requires (via an "Important" admonition in the docs) the v1 object outlives the v_noabi object. For consistency with the rest of v1 API, "maybe-owning" behavior is avoided (i.e. view_or_value, bson_t, etc.).
  • The v_noabi -> v1 conversion for the apm class is straightforward due to std::function<T>'s converting constructors (a v1::events::* argument can implicitly convert to a callback's v_noabi::events::* parameter). The opposite is not true due to v_noabi -> v1 for event classes being an explicit conversion (a v_noabi::events::* argument cannot implicitly convert to a callback's v1::events::* parameter). Therefore, an intermediate function that explicitly converts the argument from v_noabi -> v1 is required. This would be trivial with lambda expressions; however, the required init-capture form to permit move-capturing the callback function via [x = std::move(init)] is only available with C++14 and newer. Therefore, the invoke_as_v1<Fn> class template is used instead as a workaround until the minimum C++ standard can be bumped to C++14 or newer. If preferable, we can avoid this boilerplate by copy-capturing the callback instead using [fn]; I've opted for std::move_only_function<T>-like behavior for now.
// With C++14 and newer, `invoke_as_v1<Fn>` and related boilerplate is not necessary.
apm::apm(v1::apm other)
  : _command_started{
      [fn = std::move(v1::apm::internal::command_started(other))](v_noabi::events::command_started_event const& event) {
        fn(v1::events::command_started{event});
      }
    },
    ...
{}

// With C++11, but forces an intermediate copy of the callback in the `[fn]` capture.
apm::apm(v1::apm other)
  : _command_started{
      [&]{
        auto const& fn = v1::apm::internal::command_started(other);
        return [fn](v_noabi::events::command_started_event const& event) {
          fn(v1::events::command_started{event}); // Explicit conversion.
        };
      }()
    },
    ...
{}
  • Unfortunately, __cdecl cannot be specified for lambda expressions for MSVC. This conflicts with compiling test code with __vectorcall (see CXX-3092) when comparing the identity of registered APM callbacks in test/v1/apm.cpp. Thankfully, MSVC does support implicit conversion to function pointers with the correct calling convention; this is implemented via the fn_type type aliases. This should be unnecessary once CXX-3310 is resolved (dropping support for default calling conventions that are not __cdecl).

@eramongodb eramongodb self-assigned this Dec 2, 2025
@eramongodb eramongodb requested a review from a team as a code owner December 2, 2025 15:53
@eramongodb eramongodb requested a review from kevinAlbs December 2, 2025 15:53
Copy link
Collaborator

@kevinAlbs kevinAlbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with some minor fix-ups and a question to confirm my understanding.

Copy link
Collaborator

@connorsmacd connorsmacd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with one weak suggestion.

Comment on lines 61 to 69
std::size_t n = {};
auto const sds = libmongoc::topology_description_get_servers(to_mongoc(_impl), &n);

// Transfer ownership of each element, but not the parent array.
ret.reserve(n);
std::transform(sds, sds + n, std::back_inserter(ret), [](mongoc_server_description_t* sd) {
return v1::events::server_description::internal::make(sd);
});
bson_free(sds);
Copy link
Collaborator

@connorsmacd connorsmacd Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::size_t n = {};
auto const sds = libmongoc::topology_description_get_servers(to_mongoc(_impl), &n);
// Transfer ownership of each element, but not the parent array.
ret.reserve(n);
std::transform(sds, sds + n, std::back_inserter(ret), [](mongoc_server_description_t* sd) {
return v1::events::server_description::internal::make(sd);
});
bson_free(sds);
auto const sds_deleter = [](mongoc_server_description_t** sds) { bson_free(sds); };
using sds_unique_ptr_t = std::unique_ptr<mongoc_server_description_t**, decltype(sds_deleter)>;
std::size_t n = {};
auto const sds = sds_unique_ptr_t(libmongoc::topology_description_get_servers(to_mongoc(_impl), &n), sds_deleter);
// Transfer ownership of each element, but not the parent array.
ret.reserve(n);
std::transform(sds.get(), sds.get() + n, std::back_inserter(ret), [](mongoc_server_description_t* sd) {
return v1::events::server_description::internal::make(sd);
});

Suggest making this exception-safe since reserve can throw. However, that exception is highly unlikely and would probably mean the program is running OOM, so I don't feel strongly about this.

Copy link
Contributor Author

@eramongodb eramongodb Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support for OOM is inconsistently-but-generally-not-supported as far as I am aware. Nevertheless, .reserve() can also potentially throw std::length_error (although I expect this to be equally unlikely), so for the sake of exception correctness, opted to guard sds as suggested. However, given the scope of potentially-throwing code is limited to just the .reserve() call, used a try-catch block instead of std::unique_ptr (also note the different deleter required before vs. after the call to std::transform()). As far as I can tell, no other instance of a call to .reserve() currently contains an unguarded owning resource in its immediate scope.

@eramongodb eramongodb merged commit 1c31e7c into mongodb:master Dec 3, 2025
1 of 2 checks passed
@eramongodb eramongodb deleted the cxx-abi-v1-apm-events branch December 3, 2025 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants