//
// 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/with_diagnostics.hpp>

#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/connection_pool_fwd.hpp>
#include <boost/mysql/detail/initiation_base.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 Default completion tokens
 * The default completion token for all async operations in this class is
 * `with_diagnostics(asio::deferred)`, which allows you to use `co_await`
 * and have the expected exceptions thrown on error.
 *
 * \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 : detail::initiation_base
    {
        using detail::initiation_base::initiation_base;

        // Having diagnostics* here makes async_run compatible with with_diagnostics
        template <class Handler>
        void operator()(Handler&& h, diagnostics*, 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 : detail::initiation_base
    {
        using detail::initiation_base::initiation_base;

        template <class Handler>
        void operator()(
            Handler&& h,
            diagnostics* diag,
            std::shared_ptr<detail::pool_impl> self,
            std::chrono::steady_clock::duration timeout
        )
        {
            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)>(
            std::declval<initiate_get_connection>(),
            token,
            diag,
            impl_,
            timeout
        ))
    {
        BOOST_ASSERT(valid());
        return asio::async_initiate<CompletionToken, void(error_code, pooled_connection)>(
            initiate_get_connection{get_executor()},
            token,
            diag,
            impl_,
            timeout
        );
    }

    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 = with_diagnostics_t<asio::deferred_t>>
    auto async_run(CompletionToken&& token = {})
        BOOST_MYSQL_RETURN_TYPE(decltype(asio::async_initiate<CompletionToken, void(error_code)>(
            std::declval<initiate_run>(),
            token,
            static_cast<diagnostics*>(nullptr),
            impl_
        )))
    {
        BOOST_ASSERT(valid());
        return asio::async_initiate<CompletionToken, void(error_code)>(
            initiate_run{get_executor()},
            token,
            static_cast<diagnostics*>(nullptr),
            impl_
        );
    }

    /// \copydoc async_get_connection(diagnostics&,CompletionToken&&)
    template <
        BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::pooled_connection))
            CompletionToken = with_diagnostics_t<asio::deferred_t>>
    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 = with_diagnostics_t<asio::deferred_t>>
    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 = with_diagnostics_t<asio::deferred_t>>
    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 = with_diagnostics_t<asio::deferred_t>>
    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
