Skip to content

Commit db943d5

Browse files
committed
CXX-890 Don't use objects requiring dynamic init/fini for instance::current
1 parent b3061e3 commit db943d5

File tree

6 files changed

+144
-20
lines changed

6 files changed

+144
-20
lines changed

examples/mongocxx/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ set(MONGOCXX_EXAMPLES
3030
exception.cpp
3131
index.cpp
3232
inserted_id.cpp
33+
instance_management.cpp
3334
query.cpp
3435
query_projection.cpp
3536
remove.cpp
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2016 MongoDB Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <cstdlib>
16+
#include <memory>
17+
18+
#include <bsoncxx/stdx/make_unique.hpp>
19+
#include <bsoncxx/stdx/optional.hpp>
20+
#include <bsoncxx/stdx/string_view.hpp>
21+
22+
#include <mongocxx/instance.hpp>
23+
#include <mongocxx/logger.hpp>
24+
#include <mongocxx/pool.hpp>
25+
#include <mongocxx/stdx.hpp>
26+
#include <mongocxx/uri.hpp>
27+
28+
namespace {
29+
30+
// This example demonstrates how one might keep a heap allocated mongocxx::instance and
31+
// mongocxx::pool object associated in a way that allows dynamic configuration. Most of the examples
32+
// simply create a stack allocated mongocxx::instance object, which is fine for small examples, but
33+
// real programs will typically require shared access to a commonly configured instance and
34+
// connection pool across different translation units and components. By providing a singleton which
35+
// owns the instance and pool objects, we can defer configuration until we are ready to use MongoDB,
36+
// and share the instance and pool objects between multiple functions.
37+
38+
class mongo_access {
39+
public:
40+
41+
static mongo_access& instance() {
42+
static mongo_access instance;
43+
return instance;
44+
}
45+
46+
void configure(std::unique_ptr<mongocxx::instance> instance,
47+
std::unique_ptr<mongocxx::pool> pool) {
48+
_instance = std::move(instance);
49+
_pool = std::move(pool);
50+
}
51+
52+
using connection = mongocxx::pool::entry;
53+
54+
connection get_connection() {
55+
return _pool->acquire();
56+
}
57+
58+
mongocxx::stdx::optional<connection> try_get_connection() {
59+
return _pool->try_acquire();
60+
}
61+
62+
private:
63+
mongo_access() = default;
64+
65+
std::unique_ptr<mongocxx::instance> _instance = nullptr;
66+
std::unique_ptr<mongocxx::pool> _pool = nullptr;
67+
};
68+
69+
} // namespace
70+
71+
// The 'configure' and 'do_work' functions use the same mongocxx::instance and mongocxx::pool
72+
// objects by way of the mongo_access singleton.
73+
74+
void configure(mongocxx::uri uri) {
75+
class noop_logger : public mongocxx::logger {
76+
public:
77+
virtual void operator()(mongocxx::log_level, mongocxx::stdx::string_view,
78+
mongocxx::stdx::string_view message) noexcept {}
79+
};
80+
81+
mongo_access::instance().configure(
82+
mongocxx::stdx::make_unique<mongocxx::instance>(mongocxx::stdx::make_unique<noop_logger>()),
83+
mongocxx::stdx::make_unique<mongocxx::pool>(std::move(uri))
84+
);
85+
86+
}
87+
88+
bool do_work() {
89+
auto connection = mongo_access::instance().get_connection();
90+
if (!connection)
91+
return false;
92+
return true;
93+
}
94+
95+
int main(int argc, char* argv[]) {
96+
auto uri = mongocxx::uri{(argc >= 2) ? argv[1] : mongocxx::uri::k_default_uri};
97+
configure(std::move(uri));
98+
return do_work() ? EXIT_SUCCESS : EXIT_FAILURE;
99+
}

src/mongocxx/instance.cpp

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
#include <mongocxx/instance.hpp>
1616

17+
#include <atomic>
1718
#include <mutex>
19+
#include <type_traits>
1820
#include <utility>
1921

2022
#include <bsoncxx/stdx/make_unique.hpp>
@@ -60,9 +62,14 @@ void user_log_handler(::mongoc_log_level_t mongoc_log_level, const char *log_dom
6062
stdx::string_view{log_domain}, stdx::string_view{message});
6163
}
6264

63-
std::recursive_mutex instance_mutex;
64-
instance *current_instance = nullptr;
65-
std::unique_ptr<instance> global_instance;
65+
// A region of memory that acts as a sentintel value indicating that an instance object is being
66+
// destroyed. We only care about the address of this object, never its contents.
67+
typename std::aligned_storage<sizeof(instance), alignof(instance)>::type sentinel;
68+
69+
std::atomic<instance*> current_instance{nullptr};
70+
static_assert(std::is_standard_layout<decltype(current_instance)>::value, "Must be standard layout");
71+
static_assert(std::is_trivially_constructible<decltype(current_instance)>::value, "Must be trivially constructible");
72+
static_assert(std::is_trivially_destructible<decltype(current_instance)>::value, "Must be trivially destructible");
6673

6774
} // namespace
6875

@@ -94,31 +101,32 @@ instance::instance() : instance(nullptr) {
94101
}
95102

96103
instance::instance(std::unique_ptr<logger> logger) {
97-
std::lock_guard<std::recursive_mutex> lock(instance_mutex);
98-
if (current_instance) {
99-
throw logic_error{error_code::k_instance_already_exists};
104+
105+
while (true) {
106+
instance* expected = nullptr;
107+
if (current_instance.compare_exchange_strong(expected, this))
108+
break;
109+
if (expected != reinterpret_cast<instance*>(&sentinel))
110+
throw logic_error{error_code::k_instance_already_exists};
100111
}
112+
101113
_impl = stdx::make_unique<impl>(std::move(logger));
102-
current_instance = this;
103114
}
104115

105116
instance::instance(instance &&) noexcept = default;
106117
instance &instance::operator=(instance &&) noexcept = default;
107118

108119
instance::~instance() {
109-
std::lock_guard<std::recursive_mutex> lock(instance_mutex);
110-
if (current_instance != this) std::abort();
120+
current_instance.store(reinterpret_cast<instance*>(&sentinel));
111121
_impl.reset();
112-
current_instance = nullptr;
122+
current_instance.store(nullptr);
113123
}
114124

115125
instance &instance::current() {
116-
std::lock_guard<std::recursive_mutex> lock(instance_mutex);
117-
if (!current_instance) {
118-
global_instance.reset(new instance);
119-
current_instance = global_instance.get();
126+
if (!current_instance.load()) {
127+
static instance the_instance;
120128
}
121-
return *current_instance;
129+
return *current_instance.load();
122130
}
123131

124132
MONGOCXX_INLINE_NAMESPACE_END

src/mongocxx/instance.hpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,19 @@ class MONGOCXX_API instance {
5757
~instance();
5858

5959
///
60-
/// Returns the current unique instance of the driver. If an
61-
/// instance was explicitly created, that will be returned. If no
62-
/// instance has yet been created, a default instance will be
63-
/// constructed and returned.
60+
/// Returns the current unique instance of the driver. If an instance was explicitly created,
61+
/// that will be returned. If no instance has yet been created, a default instance will be
62+
/// constructed and returned. If a default instance is constructed, its destruction will be
63+
/// sequenced according to the rules for the destruction of static local variables at program
64+
/// exit (see http://en.cppreference.com/w/cpp/utility/program/exit).
65+
///
66+
/// Note that, if you need to configure the instance in any way (e.g. with a logger), you cannot
67+
/// use this method to cause the instance to be constructed. You must explicitly create an
68+
/// properly configured instance object. You can, however, use this method to obtain that
69+
/// configured instance object.
70+
///
71+
/// @note This method is intended primarily for test authors, where managing the lifetime of the
72+
/// instance w.r.t. the test framework can be problematic.
6473
///
6574
static instance& current();
6675

src/mongocxx/pool.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class MONGOCXX_API pool {
6969
/// @note The lifetime of any entry object must be a subset of the pool object
7070
/// from which it was acquired.
7171
///
72-
using entry = std::unique_ptr<client, std::function<void(client*)>>;
72+
using entry = std::unique_ptr<client, std::function<void MONGOCXX_CALL (client*)>>;
7373

7474
///
7575
/// Acquires a client from the pool. The calling thread will block until a connection is

src/mongocxx/test/instance.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,18 @@ TEST_CASE("a user-provided log handler will be used for logging output", "[insta
5757
std::vector<test_log_handler::event> events;
5858
mongocxx::instance driver{stdx::make_unique<test_log_handler>(&events)};
5959

60+
REQUIRE(&mongocxx::instance::current() == &driver);
61+
6062
// The mocking system doesn't play well with varargs functions, so we use a bare
6163
// mongoc_log call here.
6264
mongoc_log(::MONGOC_LOG_LEVEL_ERROR, "foo", "bar");
6365

6466
REQUIRE(events.size() == 1);
6567
REQUIRE(events[0] == std::make_tuple(log_level::k_error, "foo", "bar"));
6668
}
69+
70+
TEST_CASE("after destroying a user constructed instance the instance::current method works") {
71+
mongocxx::instance::current();
72+
}
73+
6774
} // namespace

0 commit comments

Comments
 (0)