1313#include < boost/mysql/with_diagnostics.hpp>
1414
1515#include < boost/asio/awaitable.hpp>
16+ #include < boost/asio/bind_executor.hpp>
1617#include < boost/asio/cancel_after.hpp>
18+ #include < boost/asio/co_spawn.hpp>
1719#include < boost/asio/detached.hpp>
1820#include < boost/asio/io_context.hpp>
21+ #include < boost/asio/strand.hpp>
1922#include < boost/asio/thread_pool.hpp>
2023#include < boost/asio/use_future.hpp>
24+ #include < boost/config.hpp>
25+ #include < boost/system/error_code.hpp>
2126#include < boost/test/unit_test.hpp>
2227
2328#include < chrono>
2631#include " test_integration/run_coro.hpp"
2732#include " test_integration/snippets/credentials.hpp"
2833
29- using namespace boost ::mysql;
30- using namespace boost ::mysql::test;
34+ namespace asio = boost::asio;
35+ namespace mysql = boost::mysql;
36+ using namespace mysql ::test;
3137
3238namespace {
3339
@@ -36,48 +42,112 @@ namespace {
3642// Use connection pools for functions that will be called
3743// repeatedly during the application lifetime.
3844// An HTTP server handler function is a good candidate.
39- boost:: asio::awaitable<std::int64_t > get_num_employees (boost:: mysql::connection_pool& pool)
45+ asio::awaitable<std::int64_t > get_num_employees (mysql::connection_pool& pool)
4046{
4147 // Get a fresh connection from the pool.
4248 // pooled_connection is a proxy to an any_connection object.
43- boost:: mysql::pooled_connection conn = co_await pool.async_get_connection ();
49+ mysql::pooled_connection conn = co_await pool.async_get_connection ();
4450
4551 // Use pooled_connection::operator-> to access the underlying any_connection.
4652 // Let's use the connection
47- results result;
53+ mysql:: results result;
4854 co_await conn->async_execute (" SELECT COUNT(*) FROM employee" , result);
4955 co_return result.rows ().at (0 ).at (0 ).as_int64 ();
5056
5157 // When conn is destroyed, the connection is returned to the pool
5258}
5359// ]
5460
55- boost:: asio::awaitable<void > return_without_reset (boost:: mysql::connection_pool& pool)
61+ asio::awaitable<void > return_without_reset (mysql::connection_pool& pool)
5662{
5763 // [connection_pool_return_without_reset
5864 // Get a connection from the pool
59- boost:: mysql::pooled_connection conn = co_await pool.async_get_connection ();
65+ mysql::pooled_connection conn = co_await pool.async_get_connection ();
6066
6167 // Use the connection in a way that doesn't mutate session state.
6268 // We're not setting variables, preparing statements or starting transactions,
6369 // so it's safe to skip reset
64- boost:: mysql::results result;
70+ mysql::results result;
6571 co_await conn->async_execute (" SELECT COUNT(*) FROM employee" , result);
6672
6773 // Explicitly return the connection to the pool, skipping reset
6874 conn.return_without_reset ();
6975 // ]
7076}
7177
72- boost:: asio::awaitable<void > apply_timeout (boost:: mysql::connection_pool& pool)
78+ asio::awaitable<void > apply_timeout (mysql::connection_pool& pool)
7379{
7480 // [connection_pool_apply_timeout
7581 // Get a connection from the pool, but don't wait more than 5 seconds
76- auto conn = co_await pool.async_get_connection (boost:: asio::cancel_after (std::chrono::seconds (5 )));
82+ auto conn = co_await pool.async_get_connection (asio::cancel_after (std::chrono::seconds (5 )));
7783 // ]
7884
7985 conn.return_without_reset ();
8086}
87+
88+ // [connection_pool_thread_safe_use
89+ // A function that handles a user session in a server
90+ asio::awaitable<void > handle_session (mysql::connection_pool& pool)
91+ {
92+ // CAUTION: asio::cancel_after creates a timer that is *not* part of the pool's state.
93+ // The timer is not protected by the pool's strand.
94+ // This coroutine must be run within a strand for this to be safe
95+ using namespace std ::chrono_literals;
96+ co_await pool.async_get_connection (asio::cancel_after (30s));
97+
98+ // Use the connection
99+ }
100+ // ]
101+ #endif
102+
103+ #ifndef BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
104+ // [connection_pool_thread_safe_callbacks
105+ // Holds per-session state
106+ class session_handler : public std ::enable_shared_from_this<session_handler>
107+ {
108+ // The connection pool
109+ mysql::connection_pool& pool_;
110+
111+ // A strand object, unique to this session
112+ asio::strand<asio::any_io_executor> strand_;
113+
114+ public:
115+ // pool.get_executor() points to the execution context that was used
116+ // to create the pool, and never to the pool's internal strand
117+ session_handler (mysql::connection_pool& pool)
118+ : pool_(pool), strand_(asio::make_strand(pool.get_executor()))
119+ {
120+ }
121+
122+ void start ()
123+ {
124+ // Enters the strand. The passed function will be executed through the strand.
125+ // If the initiation is run outside the strand, a race condition will occur.
126+ asio::dispatch (asio::bind_executor (strand_, [self = shared_from_this ()] { self->get_connection (); }));
127+ }
128+
129+ void get_connection ()
130+ {
131+ // This function will run within the strand. Binding the passed callback to
132+ // the strand will make async_get_connection run it within the strand, too.
133+ pool_.async_get_connection (asio::cancel_after (
134+ std::chrono::seconds (30 ),
135+ asio::bind_executor (
136+ strand_,
137+ [self = shared_from_this ()](boost::system::error_code, mysql::pooled_connection) {
138+ // Use the connection as required
139+ }
140+ )
141+ ));
142+ }
143+ };
144+
145+ void handle_session_v2 (mysql::connection_pool& pool)
146+ {
147+ // Start the callback chain
148+ std::make_shared<session_handler>(pool)->start ();
149+ }
150+ // ]
81151#endif
82152
83153BOOST_AUTO_TEST_CASE (section_connection_pool)
@@ -90,39 +160,39 @@ BOOST_AUTO_TEST_CASE(section_connection_pool)
90160 // You must specify enough information to establish a connection,
91161 // including the server address and credentials.
92162 // You can configure a lot of other things, like pool limits
93- boost:: mysql::pool_params params;
163+ mysql::pool_params params;
94164 params.server_address .emplace_host_and_port (server_hostname);
95165 params.username = mysql_username;
96166 params.password = mysql_password;
97167 params.database = " boost_mysql_examples" ;
98168
99169 // The I/O context, required by all I/O operations
100- boost:: asio::io_context ctx;
170+ asio::io_context ctx;
101171
102172 // Construct a pool of connections. The context will be used internally
103173 // to create the connections and other I/O objects
104- boost:: mysql::connection_pool pool (ctx, std::move (params));
174+ mysql::connection_pool pool (ctx, std::move (params));
105175
106176 // You need to call async_run on the pool before doing anything useful with it.
107177 // async_run creates connections and keeps them healthy. It must be called
108178 // only once per pool.
109179 // The detached completion token means that we don't want to be notified when
110180 // the operation ends. It's similar to a no-op callback.
111- pool.async_run (boost:: asio::detached);
181+ pool.async_run (asio::detached);
112182 // ]
113183
114184#ifdef BOOST_ASIO_HAS_CO_AWAIT
115- run_coro (ctx, [&pool]() -> boost:: asio::awaitable<void > {
185+ run_coro (ctx, [&pool]() -> asio::awaitable<void > {
116186 co_await get_num_employees (pool);
117187 pool.cancel ();
118188 });
119189#endif
120190 }
121191 {
122- boost:: asio::io_context ctx;
192+ asio::io_context ctx;
123193
124194 // [connection_pool_configure_size
125- boost:: mysql::pool_params params;
195+ mysql::pool_params params;
126196
127197 // Set the usual params
128198 params.server_address .emplace_host_and_port (server_hostname);
@@ -134,38 +204,58 @@ BOOST_AUTO_TEST_CASE(section_connection_pool)
134204 params.initial_size = 10 ;
135205 params.max_size = 1000 ;
136206
137- boost:: mysql::connection_pool pool (ctx, std::move (params));
207+ mysql::connection_pool pool (ctx, std::move (params));
138208 // ]
139209
140210#ifdef BOOST_ASIO_HAS_CO_AWAIT
141- pool.async_run (boost:: asio::detached);
142- run_coro (ctx, [&pool]() -> boost:: asio::awaitable<void > {
211+ pool.async_run (asio::detached);
212+ run_coro (ctx, [&pool]() -> asio::awaitable<void > {
143213 co_await return_without_reset (pool);
144214 co_await apply_timeout (pool);
145215 pool.cancel ();
146216 });
147217#endif
148218 }
149219 {
150- // [connection_pool_thread_safe
151- // The I/O context, required by all I/O operations
152- boost::asio::io_context ctx;
220+ // [connection_pool_thread_safe_create
221+ // The I/O context, required by all I/O operations.
222+ // This is like an io_context, but with 5 threads running it.
223+ asio::thread_pool ctx (5 );
153224
154225 // The usual pool configuration params
155- boost:: mysql::pool_params params;
226+ mysql::pool_params params;
156227 params.server_address .emplace_host_and_port (server_hostname);
157228 params.username = mysql_username;
158229 params.password = mysql_password;
159230 params.database = " boost_mysql_examples" ;
160231 params.thread_safe = true ; // enable thread safety
161232
162233 // Construct a thread-safe pool
163- boost::mysql::connection_pool pool (ctx, std::move (params));
234+ mysql::connection_pool pool (ctx, std::move (params));
235+ pool.async_run (asio::detached);
164236
165237 // We can now pass a reference to pool to other threads,
166238 // and call async_get_connection concurrently without problem.
167239 // Individual connections are still not thread-safe.
168240 // ]
241+
242+ #ifdef BOOST_ASIO_HAS_CO_AWAIT
243+ // [connection_pool_thread_safe_spawn
244+ // OK: the entire coroutine runs within a strand.
245+ // In a typical server setup, each request usually gets its own strand,
246+ // so it can run in parallel with other requests.
247+ asio::co_spawn (
248+ asio::make_strand (ctx), // If we removed this make_strand, we would have a race condition
249+ [&pool] { return handle_session (pool); },
250+ asio::detached
251+ );
252+ // ]
253+ #endif
254+ #ifndef BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES
255+ handle_session_v2 (pool);
256+ #endif
257+ pool.cancel ();
258+ ctx.join ();
169259 }
170260}
171261
0 commit comments