LCOV - code coverage report
Current view: top level - asio - listener.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 90.9 % 481 437
Test Date: 2026-11-10 10:38:07 Functions: 95.4 % 109 104

            Line data    Source code
       1              : #include "listener.hpp"
       2              : #include "async_event.hpp"
       3              : #include "detail/listener_access.hpp"
       4              : #include "io_context_of.hpp"
       5              : #include "with_timeout.hpp"
       6              : #include "worker_thread.hpp"
       7              : #include "sockets/ssl_socket.hpp"
       8              : #include "sockets/tcp_socket.hpp"
       9              : #include "sockets/unix_socket.hpp"
      10              : #include "ssl/certificate_manager.hpp"
      11              : #include "ssl/client_verify.hpp"
      12              : #include "../util/logger.hpp"
      13              : #include <algorithm>
      14              : #include <atomic>
      15              : #include <cerrno>
      16              : #include <condition_variable>
      17              : #include <exception>
      18              : #include <filesystem>
      19              : #include <mutex>
      20              : #include <optional>
      21              : #include <set>
      22              : #include <system_error>
      23              : #include <thread>
      24              : #include <type_traits>
      25              : #include <unordered_map>
      26              : #include <utility>
      27              : #include <boost/asio/ip/tcp.hpp>
      28              : #include <boost/asio/local/stream_protocol.hpp>
      29              : #include <boost/asio/post.hpp>
      30              : #include <boost/asio/steady_timer.hpp>
      31              : #include <boost/asio/strand.hpp>
      32              : #include <boost/asio/this_coro.hpp>
      33              : #include <fcntl.h>
      34              : #include <sys/socket.h>
      35              : #include <unistd.h>
      36              : 
      37              : namespace thinger::asio {
      38              : 
      39              : namespace {
      40              : 
      41              : using tcp = boost::asio::ip::tcp;
      42              : using local = boost::asio::local::stream_protocol;
      43              : using steady = std::chrono::steady_clock;
      44              : 
      45              : // Time between attempts to bind (see listener_options::max_listening_attempts)
      46              : constexpr std::chrono::seconds LISTEN_RETRY_DELAY{5};
      47              : // Time before accepting again after an error accepting (e.g. out of descriptors)
      48              : constexpr std::chrono::seconds ACCEPT_RETRY_DELAY{1};
      49              : 
      50              : // An accepted descriptor on its way to the executor serving it, closed if it never gets there
      51              : class accepted_descriptor {
      52              : public:
      53        17256 :     explicit accepted_descriptor(int descriptor) : descriptor_(descriptor) {}
      54       103506 :     accepted_descriptor(accepted_descriptor&& other) noexcept : descriptor_(std::exchange(other.descriptor_, -1)) {}
      55              :     accepted_descriptor& operator=(accepted_descriptor&&) = delete;
      56       120762 :     ~accepted_descriptor() { if (descriptor_ >= 0) ::close(descriptor_); }
      57              : 
      58        34506 :     int get() const { return descriptor_; }
      59        17250 :     int release() { return std::exchange(descriptor_, -1); }
      60              : 
      61              : private:
      62              :     int descriptor_;
      63              : };
      64              : 
      65              : // A socket of the listener is not inherited by the programs a handler runs (fork and exec): asio
      66              : // opens it without SOCK_CLOEXEC. The acceptor thread on Linux needs none (see accept_pending())
      67        43761 : void close_on_exec(int descriptor) {
      68        43761 :     ::fcntl(descriptor, F_SETFD, FD_CLOEXEC);
      69        43761 : }
      70              : 
      71              : // Sets up and serves an accepted connection. Nothing doing it may throw out of an accept handler
      72              : // or a worker: it would end the process. What is thrown is logged, and only drops the connection
      73              : // (its socket is destroyed, and closed, while unwinding)
      74              : template<typename Function>
      75        80817 : void serve_without_throwing(Function&& serve) noexcept {
      76              :     try {
      77        80817 :         serve();
      78           24 :     } catch (const std::exception& e) {
      79           12 :         LOG_ERROR("cannot serve an accepted connection: {}", e.what());
      80            0 :     } catch (...) {
      81            0 :         LOG_ERROR("cannot serve an accepted connection");
      82              :     }
      83        80817 : }
      84              : 
      85              : // The thread a listener accepts on (see detail::listener_access): stopped, and joined, before
      86              : // it is destroyed
      87              : struct thread_stopper {
      88        15235 :     void operator()(worker_thread* thread) const {
      89        15235 :         thread->stop();
      90        15235 :         delete thread;
      91        15235 :     }
      92              : };
      93              : using acceptor_thread_ptr = std::unique_ptr<worker_thread, thread_stopper>;
      94              : 
      95              : // The TLS of the connections of a listener, taken by open(): each one is created with this
      96              : // context (its handshake may switch to another certificate of the manager, see
      97              : // certificate_manager::sni_callback()) and asks for client certificates with this policy
      98              : struct tls_setup {
      99              :     std::shared_ptr<boost::asio::ssl::context> context;
     100              :     std::shared_ptr<const client_verify_policy> client_verify;
     101              : };
     102              : 
     103              : // The TLS handshakes in progress on one io_context serving a listener with a bound (see
     104              : // listener_options::max_pending_handshakes). Used only on its strand, where the handshakes it
     105              : // bounds run: the connections waiting for a slot wait for `slot_freed`, set whenever a slot is
     106              : // freed (all of them wake and check again: the order is not first come, first served)
     107              : struct handshake_gate {
     108           36 :     explicit handshake_gate(boost::asio::io_context& io_context)
     109           36 :         : strand(boost::asio::make_strand(io_context)), slot_freed(strand) {}
     110              : 
     111              :     boost::asio::any_io_executor strand;
     112              :     async_event slot_freed;
     113              :     size_t in_progress = 0;
     114              : };
     115              : 
     116              : // A slot of a gate, taken while a handshake runs: freed (waking the connections waiting for one)
     117              : // however the handshake ends
     118              : class handshake_slot {
     119              : public:
     120          114 :     explicit handshake_slot(std::shared_ptr<handshake_gate> gate) : gate_(std::move(gate)) { ++gate_->in_progress; }
     121          114 :     ~handshake_slot() {
     122          114 :         --gate_->in_progress;
     123          114 :         gate_->slot_freed.set();
     124          114 :     }
     125              :     handshake_slot(const handshake_slot&) = delete;
     126              :     handshake_slot& operator=(const handshake_slot&) = delete;
     127              : 
     128              : private:
     129              :     std::shared_ptr<handshake_gate> gate_;
     130              : };
     131              : 
     132              : // A connection accepted whose handshake has not ended (waiting for a slot included): stop()
     133              : // closes it on the executor it handshakes on
     134              : struct pending_handshake {
     135              :     std::weak_ptr<tcp_socket> sock;
     136              :     boost::asio::any_io_executor executor;
     137              :     std::shared_ptr<handshake_gate> gate;   // none without a bound
     138              : };
     139              : 
     140              : // The handshake of a TLS connection, closed if it does not end by `deadline` (none: no limit)
     141         8259 : awaitable<boost::system::error_code> handshake_until(std::shared_ptr<tcp_socket> sock,
     142              :                                                      std::optional<std::chrono::steady_clock::time_point> deadline) {
     143              :     if (!deadline) co_return co_await sock->handshake();
     144              :     if (*deadline <= std::chrono::steady_clock::now()) co_return boost::asio::error::timed_out;
     145              :     // On the executor of the handshake. The handler does not own the socket
     146              :     auto timer = std::make_shared<boost::asio::steady_timer>(co_await boost::asio::this_coro::executor, *deadline);
     147              :     auto expired = std::make_shared<std::atomic<bool>>(false);
     148         8134 :     timer->async_wait([weak = std::weak_ptr<tcp_socket>(sock), expired](const boost::system::error_code& ec) {
     149         8134 :         if (ec) return;
     150         3042 :         *expired = true;
     151         3042 :         if (auto pending = weak.lock()) pending->close();
     152              :     });
     153              :     auto ec = co_await sock->handshake();
     154              :     timer->cancel();
     155              :     co_return *expired ? boost::system::error_code(boost::asio::error::timed_out) : ec;
     156        16518 : }
     157              : 
     158              : // What a TLS connection throws is logged, as when a connection is served right away (see
     159              : // serve_without_throwing())
     160         8185 : void log_serve_failure(const std::exception_ptr& error) {
     161         8185 :     if (!error) return;
     162              :     try {
     163            0 :         std::rethrow_exception(error);
     164            0 :     } catch (const std::exception& e) {
     165            0 :         LOG_ERROR("cannot serve a TLS connection: {}", e.what());
     166            0 :     } catch (...) {
     167            0 :         LOG_ERROR("cannot serve a TLS connection");
     168            0 :     }
     169              : }
     170              : 
     171              : } // namespace
     172              : 
     173              : // What a listener shares with the handlers accepting and serving its connections: they hold
     174              : // it, never the listener, which may be gone by the time they run.
     175              : //
     176              : // Its mutex guards the short transitions only (stopping, the deliveries in progress, the
     177              : // handshakes pending, the gates, the acceptor's use by an accept handler), and is never held
     178              : // while the handler of the connections runs, nor while a handshake or a wait for a slot does:
     179              : // the handler may stop the listener, or destroy it. Nor while the executor providers run: they
     180              : // are the application's, and may block or call the listener (e.g. url()). Every connection reaches the handler
     181              : // through deliver(), which records the thread delivering it unless the listener is stopping;
     182              : // stop() sets stopping, closes the handshakes pending and waits until no delivery is recorded
     183              : // (its own excepted), so that once it returns no handler starts and none runs on another
     184              : // thread.
     185              : struct listener::state : std::enable_shared_from_this<state> {
     186        25307 :     state(listener_options listener_options, executor_provider accept_executors, executor_provider serve_executors)
     187        25307 :         : certificates(std::exchange(listener_options.tls, nullptr))
     188        25307 :         , manager(certificates)
     189        25307 :         , secure(certificates != nullptr)
     190        25307 :         , options(std::move(listener_options))
     191        25307 :         , accept_on(std::move(accept_executors))
     192        25307 :         , serve_on(std::move(serve_executors))
     193        25307 :         , allowed_remotes(options.allowed_remotes.begin(), options.allowed_remotes.end())
     194        50614 :         , forbidden_remotes(options.forbidden_remotes.begin(), options.forbidden_remotes.end()) {}
     195              : 
     196              :     // The manager of the options (taken out of them), held until stop(), whatever open()
     197              :     // returns: the connections reach it through their contexts (see
     198              :     // certificate_manager::sni_callback()), so their host names keep their certificates and
     199              :     // client certificate modes while the listener runs, even if the application keeps none
     200              :     std::shared_ptr<certificate_manager> certificates;
     201              :     const std::weak_ptr<certificate_manager> manager;
     202              :     const bool secure;
     203              :     // The options, without the manager
     204              :     const listener_options options;
     205              :     const executor_provider accept_on;
     206              :     const executor_provider serve_on;
     207              :     const std::set<std::string> allowed_remotes;
     208              :     const std::set<std::string> forbidden_remotes;
     209              :     // Accept on a thread of its own (see detail::listener_access), set before open()
     210              :     bool own_acceptor_thread = false;
     211              : 
     212              :     mutable std::mutex mutex;
     213              :     // Notified when a delivery ends and when the listener stops
     214              :     std::condition_variable changed;
     215              :     // Written with the mutex held; read without it by the loops that only need to end
     216              :     std::atomic<bool> stopping{false};
     217              :     std::atomic<uint16_t> port{0};
     218              :     bool opened = false;
     219              :     bool started = false;
     220              :     std::string url;
     221              :     // The threads delivering a connection to the handler, one entry per delivery
     222              :     std::vector<std::thread::id> delivering;
     223              :     // The TLS connections from their accept to the end of their handshake; taken by stop()
     224              :     std::unordered_map<const tcp_socket*, pending_handshake> pending;
     225              :     // The gate of each io_context serving TLS connections, with a bound: created as they are
     226              :     // first used, kept until stop()
     227              :     std::unordered_map<boost::asio::io_context*, std::shared_ptr<handshake_gate>> gates;
     228              :     // Set by open(), with the mutex held, before anything accepts: no context without TLS
     229              :     tls_setup tls;
     230              :     // Whether it holds its session cache on the manager of `tls` (see open()): released once
     231              :     bool holds_session_cache = false;
     232              : 
     233              :     // Set by open(), with the mutex held, before anything accepts; taken by stop(). The thread
     234              :     // is declared before the acceptors, which use its io_context
     235              :     acceptor_thread_ptr acceptor_thread;
     236              :     std::unique_ptr<tcp::acceptor> tcp_acceptor;
     237              :     std::unique_ptr<local::acceptor> unix_acceptor;
     238              :     tcp tcp_protocol = tcp::v4();
     239              :     // Set by start(), before anything accepts
     240              :     sync_callback<void(std::shared_ptr<socket>)> on_connection;
     241              : 
     242        52168 :     bool is_tcp() const { return std::holds_alternative<tcp_endpoint>(options.endpoint); }
     243              : 
     244              :     template<typename Protocol>
     245       123492 :     std::unique_ptr<typename Protocol::acceptor>& acceptor_of() {
     246        56704 :         if constexpr (std::is_same_v<Protocol, tcp>) return tcp_acceptor;
     247        66788 :         else return unix_acceptor;
     248              :     }
     249              : 
     250              :     // The port of the options: 0 for Unix sockets
     251        40553 :     uint16_t configured_port() const {
     252        40553 :         const auto* where = std::get_if<tcp_endpoint>(&options.endpoint);
     253        40553 :         return where ? where->port : 0;
     254              :     }
     255              : 
     256              :     // "http://host:port", "https://host:port" or "unix:/path"
     257        87689 :     std::string url_for(uint16_t bound_port) const {
     258        87689 :         if (const auto* unix = std::get_if<unix_endpoint>(&options.endpoint)) return "unix:" + unix->path;
     259        44771 :         const auto& host = std::get<tcp_endpoint>(options.endpoint).host;
     260        44771 :         auto shown = host.find(':') == std::string::npos ? host : "[" + host + "]";
     261        44771 :         return (secure ? "https://" : "http://") + shown + ":" + std::to_string(bound_port);
     262        44771 :     }
     263              : 
     264              :     // --- open() ---
     265              : 
     266        25310 :     boost::system::error_code open() {
     267        25310 :         std::shared_ptr<certificate_manager> tls;
     268              :         {
     269        25310 :             std::scoped_lock lock(mutex);
     270        25310 :             if (opened || stopping) return boost::asio::error::already_open;
     271        25303 :             tls = certificates;
     272        25310 :         }
     273        25303 :         return open_with(tls);
     274        25310 :     }
     275              : 
     276        25303 :     boost::system::error_code open_with(const std::shared_ptr<certificate_manager>& tls) {
     277        25303 :         tls_setup connections_tls;
     278        25303 :         if (auto ec = prepare_tls(tls.get(), connections_tls)) return ec;
     279        25279 :         if (auto ec = claim_session_cache(tls.get())) return ec;
     280        25258 :         auto ec = open_claimed(std::move(connections_tls));
     281              :         // Nothing bound: the setting is left to other listeners
     282        25258 :         if (ec) release_session_cache();
     283        25258 :         return ec;
     284        25303 :     }
     285              : 
     286        25258 :     boost::system::error_code open_claimed(tls_setup connections_tls) {
     287        25258 :         acceptor_thread_ptr thread;
     288        25258 :         if (own_acceptor_thread) {
     289        15235 :             thread.reset(new worker_thread(url_for(configured_port()) + " acceptor"));
     290        15235 :             thread->start();
     291              :         }
     292        65751 :         auto executor = thread ? boost::asio::any_io_executor(thread->get_io_context().get_executor())
     293        40493 :                                : (accept_on ? accept_on() : boost::asio::any_io_executor());
     294        25258 :         if (auto ec = check_executors(executor)) return ec;
     295        77892 :         return is_tcp() ? open_on<tcp>(executor, std::move(thread), std::move(connections_tls))
     296        77892 :                         : open_on<local>(executor, std::move(thread), std::move(connections_tls));
     297        25258 :     }
     298              : 
     299              :     // The TLS of the connections, if any: the default certificate of the manager as it is now,
     300              :     // and the client certificate policy, whose trusted CAs must be readable
     301        25303 :     boost::system::error_code prepare_tls(certificate_manager* tls, tls_setup& setup) const {
     302        25303 :         if (!tls) {
     303        21472 :             if (options.client_verify.mode != client_verify::none || !options.client_verify.trusted_cas.empty()) {
     304            0 :                 LOG_WARNING("{} has no TLS: its client certificate options are ignored", url_for(configured_port()));
     305              :             }
     306        21472 :             return {};
     307              :         }
     308         3831 :         if (!is_tcp()) {
     309            0 :             LOG_ERROR("cannot listen on {}: TLS is served on TCP endpoints only", url_for(configured_port()));
     310            0 :             return boost::asio::error::invalid_argument;
     311              :         }
     312         3831 :         setup.client_verify = client_verify_policy::create(options.client_verify);
     313         3831 :         if (!setup.client_verify) {
     314            6 :             LOG_ERROR("cannot listen on {}: the trusted CAs of the client certificates cannot be read",
     315              :                       url_for(configured_port()));
     316            6 :             return boost::asio::error::invalid_argument;
     317              :         }
     318         3825 :         setup.context = tls->get_default_certificate();
     319         3825 :         if (!setup.context) {
     320           18 :             LOG_ERROR("cannot listen on {}: its certificate_manager has no default certificate", url_for(configured_port()));
     321           18 :             return boost::asio::error::invalid_argument;
     322              :         }
     323         3807 :         return {};
     324              :     }
     325              : 
     326              :     // Apply the session cache of the options to the manager, before binding: refused if other
     327              :     // listeners of the same manager hold another one. Plain listeners have none
     328        25279 :     boost::system::error_code claim_session_cache(certificate_manager* tls) {
     329        25279 :         if (!tls) return {};
     330         3807 :         if (auto ec = tls->claim_session_cache(options.session_cache)) {
     331           21 :             LOG_ERROR("cannot listen on {}: invalid TLS session cache", url_for(configured_port()));
     332           21 :             return ec;
     333              :         }
     334         3786 :         std::scoped_lock lock(mutex);
     335         3786 :         holds_session_cache = true;
     336         3786 :         return {};
     337         3786 :     }
     338              : 
     339              :     // Once, when open() fails or the listener stops: nothing if the manager is gone, and its
     340              :     // setting with it
     341        27000 :     void release_session_cache() {
     342              :         {
     343        27000 :             std::scoped_lock lock(mutex);
     344        27000 :             if (!std::exchange(holds_session_cache, false)) return;
     345        27000 :         }
     346         3786 :         if (auto tls = manager.lock()) tls->release_session_cache();
     347              :     }
     348              : 
     349              :     // Both executors run on an io_context: the sockets of this release need one
     350        25258 :     boost::system::error_code check_executors(const boost::asio::any_io_executor& accept_executor) const {
     351        25258 :         if (!io_context_of(accept_executor)) {
     352            3 :             LOG_ERROR("cannot listen on {}: the executor to accept on does not run on an io_context", url_for(configured_port()));
     353            3 :             return boost::asio::error::invalid_argument;
     354              :         }
     355        25255 :         if (!serve_on || !io_context_of(serve_on())) {
     356            3 :             LOG_ERROR("cannot listen on {}: the executor to serve on does not run on an io_context", url_for(configured_port()));
     357            3 :             return boost::asio::error::invalid_argument;
     358              :         }
     359        25252 :         return {};
     360              :     }
     361              : 
     362              :     template<typename Protocol>
     363        25252 :     boost::system::error_code open_on(const boost::asio::any_io_executor& executor, acceptor_thread_ptr thread,
     364              :                                       tls_setup connections_tls) {
     365        25252 :         typename Protocol::endpoint endpoint;
     366        25252 :         if (auto ec = endpoint_of(executor, endpoint)) return ec;
     367        25246 :         std::unique_ptr<typename Protocol::acceptor> acceptor;
     368        25246 :         if (auto ec = bind(executor, endpoint, acceptor)) return ec;
     369        25225 :         return publish<Protocol>(std::move(acceptor), std::move(thread), std::move(connections_tls));
     370        25246 :     }
     371              : 
     372        13694 :     boost::system::error_code endpoint_of(const boost::asio::any_io_executor& executor, tcp::endpoint& endpoint) const {
     373        13694 :         const auto& where = std::get<tcp_endpoint>(options.endpoint);
     374        13694 :         boost::system::error_code ec;
     375        13694 :         tcp::resolver resolver(executor);
     376        13694 :         auto results = resolver.resolve(where.host, std::to_string(where.port), ec);
     377        13694 :         if (!ec && results.empty()) ec = boost::asio::error::host_not_found;
     378        13694 :         if (ec) {
     379            6 :             LOG_ERROR("cannot resolve {}:{}: {}", where.host, where.port, ec.message());
     380            6 :             return ec;
     381              :         }
     382        13688 :         endpoint = results.begin()->endpoint();
     383        13688 :         return {};
     384        13694 :     }
     385              : 
     386        11558 :     boost::system::error_code endpoint_of(const boost::asio::any_io_executor&, local::endpoint& endpoint) const {
     387        11558 :         const auto& path = std::get<unix_endpoint>(options.endpoint).path;
     388        11558 :         if (path.empty()) {
     389            0 :             LOG_ERROR("cannot listen on a Unix socket without a path");
     390            0 :             return boost::asio::error::invalid_argument;
     391              :         }
     392              :         // A socket file left by an earlier server is replaced
     393        11558 :         std::error_code ec;
     394        11558 :         std::filesystem::remove(path, ec);
     395        11558 :         endpoint = local::endpoint(path);
     396        11558 :         return {};
     397              :     }
     398              : 
     399              :     // Bind and listen, retrying every LISTEN_RETRY_DELAY up to max_listening_attempts
     400              :     template<typename Endpoint, typename Acceptor>
     401        25246 :     boost::system::error_code bind(const boost::asio::any_io_executor& executor, const Endpoint& endpoint,
     402              :                                    std::unique_ptr<Acceptor>& acceptor) {
     403        25246 :         int attempts = 0;
     404            6 :         while (true) {
     405        25246 :             LOG_DEBUG("starting acceptor of {}", url_for(configured_port()));
     406        25246 :             auto ec = try_bind(executor, endpoint, acceptor);
     407        25267 :             if (!ec) return {};
     408           21 :             LOG_ERROR("cannot start listening on {}: {}", url_for(configured_port()), ec.message());
     409           21 :             if (options.max_listening_attempts >= 0 && ++attempts >= options.max_listening_attempts) return ec;
     410            6 :             std::unique_lock lock(mutex);
     411           18 :             if (changed.wait_for(lock, LISTEN_RETRY_DELAY, [this] { return stopping.load(); })) {
     412            6 :                 return boost::asio::error::operation_aborted;
     413              :             }
     414              :         }
     415              :     }
     416              : 
     417              :     template<typename Endpoint, typename Acceptor>
     418        25246 :     static boost::system::error_code try_bind(const boost::asio::any_io_executor& executor, const Endpoint& endpoint,
     419              :                                               std::unique_ptr<Acceptor>& result) {
     420        25246 :         auto acceptor = std::make_unique<Acceptor>(executor);
     421        25246 :         boost::system::error_code ec;
     422        25246 :         acceptor->open(endpoint.protocol(), ec);
     423        25246 :         if (!ec) close_on_exec(acceptor->native_handle());
     424              :         if constexpr (std::is_same_v<Acceptor, tcp::acceptor>) {
     425        13688 :             if (!ec) acceptor->set_option(tcp::acceptor::reuse_address(true), ec);
     426              :         }
     427        25246 :         if (!ec) acceptor->bind(endpoint, ec);
     428        25246 :         if (!ec) acceptor->listen(boost::asio::socket_base::max_listen_connections, ec);
     429        25246 :         if (!ec) result = std::move(acceptor);
     430        50492 :         return ec;
     431        25246 :     }
     432              : 
     433              :     // Make the acceptor (and its thread) the listener's, unless it stopped meanwhile
     434              :     template<typename Protocol>
     435        25225 :     boost::system::error_code publish(std::unique_ptr<typename Protocol::acceptor> acceptor, acceptor_thread_ptr thread,
     436              :                                       tls_setup connections_tls) {
     437        25225 :         uint16_t bound = 0;
     438        25225 :         typename Protocol::endpoint local_endpoint;
     439        25225 :         boost::system::error_code ec;
     440        25225 :         local_endpoint = acceptor->local_endpoint(ec);
     441              :         if constexpr (std::is_same_v<Protocol, tcp>) {
     442        13667 :             if (!ec) bound = local_endpoint.port();
     443              :         } else {
     444        11558 :             set_permissions();
     445              :         }
     446              :         {
     447        25225 :             std::scoped_lock lock(mutex);
     448        25225 :             if (!stopping) {
     449              :                 if constexpr (std::is_same_v<Protocol, tcp>) {
     450        12719 :                     if (!ec) tcp_protocol = local_endpoint.protocol();
     451              :                 }
     452        23565 :                 acceptor_of<Protocol>() = std::move(acceptor);
     453        23565 :                 acceptor_thread = std::move(thread);
     454        23565 :                 tls = std::move(connections_tls);
     455        23565 :                 port = bound;
     456        23565 :                 url = url_for(bound);
     457        23565 :                 opened = true;
     458              :             }
     459        25225 :         }
     460        25225 :         if (acceptor) {
     461              :             // Stopped while opening: its socket file goes first, as in close() (stop() removes it
     462              :             // once open only), and its acceptor before its thread
     463              :             if constexpr (std::is_same_v<Protocol, local>) {
     464          712 :                 std::error_code removed;
     465          712 :                 std::filesystem::remove(std::get<unix_endpoint>(options.endpoint).path, removed);
     466              :             }
     467         1660 :             acceptor.reset();
     468         1660 :             return boost::asio::error::operation_aborted;
     469              :         }
     470        23565 :         LOG_DEBUG("listening on {}", url_for(bound));
     471        23565 :         return {};
     472              :     }
     473              : 
     474              :     // Let the owner and its group use the socket file
     475        11558 :     void set_permissions() const {
     476        11558 :         const auto& path = std::get<unix_endpoint>(options.endpoint).path;
     477        11558 :         std::error_code ec;
     478        11558 :         std::filesystem::permissions(path,
     479              :                                      std::filesystem::perms::owner_all |
     480              :                                      std::filesystem::perms::group_read |
     481              :                                      std::filesystem::perms::group_write,
     482              :                                      std::filesystem::perm_options::replace, ec);
     483        11558 :         if (ec) LOG_WARNING("cannot set the permissions of Unix socket {}: {}", path, ec.message());
     484        11558 :     }
     485              : 
     486              :     // --- start() ---
     487              : 
     488        23091 :     void start(sync_callback<void(std::shared_ptr<socket>)> handler) {
     489              :         {
     490        23091 :             std::scoped_lock lock(mutex);
     491        23091 :             if (!opened || started || stopping || !handler) {
     492            6 :                 LOG_ERROR("cannot start the listener on {}: {}", url_for(port),
     493              :                           !handler ? "no handler" : !opened ? "not open" : started ? "started already" : "stopped");
     494            6 :                 return;
     495              :             }
     496        23085 :             on_connection = std::move(handler);
     497        23085 :             started = true;
     498        23085 :             if (acceptor_thread) {
     499        13158 :                 if (is_tcp()) accept_on_thread<tcp>();
     500         8795 :                 else accept_on_thread<local>();
     501        13158 :                 return;
     502              :             }
     503        23091 :         }
     504         9927 :         if (is_tcp()) accept_next<tcp>();
     505         1881 :         else accept_next<local>();
     506              :     }
     507              : 
     508              :     // With the mutex held: on a thread of its own, every pending connection is accepted at
     509              :     // once, without waiting
     510              :     template<typename Protocol>
     511        13158 :     void accept_on_thread() {
     512        13158 :         boost::system::error_code ec;
     513        13158 :         acceptor_of<Protocol>()->non_blocking(true, ec);
     514        13158 :         if (ec) {
     515            0 :             LOG_ERROR("cannot accept connections: {}", ec.message());
     516            0 :             return;
     517              :         }
     518        39474 :         boost::asio::post(acceptor_thread->get_io_context(), [self = shared_from_this()] {
     519        13158 :             self->accept_pending<Protocol>();
     520              :         });
     521              :     }
     522              : 
     523              :     // --- Accepting with executor providers ---
     524              : 
     525              :     // Without the mutex: accept the next connection on the next executor to serve on, unless
     526              :     // the listener stopped meanwhile (its acceptor is gone then)
     527              :     template<typename Protocol>
     528        28442 :     void accept_next() {
     529        28442 :         auto executor = serve_on();
     530        28442 :         bool usable = io_context_of(executor) != nullptr;
     531        28442 :         std::scoped_lock lock(mutex);
     532        28442 :         if (stopping) return;
     533        28435 :         auto& acceptor = acceptor_of<Protocol>();
     534        28435 :         if (!usable) executor = acceptor->get_executor();
     535        80469 :         acceptor->async_accept(executor, [self = shared_from_this(), usable]
     536              :             (const boost::system::error_code& e, typename Protocol::socket peer) {
     537        26017 :                 self->on_accepted<Protocol>(e, std::move(peer), usable);
     538              :             });
     539        28449 :     }
     540              : 
     541              :     template<typename Protocol>
     542        26017 :     void on_accepted(const boost::system::error_code& e, typename Protocol::socket peer, bool usable) {
     543        26017 :         auto accepted_at = steady::now();
     544              :         // Where asio sets SO_NOSIGPIPE on the socket it accepted, this is a client already gone
     545              :         // (EINVAL by then): nothing wrong with the acceptor, and nothing to wait for
     546              : #if defined(SO_NOSIGPIPE)
     547              :         bool client_gone = e == boost::asio::error::invalid_argument;
     548              : #else
     549        26017 :         bool client_gone = false;
     550              : #endif
     551              :         {
     552              :             // Stopped meanwhile (from any thread): its acceptor is gone
     553        26017 :             std::scoped_lock lock(mutex);
     554        26017 :             if (stopping) return;
     555        18515 :             if (e == boost::asio::error::operation_aborted) {
     556            0 :                 LOG_DEBUG("stop accepting connections");
     557            0 :                 return;
     558              :             }
     559        18515 :             if (e && !client_gone) {
     560            0 :                 LOG_ERROR("cannot accept more connections: {}", e.message());
     561            0 :                 retry_accept<Protocol>();
     562            0 :                 return;
     563              :             }
     564        26017 :         }
     565        18515 :         if (client_gone) {
     566            0 :             accept_next<Protocol>();
     567            0 :             return;
     568              :         }
     569              :         // set before it reaches the executor serving it
     570        18515 :         close_on_exec(peer.native_handle());
     571        18515 :         accept_next<Protocol>();
     572        18515 :         if (!usable) {
     573            0 :             LOG_ERROR("cannot serve an accepted connection: the executor to serve on does not run on an io_context");
     574            0 :             return;
     575              :         }
     576        37030 :         serve_without_throwing([&] {
     577        18515 :             if (is_remote_allowed<Protocol>(peer.native_handle())) serve(std::move(peer), accepted_at);
     578              :         });
     579              :     }
     580              : 
     581              :     // --- Accepting on a thread of its own ---
     582              : 
     583              :     template<typename Protocol>
     584        27636 :     void wait_connections() {
     585        55272 :         acceptor_of<Protocol>()->async_wait(boost::asio::socket_base::wait_read,
     586        72812 :             [self = shared_from_this()](const boost::system::error_code& e) {
     587        17540 :                 if (!e) {
     588        17540 :                     self->accept_pending<Protocol>();
     589            0 :                 } else if (e != boost::asio::error::operation_aborted) {
     590            0 :                     LOG_ERROR("cannot accept more connections: {}", e.message());
     591            0 :                     self->retry_accept<Protocol>();
     592              :                 } else {
     593            0 :                     LOG_DEBUG("stop accepting connections");
     594              :                 }
     595              :             });
     596        27636 :     }
     597              : 
     598              :     // Only the acceptor thread uses the acceptor (stop() joins it before closing the acceptor),
     599              :     // and it is the only one running its io_context, so no other thread takes its readiness
     600              :     template<typename Protocol>
     601        30698 :     void accept_pending() {
     602        30698 :         auto& acceptor = acceptor_of<Protocol>();
     603        47954 :         while (!stopping) {
     604              : #if defined(__linux__)
     605        44892 :             int descriptor = ::accept4(acceptor->native_handle(), nullptr, nullptr, SOCK_CLOEXEC);
     606              : #else
     607              :             int descriptor = ::accept(acceptor->native_handle(), nullptr, nullptr);
     608              :             if (descriptor >= 0) close_on_exec(descriptor);
     609              : #endif
     610        44892 :             if (descriptor >= 0) {
     611        17256 :                 accepted_descriptor accepted(descriptor);
     612        17256 :                 auto accepted_at = steady::now();
     613              : #if defined(SO_NOSIGPIPE)
     614              :                 int enabled = 1;
     615              :                 ::setsockopt(descriptor, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled));
     616              : #endif
     617        34512 :                 serve_without_throwing([&] { hand_over<Protocol>(std::move(accepted), accepted_at); });
     618        17256 :                 continue;
     619        17256 :             }
     620        27636 :             int error = errno;
     621        27636 :             if (error == EINTR || error == ECONNABORTED || error == EPROTO) continue;
     622        27636 :             if (error == EAGAIN || error == EWOULDBLOCK) {
     623        27636 :                 wait_connections<Protocol>();
     624              :             } else {
     625            0 :                 LOG_ERROR("cannot accept more connections: {}", std::error_code(error, std::system_category()).message());
     626            0 :                 retry_accept<Protocol>();
     627              :             }
     628        27636 :             return;
     629              :         }
     630              :     }
     631              : 
     632              :     // The connection is registered and set up on the executor serving it, waking it up once
     633              :     template<typename Protocol>
     634        17256 :     void hand_over(accepted_descriptor descriptor, steady::time_point accepted_at) {
     635        17256 :         if (!is_remote_allowed<Protocol>(descriptor.get())) return;
     636        17250 :         auto executor = serve_on();
     637        17250 :         if (!io_context_of(executor)) {
     638            0 :             LOG_ERROR("cannot serve an accepted connection: the executor to serve on does not run on an io_context");
     639            0 :             return;
     640              :         }
     641        17250 :         auto protocol = protocol_of<Protocol>();
     642        17250 :         boost::asio::post(executor,
     643        34500 :             [self = shared_from_this(), executor, protocol, descriptor = std::move(descriptor), accepted_at]() mutable {
     644        17250 :                 typename Protocol::socket peer(executor);
     645        17250 :                 boost::system::error_code ec;
     646        17250 :                 peer.assign(protocol, descriptor.get(), ec);
     647        17250 :                 if (ec) {
     648            0 :                     LOG_ERROR("cannot serve an accepted connection: {}", ec.message());
     649            0 :                     return;
     650              :                 }
     651        17250 :                 descriptor.release();
     652        34500 :                 serve_without_throwing([&] { self->serve(std::move(peer), accepted_at); });
     653        17250 :             });
     654        17250 :     }
     655              : 
     656              :     template<typename Protocol>
     657        17250 :     Protocol protocol_of() const {
     658         3950 :         if constexpr (std::is_same_v<Protocol, tcp>) return tcp_protocol;
     659        13300 :         else return local();
     660              :     }
     661              : 
     662              :     // Accept again after a delay: an error accepting (e.g. out of descriptors) would repeat
     663              :     // at once otherwise
     664              :     template<typename Protocol>
     665            0 :     void retry_accept() {
     666            0 :         auto timer = std::make_shared<boost::asio::steady_timer>(acceptor_of<Protocol>()->get_executor(),
     667              :                                                                  ACCEPT_RETRY_DELAY);
     668            0 :         timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& e) {
     669            0 :             if (e) return;
     670            0 :             if (self->own_acceptor_thread) self->accept_pending<Protocol>();
     671            0 :             else self->accept_next<Protocol>();
     672              :         });
     673            0 :     }
     674              : 
     675              :     // Whether to serve a connection just accepted, for its remote address (TCP only)
     676              :     template<typename Protocol>
     677        35771 :     bool is_remote_allowed(int descriptor) const {
     678              :         if constexpr (!std::is_same_v<Protocol, tcp>) {
     679        16767 :             return true;
     680              :         } else {
     681              :             // The remote address is only looked up to filter it
     682        19004 :             if (allowed_remotes.empty() && forbidden_remotes.empty()) return true;
     683           18 :             tcp::endpoint remote;
     684           18 :             auto size = static_cast<socklen_t>(remote.capacity());
     685           18 :             std::string remote_ip = "0.0.0.0";
     686           18 :             if (::getpeername(descriptor, remote.data(), &size) == 0) {
     687           18 :                 remote.resize(size);
     688           18 :                 remote_ip = remote.address().to_string();
     689              :             }
     690           42 :             if (!forbidden_remotes.contains(remote_ip) &&
     691           24 :                 (allowed_remotes.empty() || allowed_remotes.contains(remote_ip))) {
     692            6 :                 return true;
     693              :             }
     694           24 :             LOG_DEBUG("rejecting connection from: ip: {}, port: {}, secure: {}",
     695              :                         remote_ip, port.load(), secure);
     696           12 :             return false;
     697           18 :         }
     698              :     }
     699              : 
     700              :     // --- Serving ---
     701              : 
     702              :     // A connection accepted, on the executor serving it (see serve_without_throwing())
     703        18992 :     void serve(tcp::socket peer, steady::time_point accepted_at) {
     704        18992 :         auto sock = make_socket(std::move(peer));
     705        18992 :         LOG_DEBUG("received connection from: ip: {}, port: {}, secure: {}",
     706              :                   sock->get_remote_ip(), sock->get_local_port(), sock->is_secure());
     707              :         // Fails if the connection is already gone: drop it
     708        18992 :         if (options.tcp_no_delay && !sock->enable_tcp_no_delay()) return;
     709        18992 :         if (sock->requires_handshake()) {
     710         8292 :             handshake(std::move(sock), accepted_at);
     711              :         } else {
     712        10700 :             deliver(std::move(sock));
     713              :         }
     714        18992 :     }
     715              : 
     716              :     // A TLS connection is created with the context of the listener and asks for client
     717              :     // certificates with its policy (the mode of the host name, if it has one, replaces it in the
     718              :     // handshake: see certificate_manager::sni_callback())
     719        18992 :     std::shared_ptr<tcp_socket> make_socket(tcp::socket peer) const {
     720        18992 :         if (!tls.context) return std::make_shared<tcp_socket>("listener", std::move(peer));
     721              :         // On an io_context: checked as it was accepted
     722         8292 :         auto* io_context = io_context_of(peer.get_executor());
     723         8292 :         auto sock = std::make_shared<ssl_socket>("listener", *io_context, tls.context);
     724         8292 :         sock->get_socket() = std::move(peer);
     725         8292 :         sock->set_client_verify(tls.client_verify);
     726         8292 :         return sock;
     727         8292 :     }
     728              : 
     729              :     // --- TLS handshakes ---
     730              : 
     731              :     // The end of the handshake of a connection accepted at `accepted_at`: none without a limit
     732         8274 :     std::optional<steady::time_point> handshake_deadline(steady::time_point accepted_at) const {
     733         8274 :         if (options.handshake_timeout.count() <= 0) return std::nullopt;
     734         8265 :         return accepted_at + options.handshake_timeout;
     735              :     }
     736              : 
     737              :     // Start the handshake of a TLS connection, registered as pending until it ends: on the
     738              :     // io_context serving it, or on the strand of its gate with a bound
     739         8292 :     void handshake(std::shared_ptr<tcp_socket> sock, steady::time_point accepted_at) {
     740         8292 :         pending_handshake entry{sock, sock->get_io_context().get_executor(), nullptr};
     741              :         {
     742         8292 :             std::scoped_lock lock(mutex);
     743         8292 :             if (stopping) {
     744           18 :                 sock->close();
     745           18 :                 return;
     746              :             }
     747         8274 :             if (options.max_pending_handshakes > 0) {
     748          129 :                 entry.gate = gate_of(sock->get_io_context());
     749          129 :                 entry.executor = entry.gate->strand;
     750              :             }
     751         8274 :             pending.emplace(sock.get(), entry);
     752         8292 :         }
     753         8274 :         co_spawn(entry.executor, serve_tls(shared_from_this(), std::move(sock), entry.gate, accepted_at),
     754              :                  log_serve_failure);
     755         8292 :     }
     756              : 
     757              :     // With the mutex held
     758          129 :     std::shared_ptr<handshake_gate> gate_of(boost::asio::io_context& io_context) {
     759          129 :         auto& gate = gates[&io_context];
     760          129 :         if (!gate) gate = std::make_shared<handshake_gate>(io_context);
     761          129 :         return gate;
     762              :     }
     763              : 
     764              :     // The handshake of a TLS connection, then the handler, unless the listener stopped meanwhile
     765         8274 :     static awaitable<void> serve_tls(std::shared_ptr<state> self, std::shared_ptr<tcp_socket> sock,
     766              :                                      std::shared_ptr<handshake_gate> gate, steady::time_point accepted_at) {
     767              :         auto deadline = self->handshake_deadline(accepted_at);
     768              :         boost::system::error_code ec;
     769              :         {
     770              :             pending_registration registered(self, sock.get());
     771              :             if (gate) {
     772              :                 ec = co_await handshake_in_slot(self, sock, std::move(gate), deadline);
     773              :             } else {
     774              :                 ec = co_await handshake_until(sock, deadline);
     775              :             }
     776              :         }
     777              :         // Not errors of the listener: any client (or scanner) may cause them
     778              :         if (ec == boost::asio::error::timed_out) {
     779              :             LOG_DEBUG("TLS handshake not done in time, remote ip: {}", sock->get_remote_ip());
     780              :             sock->close();
     781              :         } else if (ec && self->stopping) {
     782              :             sock->close();
     783              :         } else if (ec) {
     784              :             LOG_DEBUG("error while handling SSL handshake: {}, remote ip: {}", ec.message(), sock->get_remote_ip());
     785              :         } else {
     786              :             self->deliver(std::move(sock));
     787              :         }
     788        16548 :     }
     789              : 
     790              :     // Removes a connection from the pending ones once its handshake ended, however it ended
     791              :     struct pending_registration {
     792              :         std::shared_ptr<state> self;
     793              :         const tcp_socket* sock;
     794              : 
     795         8274 :         pending_registration(std::shared_ptr<state> listener_state, const tcp_socket* pending_sock)
     796         8274 :             : self(std::move(listener_state)), sock(pending_sock) {}
     797              :         pending_registration(const pending_registration&) = delete;
     798              :         pending_registration& operator=(const pending_registration&) = delete;
     799         8274 :         ~pending_registration() {
     800         8274 :             std::scoped_lock lock(self->mutex);
     801         8274 :             self->pending.erase(sock);
     802         8274 :         }
     803              :     };
     804              : 
     805              :     // On the strand of the gate: the handshake in a slot, waited for within the deadline
     806          129 :     static awaitable<boost::system::error_code> handshake_in_slot(std::shared_ptr<state> self,
     807              :                                                                   std::shared_ptr<tcp_socket> sock,
     808              :                                                                   std::shared_ptr<handshake_gate> gate,
     809              :                                                                   std::optional<steady::time_point> deadline) {
     810              :         if (auto ec = co_await wait_for_slot(self, *gate, deadline)) co_return ec;
     811              :         handshake_slot slot(std::move(gate));
     812              :         co_return co_await handshake_until(std::move(sock), deadline);
     813          258 :     }
     814              : 
     815              :     // On the strand of the gate: until a slot is free (taken by the caller before it suspends
     816              :     // again), `timed_out` at the deadline, or `operation_aborted` once the listener stops
     817          129 :     static awaitable<boost::system::error_code> wait_for_slot(std::shared_ptr<state> self, handshake_gate& gate,
     818              :                                                               std::optional<steady::time_point> deadline) {
     819              :         while (true) {
     820              :             if (self->stopping) co_return boost::asio::error::operation_aborted;
     821              :             if (gate.in_progress < self->options.max_pending_handshakes) co_return boost::system::error_code{};
     822              :             // Set again by the next slot freed, or by stop()
     823              :             gate.slot_freed.reset();
     824              :             if (!deadline) {
     825              :                 co_await gate.slot_freed.wait();
     826              :                 continue;
     827              :             }
     828              :             auto left = *deadline - steady::now();
     829              :             if (left <= steady::duration::zero() || !co_await with_timeout(gate.slot_freed.wait(), left)) {
     830              :                 co_return boost::asio::error::timed_out;
     831              :             }
     832              :         }
     833          258 :     }
     834              : 
     835        16767 :     void serve(local::socket peer, steady::time_point) {
     836        16767 :         auto sock = std::make_shared<unix_socket>("listener", std::move(peer));
     837        16767 :         LOG_DEBUG("received connection on Unix socket: {}", std::get<unix_endpoint>(options.endpoint).path);
     838        16767 :         deliver(std::move(sock));
     839        16767 :     }
     840              : 
     841              :     // Hand a connection to the handler, unless the listener is stopping (see above)
     842        32042 :     void deliver(std::shared_ptr<socket> sock) {
     843        32042 :         auto delivering_thread = std::this_thread::get_id();
     844              :         {
     845        32042 :             std::scoped_lock lock(mutex);
     846        32042 :             if (stopping) {
     847         4246 :                 sock->close();
     848         4246 :                 return;
     849              :             }
     850        27796 :             delivering.push_back(delivering_thread);
     851        32042 :         }
     852        55592 :         serve_without_throwing([&] { on_connection(std::move(sock)); });
     853              :         {
     854              :             // Not there if the handler stopped the listener
     855        27796 :             std::scoped_lock lock(mutex);
     856        27796 :             auto it = std::find(delivering.begin(), delivering.end(), delivering_thread);
     857        27796 :             if (it != delivering.end()) delivering.erase(it);
     858        27796 :         }
     859        27796 :         changed.notify_all();
     860              :     }
     861              : 
     862              :     // --- stop() ---
     863              : 
     864        50497 :     void stop() {
     865        50497 :         std::unique_lock lock(mutex);
     866        50497 :         bool first = !stopping;
     867        50497 :         stopping = true;
     868              :         // A handler stopping the listener does not wait for itself
     869        50497 :         std::erase(delivering, std::this_thread::get_id());
     870        50497 :         if (first) {
     871        25307 :             auto thread = std::move(acceptor_thread);
     872        25307 :             auto handshakes = std::exchange(pending, {});
     873              :             // Their strands go with the handshakes still holding them, not with this state
     874        25307 :             auto released_gates = std::exchange(gates, {});
     875        25307 :             auto released_manager = std::exchange(certificates, nullptr);
     876        25307 :             lock.unlock();
     877        25307 :             changed.notify_all();   // an open() waiting to bind again gives up
     878        25307 :             close(std::move(thread));
     879        25307 :             end_handshakes(handshakes);
     880        25307 :             released_gates.clear();
     881        25307 :             release_session_cache();
     882              :             // Destroyed here if nothing else holds it, outside the lock: the handshakes still
     883              :             // running find it gone (see certificate_manager::sni_callback())
     884        25307 :             released_manager.reset();
     885        25307 :             lock.lock();
     886        25307 :         }
     887       101047 :         changed.wait(lock, [this] { return delivering.empty(); });
     888        50497 :     }
     889              : 
     890              :     // Close the connections whose handshake is pending, on the executor they handshake on, and
     891              :     // wake those waiting for a slot: they see the listener stopping and end, without serving.
     892              :     // Those gone already (their io_context with them, maybe) are left alone
     893        25307 :     static void end_handshakes(const std::unordered_map<const tcp_socket*, pending_handshake>& handshakes) {
     894        25838 :         for (const auto& [_, handshake] : handshakes) {
     895          531 :             if (handshake.sock.expired()) continue;
     896          520 :             boost::asio::post(handshake.executor, [weak = handshake.sock, gate = handshake.gate] {
     897          440 :                 if (auto sock = weak.lock()) sock->close();
     898          440 :                 if (gate) gate->slot_freed.set();
     899          440 :             });
     900              :         }
     901        25307 :     }
     902              : 
     903              :     // Close the acceptor, once its thread (if any) is not using it. The Unix socket file goes
     904              :     // first, as in libuv and Go: a server binding its path as soon as a client cannot connect
     905              :     // does not lose its new file to this one. The accept handlers still to run do nothing, as
     906              :     // the listener is stopping
     907        25307 :     void close(acceptor_thread_ptr thread) {
     908        25307 :         if (thread) thread->stop();
     909        25307 :         std::unique_ptr<tcp::acceptor> tcp_closed;
     910        25307 :         std::unique_ptr<local::acceptor> unix_closed;
     911        25307 :         bool was_open = false;
     912              :         {
     913        25307 :             std::scoped_lock lock(mutex);
     914        25307 :             tcp_closed = std::move(tcp_acceptor);
     915        25307 :             unix_closed = std::move(unix_acceptor);
     916        25307 :             was_open = opened;
     917        25307 :         }
     918        25307 :         if (const auto* unix = std::get_if<unix_endpoint>(&options.endpoint); unix && was_open) {
     919        10846 :             std::error_code ec;
     920        10846 :             std::filesystem::remove(unix->path, ec);
     921        10846 :             if (ec) LOG_WARNING("cannot remove Unix socket file {}: {}", unix->path, ec.message());
     922              :         }
     923        25307 :         close_acceptor(tcp_closed);
     924        25307 :         close_acceptor(unix_closed);
     925        25307 :         port = 0;
     926              :         // The thread goes after its acceptor, with the handlers it never ran
     927        25307 :         tcp_closed.reset();
     928        25307 :         unix_closed.reset();
     929        25307 :     }
     930              : 
     931              :     template<typename Acceptor>
     932        50614 :     static void close_acceptor(const std::unique_ptr<Acceptor>& acceptor) {
     933        50614 :         if (!acceptor || !acceptor->is_open()) return;
     934        23565 :         boost::system::error_code ec;
     935        23565 :         acceptor->close(ec);
     936        23565 :         if (ec) LOG_WARNING("cannot close the acceptor: {}", ec.message());
     937              :     }
     938              : };
     939              : 
     940        25307 : listener::listener(listener_options options, executor_provider accept_on, executor_provider serve_on)
     941        25307 :     : state_(std::make_shared<state>(std::move(options), std::move(accept_on), std::move(serve_on))) {}
     942              : 
     943        25307 : listener::~listener() {
     944              :     try {
     945        25307 :         stop();
     946            0 :     } catch (const std::exception& e) {
     947            0 :         LOG_ERROR("cannot stop a listener: {}", e.what());
     948            0 :     }
     949        25307 : }
     950              : 
     951        25310 : boost::system::error_code listener::open() {
     952        25310 :     return state_->open();
     953              : }
     954              : 
     955        23091 : void listener::start(sync_callback<void(std::shared_ptr<socket>)> on_connection) {
     956        23091 :     state_->start(std::move(on_connection));
     957        23091 : }
     958              : 
     959        50497 : void listener::stop() {
     960        50497 :     state_->stop();
     961        50497 : }
     962              : 
     963        26040 : uint16_t listener::local_port() const {
     964        26040 :     return state_->port;
     965              : }
     966              : 
     967        21255 : std::string listener::url() const {
     968        21255 :     std::scoped_lock lock(state_->mutex);
     969        42510 :     return state_->url;
     970        21255 : }
     971              : 
     972        15245 : void detail::listener_access::use_acceptor_thread(listener& l) {
     973        15245 :     std::scoped_lock lock(l.state_->mutex);
     974        15245 :     if (!l.state_->opened) l.state_->own_acceptor_thread = true;
     975        15245 : }
     976              : 
     977              : } // namespace thinger::asio
        

Generated by: LCOV version 2.0-1