gnss-sim/3rdparty/boost/mysql/connection_pool.hpp

733 lines
28 KiB
C++

//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_CONNECTION_POOL_HPP
#define BOOST_MYSQL_CONNECTION_POOL_HPP
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/pool_params.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/connection_pool_fwd.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <chrono>
#include <memory>
#include <utility>
namespace boost {
namespace mysql {
/**
* \brief (EXPERIMENTAL) A proxy to a connection owned by a pool that returns it to the pool when destroyed.
* \details
* A `pooled_connection` behaves like to a `std::unique_ptr`: it has exclusive ownership of an
* \ref any_connection created by the pool. When destroyed, it returns the connection to the pool.
* A `pooled_connection` may own nothing. We say such a connection is invalid (`this->valid() == false`).
* \n
* This class is movable but not copyable.
*
* \par Object lifetimes
* While `*this` is alive, the \ref connection_pool internal data will be kept alive
* automatically. It's safe to destroy the `connection_pool` object before `*this`.
*
* \par Thread safety
* By default, individual connections created by the pool are **not** thread-safe,
* even if the pool was created using \ref pool_executor_params::thread_safe.
* \n
* Distinct objects: safe. \n
* Shared objects: unsafe. \n
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class pooled_connection
{
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::access;
friend class detail::basic_pool_impl<detail::io_traits, pooled_connection>;
#endif
detail::connection_node* impl_{nullptr};
std::shared_ptr<detail::pool_impl> pool_impl_;
pooled_connection(detail::connection_node& node, std::shared_ptr<detail::pool_impl> pool_impl) noexcept
: impl_(&node), pool_impl_(std::move(pool_impl))
{
}
public:
/**
* \brief Constructs an invalid pooled connection.
* \details
* The resulting object is invalid (`this->valid() == false`).
*
* \par Exception safety
* No-throw guarantee.
*/
pooled_connection() noexcept = default;
/**
* \brief Move constructor.
* \details
* Transfers connection ownership from `other` to `*this`.
* \n
* After this function returns, if `other.valid() == true`, `this->valid() == true`.
* In any case, `other` will become invalid (`other.valid() == false`).
*
* \par Exception safety
* No-throw guarantee.
*/
pooled_connection(pooled_connection&& other) noexcept
: impl_(other.impl_), pool_impl_(std::move(other.pool_impl_))
{
other.impl_ = nullptr;
}
/**
* \brief Move assignment.
* \details
* If `this->valid()`, returns the connection owned by `*this` to the pool and marks
* it as pending reset (as if the destructor was called).
* It then transfers connection ownership from `other` to `*this`.
* \n
* After this function returns, if `other.valid() == true`, `this->valid() == true`.
* In any case, `other` will become invalid (`other.valid() == false`).
*
* \par Exception safety
* No-throw guarantee.
*/
pooled_connection& operator=(pooled_connection&& other) noexcept
{
if (impl_)
detail::return_connection(std::move(pool_impl_), *impl_, true);
impl_ = other.impl_;
other.impl_ = nullptr;
pool_impl_ = std::move(other.pool_impl_);
return *this;
}
#ifndef BOOST_MYSQL_DOXYGEN
pooled_connection(const pooled_connection&) = delete;
pooled_connection& operator=(const pooled_connection&) = delete;
#endif
/**
* \brief Destructor.
* \details
* If `this->valid() == true`, returns the owned connection to the pool
* and marks it as pending reset. If your connection doesn't need to be reset
* (e.g. because you didn't mutate session state), use \ref return_without_reset.
*
* \par Thead-safety
* If the \ref connection_pool object that `*this` references has been constructed
* with adequate executor configuration, this function is safe to be called concurrently
* with \ref connection_pool::async_run, \ref connection_pool::async_get_connection,
* \ref connection_pool::cancel and \ref return_without_reset on other `pooled_connection` objects.
*/
~pooled_connection()
{
if (impl_)
detail::return_connection(std::move(pool_impl_), *impl_, true);
}
/**
* \brief Returns whether the object owns a connection or not.
* \par Exception safety
* No-throw guarantee.
*/
bool valid() const noexcept { return impl_ != nullptr; }
/**
* \brief Retrieves the connection owned by this object.
* \par Preconditions
* The object should own a connection (`this->valid() == true`).
*
* \par Object lifetimes
* The returned reference is valid as long as `*this` or an object
* move-constructed or move-assigned from `*this` is alive.
*
* \par Exception safety
* No-throw guarantee.
*/
any_connection& get() noexcept { return detail::get_connection(*impl_); }
/// \copydoc get
const any_connection& get() const noexcept { return detail::get_connection(*impl_); }
/// \copydoc get
any_connection* operator->() noexcept { return &get(); }
/// \copydoc get
const any_connection* operator->() const noexcept { return &get(); }
/**
* \brief Returns the owned connection to the pool and marks it as not requiring reset.
* \details
* Returns a connection to the pool and marks it as idle. This will
* skip the \ref any_connection::async_reset_connection call to wipe session state.
* \n
* This can provide a performance gain, but must be used with care. Failing to wipe
* session state can lead to resource leaks (prepared statements not being released),
* incorrect results and vulnerabilities (different logical operations interacting due
* to leftover state).
* \n
* Please read the documentation on \ref any_connection::async_reset_connection before
* calling this function. If in doubt, don't use it, and leave the destructor return
* the connection to the pool for you.
* \n
* When this function returns, `*this` will own nothing (`this->valid() == false`).
*
* \par Preconditions
* `this->valid() == true`
*
* \par Exception safety
* No-throw guarantee.
*
* \par Thead-safety
* If the \ref connection_pool object that `*this` references has been constructed
* with adequate executor configuration, this function is safe to be called concurrently
* with \ref connection_pool::async_run, \ref connection_pool::async_get_connection,
* \ref connection_pool::cancel and `~pooled_connection`.
*/
void return_without_reset() noexcept
{
BOOST_ASSERT(valid());
detail::return_connection(std::move(pool_impl_), *impl_, false);
impl_ = nullptr;
}
};
/**
* \brief (EXPERIMENTAL) A pool of connections of variable size.
* \details
* A connection pool creates and manages \ref any_connection objects.
* Using a pool allows to reuse sessions, avoiding part of the overhead associated
* to session establishment. It also features built-in error handling and reconnection.
* See the discussion and examples for more details on when to use this class.
* \n
* Connections are retrieved by \ref async_get_connection, which yields a
* \ref pooled_connection object. They are returned to the pool when the
* `pooled_connection` is destroyed, or by calling \ref pooled_connection::return_without_reset.
* \n
* A pool needs to be run before it can return any connection. Use \ref async_run for this.
* Pools can only be run once.
* \n
* Connections are created, connected and managed internally by the pool, following
* a well-defined state model. Please refer to the discussion for details.
* \n
* Due to oddities in Boost.Asio's universal async model, this class only
* exposes async functions. You can use `asio::use_future` to transform them
* into sync functions (please read the discussion for details).
* \n
* This is a move-only type.
*
* \par Thread-safety
* By default, connection pools are *not* thread-safe, but most functions can
* be made thread-safe by passing an adequate \ref pool_executor_params objects
* to the constructor. See \ref pool_executor_params::thread_safe and the discussion
* for details.
* \n
* Distinct objects: safe. \n
* Shared objects: unsafe, unless passing adequate values to the constructor.
*
* \par Object lifetimes
* Connection pool objects create an internal state object that is referenced
* by other objects and operations (like \ref pooled_connection). This object
* will be kept alive using shared ownership semantics even after the `connection_pool`
* object is destroyed. This results in intuitive lifetime rules.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class connection_pool
{
std::shared_ptr<detail::pool_impl> impl_;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::access;
#endif
static constexpr std::chrono::steady_clock::duration get_default_timeout() noexcept
{
return std::chrono::seconds(30);
}
struct initiate_run
{
template <class Handler>
void operator()(Handler&& h, std::shared_ptr<detail::pool_impl> self)
{
async_run_erased(std::move(self), std::forward<Handler>(h));
}
};
BOOST_MYSQL_DECL
static void async_run_erased(
std::shared_ptr<detail::pool_impl> pool,
asio::any_completion_handler<void(error_code)> handler
);
struct initiate_get_connection
{
template <class Handler>
void operator()(
Handler&& h,
std::shared_ptr<detail::pool_impl> self,
std::chrono::steady_clock::duration timeout,
diagnostics* diag
)
{
async_get_connection_erased(std::move(self), timeout, diag, std::forward<Handler>(h));
}
};
BOOST_MYSQL_DECL
static void async_get_connection_erased(
std::shared_ptr<detail::pool_impl> pool,
std::chrono::steady_clock::duration timeout,
diagnostics* diag,
asio::any_completion_handler<void(error_code, pooled_connection)> handler
);
template <class CompletionToken>
auto async_get_connection_impl(
std::chrono::steady_clock::duration timeout,
diagnostics* diag,
CompletionToken&& token
)
-> decltype(asio::async_initiate<CompletionToken, void(error_code, pooled_connection)>(
initiate_get_connection{},
token,
impl_,
timeout,
diag
))
{
BOOST_ASSERT(valid());
return asio::async_initiate<CompletionToken, void(error_code, pooled_connection)>(
initiate_get_connection{},
token,
impl_,
timeout,
diag
);
}
BOOST_MYSQL_DECL
connection_pool(pool_executor_params&& ex_params, pool_params&& params, int);
public:
/**
* \brief Constructs a connection pool.
* \details
* Internal I/O objects (like timers) are constructed using
* `ex_params.pool_executor`. Connections are constructed using
* `ex_params.connection_executor`. This can be used to create
* thread-safe pools.
* \n
* The pool is created in a "not-running" state. Call \ref async_run to transition to the
* "running" state. Calling \ref async_get_connection in the "not-running" state will fail
* with \ref client_errc::cancelled.
* \n
* The constructed pool is always valid (`this->valid() == true`).
*
* \par Exception safety
* Strong guarantee. Exceptions may be thrown by memory allocations.
* \throws std::invalid_argument If `params` contains values that violate the rules described in \ref
* pool_params.
*/
connection_pool(pool_executor_params ex_params, pool_params params)
: connection_pool(std::move(ex_params), std::move(params), 0)
{
}
/**
* \brief Constructs a connection pool.
* \details
* Both internal I/O objects and connections are constructed using the passed executor.
* \n
* The pool is created in a "not-running" state. Call \ref async_run to transition to the
* "running" state. Calling \ref async_get_connection in the "not-running" state will fail
* with \ref client_errc::cancelled.
* \n
* The constructed pool is always valid (`this->valid() == true`).
*
* \par Exception safety
* Strong guarantee. Exceptions may be thrown by memory allocations.
* \throws std::invalid_argument If `params` contains values that violate the rules described in \ref
* pool_params.
*/
connection_pool(asio::any_io_executor ex, pool_params params)
: connection_pool(pool_executor_params{ex, ex}, std::move(params), 0)
{
}
/**
* \brief Constructs a connection pool.
* \details
* Both internal I/O objects and connections are constructed using `ctx.get_executor()`.
* \n
* The pool is created in a "not-running" state. Call \ref async_run to transition to the
* "running" state. Calling \ref async_get_connection in the "not-running" state will fail
* with \ref client_errc::cancelled.
* \n
* The constructed pool is always valid (`this->valid() == true`).
* \n
* This function participates in overload resolution only if `ExecutionContext`
* satisfies the `ExecutionContext` requirements imposed by Boost.Asio.
*
* \par Exception safety
* Strong guarantee. Exceptions may be thrown by memory allocations.
* \throws std::invalid_argument If `params` contains values that violate the rules described in \ref
* pool_params.
*/
template <
class ExecutionContext
#ifndef BOOST_MYSQL_DOXYGEN
,
class = typename std::enable_if<std::is_convertible<
decltype(std::declval<ExecutionContext&>().get_executor()),
asio::any_io_executor>::value>::type
#endif
>
connection_pool(ExecutionContext& ctx, pool_params params)
: connection_pool({ctx.get_executor(), ctx.get_executor()}, std::move(params), 0)
{
}
#ifndef BOOST_MYSQL_DOXYGEN
connection_pool(const connection_pool&) = delete;
connection_pool& operator=(const connection_pool&) = delete;
#endif
/**
* \brief Move-constructor.
* \details
* Constructs a connection pool by taking ownership of `other`.
* \n
* After this function returns, if `other.valid() == true`, `this->valid() == true`.
* In any case, `other` will become invalid (`other.valid() == false`).
* \n
* Moving a connection pool with outstanding async operations
* is safe.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Thead-safety
* This function is never thread-safe, regardless of the executor
* configuration passed to the constructor. Calling this function
* concurrently with any other function introduces data races.
*/
connection_pool(connection_pool&& other) = default;
/**
* \brief Move assignment.
* \details
* Assigns `other` to `*this`, transferring ownership.
* \n
* After this function returns, if `other.valid() == true`, `this->valid() == true`.
* In any case, `other` will become invalid (`other.valid() == false`).
* \n
* Moving a connection pool with outstanding async operations
* is safe.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Thead-safety
* This function is never thread-safe, regardless of the executor
* configuration passed to the constructor. Calling this function
* concurrently with any other function introduces data races.
*/
connection_pool& operator=(connection_pool&& other) = default;
/// Destructor.
~connection_pool() = default;
/**
* \brief Returns whether the object is in a moved-from state.
* \details
* This function returns always `true` except for pools that have been
* moved-from. Moved-from objects don't represent valid pools. They can only
* be assigned to or destroyed.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Thead-safety
* This function is never thread-safe, regardless of the executor
* configuration passed to the constructor. Calling this function
* concurrently with any other function introduces data races.
*/
bool valid() const noexcept { return impl_.get() != nullptr; }
/// The executor type associated to this object.
using executor_type = asio::any_io_executor;
/**
* \brief Retrieves the executor associated to this object.
* \details
* Returns the pool executor passed to the constructor, as per
* \ref pool_executor_params::pool_executor.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Thead-safety
* This function is never thread-safe, regardless of the executor
* configuration passed to the constructor. Calling this function
* concurrently with any other function introduces data races.
*/
BOOST_MYSQL_DECL
executor_type get_executor() noexcept;
/**
* \brief Runs the pool task in charge of managing connections.
* \details
* This function creates and connects new connections, and resets and pings
* already created ones. You need to call this function for \ref async_get_connection
* to succeed.
* \n
* The async operation will run indefinitely, until the pool is cancelled
* (by being destroyed or calling \ref cancel). The operation completes once
* all internal connection operations (including connects, pings and resets)
* complete.
* \n
* It is safe to call this function after calling \ref cancel.
*
* \par Preconditions
* This function can be called at most once for a single pool.
* Formally, `async_run` hasn't been called before on `*this` or any object
* used to move-construct or move-assign `*this`.
* \n
* Additionally, `this->valid() == true`.
*
* \par Object lifetimes
* While the operation is outstanding, the pool's internal data will be kept alive.
* It is safe to destroy `*this` while the operation is outstanding.
*
* \par Handler signature
* The handler signature for this operation is `void(boost::mysql::error_code)`
*
* \par Errors
* This function always complete successfully. The handler signature ensures
* maximum compatibility with Boost.Asio infrastructure.
*
* \par Executor
* This function will run entirely in the pool's executor (as given by `this->get_executor()`).
* No internal data will be accessed or modified as part of the initiating function.
* This simplifies thread-safety.
*
* \par Thead-safety
* When the pool is constructed with adequate executor configuration, this function
* is safe to be called concurrently with \ref async_get_connection, \ref cancel,
* `~pooled_connection` and \ref pooled_connection::return_without_reset.
*/
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
auto async_run(CompletionToken&& token) BOOST_MYSQL_RETURN_TYPE(
decltype(asio::async_initiate<CompletionToken, void(error_code)>(initiate_run{}, token, impl_))
)
{
BOOST_ASSERT(valid());
return asio::async_initiate<CompletionToken, void(error_code)>(initiate_run{}, token, impl_);
}
/// \copydoc async_get_connection(diagnostics&,CompletionToken&&)
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection))
CompletionToken>
auto async_get_connection(CompletionToken&& token) BOOST_MYSQL_RETURN_TYPE(
decltype(async_get_connection_impl({}, nullptr, std::forward<CompletionToken>(token)))
)
{
return async_get_connection_impl(
get_default_timeout(),
nullptr,
std::forward<CompletionToken>(token)
);
}
/**
* \brief Retrieves a connection from the pool.
* \details
* Retrieves an idle connection from the pool to be used.
* \n
* If this function completes successfully (empty error code), the return \ref pooled_connection
* will have `valid() == true` and will be usable. If it completes with a non-empty error code,
* it will have `valid() == false`.
* \n
* If a connection is idle when the operation is started, it will complete immediately
* with that connection. Otherwise, it will wait for a connection to become idle
* (possibly creating one in the process, if pool configuration allows it), up to
* a duration of 30 seconds.
* \n
* If a timeout happens because connection establishment has failed, appropriate
* diagnostics will be returned.
*
* \par Preconditions
* `this->valid() == true` \n
*
* \par Object lifetimes
* While the operation is outstanding, the pool's internal data will be kept alive.
* It is safe to destroy `*this` while the operation is outstanding.
*
* \par Handler signature
* The handler signature for this operation is
* `void(boost::mysql::error_code, boost::mysql::pooled_connection)`
*
* \par Errors
* \li Any error returned by \ref any_connection::async_connect, if a timeout
* happens because connection establishment is failing.
* \li \ref client_errc::timeout, if a timeout happens for any other reason
* (e.g. all connections are in use and limits forbid creating more).
* \li \ref client_errc::cancelled if \ref cancel was called before the operation is started or while
* it is outstanding, or if the pool is not running.
*
* \par Executor
* This function will run entirely in the pool's executor (as given by `this->get_executor()`).
* No internal data will be accessed or modified as part of the initiating function.
* This simplifies thread-safety.
*
* \par Thead-safety
* When the pool is constructed with adequate executor configuration, this function
* is safe to be called concurrently with \ref async_run, \ref cancel,
* `~pooled_connection` and \ref pooled_connection::return_without_reset.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection))
CompletionToken>
auto async_get_connection(diagnostics& diag, CompletionToken&& token) BOOST_MYSQL_RETURN_TYPE(
decltype(async_get_connection_impl({}, nullptr, std::forward<CompletionToken>(token)))
)
{
return async_get_connection_impl(get_default_timeout(), &diag, std::forward<CompletionToken>(token));
}
/// \copydoc async_get_connection(std::chrono::steady_clock::duration,diagnostics&,CompletionToken&&)
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection))
CompletionToken>
auto async_get_connection(std::chrono::steady_clock::duration timeout, CompletionToken&& token)
BOOST_MYSQL_RETURN_TYPE(
decltype(async_get_connection_impl({}, nullptr, std::forward<CompletionToken>(token)))
)
{
return async_get_connection_impl(timeout, nullptr, std::forward<CompletionToken>(token));
}
/**
* \brief Retrieves a connection from the pool.
* \details
* Retrieves an idle connection from the pool to be used.
* \n
* If this function completes successfully (empty error code), the return \ref pooled_connection
* will have `valid() == true` and will be usable. If it completes with a non-empty error code,
* it will have `valid() == false`.
* \n
* If a connection is idle when the operation is started, it will complete immediately
* with that connection. Otherwise, it will wait for a connection to become idle
* (possibly creating one in the process, if pool configuration allows it), up to
* a duration of `timeout`. A zero timeout disables it.
* \n
* If a timeout happens because connection establishment has failed, appropriate
* diagnostics will be returned.
*
* \par Preconditions
* `this->valid() == true` \n
* Timeout values must be positive: `timeout.count() >= 0`.
*
* \par Object lifetimes
* While the operation is outstanding, the pool's internal data will be kept alive.
* It is safe to destroy `*this` while the operation is outstanding.
*
* \par Handler signature
* The handler signature for this operation is
* `void(boost::mysql::error_code, boost::mysql::pooled_connection)`
*
* \par Errors
* \li Any error returned by \ref any_connection::async_connect, if a timeout
* happens because connection establishment is failing.
* \li \ref client_errc::timeout, if a timeout happens for any other reason
* (e.g. all connections are in use and limits forbid creating more).
* \li \ref client_errc::cancelled if \ref cancel was called before the operation is started or while
* it is outstanding, or if the pool is not running.
*
* \par Executor
* This function will run entirely in the pool's executor (as given by `this->get_executor()`).
* No internal data will be accessed or modified as part of the initiating function.
* This simplifies thread-safety.
*
* \par Thead-safety
* When the pool is constructed with adequate executor configuration, this function
* is safe to be called concurrently with \ref async_run, \ref cancel,
* `~pooled_connection` and \ref pooled_connection::return_without_reset.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection))
CompletionToken>
auto async_get_connection(
std::chrono::steady_clock::duration timeout,
diagnostics& diag,
CompletionToken&& token
)
BOOST_MYSQL_RETURN_TYPE(
decltype(async_get_connection_impl({}, nullptr, std::forward<CompletionToken>(token)))
)
{
return async_get_connection_impl(timeout, &diag, std::forward<CompletionToken>(token));
}
/**
* \brief Stops any current outstanding operation and marks the pool as cancelled.
* \details
* This function has the following effects:
* \n
* \li Stops the currently outstanding \ref async_run operation, if any, which will complete
* with a success error code.
* \li Cancels any outstanding \ref async_get_connection operations, which will complete with
* \ref client_errc::cancelled.
* \li Marks the pool as cancelled. Successive `async_get_connection` calls will complete
* immediately with \ref client_errc::cancelled.
* \n
* This function will return immediately, without waiting for the cancelled operations to complete.
* \n
* You may call this function any number of times. Successive calls will have no effect.
*
* \par Preconditions
* `this->valid() == true`
*
* \par Exception safety
* Basic guarantee. Memory allocations and acquiring mutexes may throw.
*
* \par Thead-safety
* When the pool is constructed with adequate executor configuration, this function
* is safe to be called concurrently with \ref async_run, \ref async_get_connection,
* `~pooled_connection` and \ref pooled_connection::return_without_reset.
*/
BOOST_MYSQL_DECL
void cancel();
};
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/connection_pool.ipp>
#endif
#endif