From a1a8680dea4b32af1333e81531fa610cc196547f Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Mon, 28 Sep 2026 16:04:05 +0200 Subject: [PATCH] refactor: create I/O services on first use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend construct() registered every protocol service eagerly, and the scheduler constructors did the same for the resolver, signal, and file services, so every program linked the full service surface of every compiled backend. Registration now happens on first use. The abstract scheduler becomes a registry service keyed under its own type — the concrete schedulers already declared that key but the base was not findable — so services constructed from only an execution_context can locate the scheduler without naming a concrete backend type. Each public I/O object gets or creates its service through a per-protocol dispatch helper that probes which scheduler the context was built with, so the linker keeps only the services of the protocols a program actually names. Acceptor services get-or-create the stream service they hand accepted descriptors to. --- include/boost/corosio/detail/scheduler.hpp | 28 ++- .../native/detail/endpoint_convert.hpp | 4 +- .../native/detail/epoll/epoll_scheduler.hpp | 9 - .../win_local_stream_acceptor_service.hpp | 8 +- .../detail/iocp/win_local_stream_service.hpp | 9 +- .../detail/iocp/win_resolver_service.hpp | 8 +- .../native/detail/iocp/win_scheduler.hpp | 4 - .../detail/iocp/win_tcp_acceptor_service.hpp | 7 +- .../native/detail/kqueue/kqueue_scheduler.hpp | 9 - .../posix_random_access_file_service.hpp | 12 +- .../detail/posix/posix_resolver_service.hpp | 24 +- .../detail/posix/posix_signal_service.hpp | 30 +-- .../posix/posix_stream_file_service.hpp | 11 +- .../detail/reactor/reactor_scheduler.hpp | 2 - .../detail/reactor/reactor_service_finals.hpp | 6 +- .../native/detail/select/select_scheduler.hpp | 9 - .../detail/uring/uring_random_access_file.hpp | 4 +- .../native/detail/uring/uring_scheduler.hpp | 6 - .../native/detail/uring/uring_stream_file.hpp | 5 +- .../native/native_local_datagram_socket.hpp | 2 +- .../native/native_local_stream_acceptor.hpp | 2 +- .../native/native_local_stream_socket.hpp | 2 +- .../native/native_random_access_file.hpp | 2 +- .../corosio/native/native_stream_file.hpp | 2 +- .../corosio/native/native_tcp_acceptor.hpp | 2 +- .../corosio/native/native_tcp_socket.hpp | 2 +- .../corosio/native/native_udp_socket.hpp | 2 +- .../src/detail/use_backend_service.hpp | 145 ++++++++++++ src/corosio/src/io_context.cpp | 64 +----- src/corosio/src/local_datagram_socket.cpp | 8 +- src/corosio/src/local_stream_acceptor.cpp | 8 +- src/corosio/src/local_stream_socket.cpp | 8 +- src/corosio/src/random_access_file.cpp | 11 +- src/corosio/src/resolver.cpp | 2 +- src/corosio/src/signal_set.cpp | 2 +- src/corosio/src/stream_file.cpp | 11 +- src/corosio/src/tcp_acceptor.cpp | 11 +- src/corosio/src/tcp_socket.cpp | 21 +- src/corosio/src/udp_socket.cpp | 8 +- test/unit/fault/iocp_faults.cpp | 15 +- test/unit/lazy_services.cpp | 208 ++++++++++++++++++ 41 files changed, 508 insertions(+), 225 deletions(-) create mode 100644 src/corosio/src/detail/use_backend_service.hpp create mode 100644 test/unit/lazy_services.cpp diff --git a/include/boost/corosio/detail/scheduler.hpp b/include/boost/corosio/detail/scheduler.hpp index 42b62bb57..5e946d6d6 100644 --- a/include/boost/corosio/detail/scheduler.hpp +++ b/include/boost/corosio/detail/scheduler.hpp @@ -13,12 +13,14 @@ #define BOOST_COROSIO_DETAIL_SCHEDULER_HPP #include +#include -#include #include -#include +#include +#include #include +#include namespace boost::corosio::detail { @@ -30,11 +32,18 @@ class scheduler_op; this to implement the reactor/proactor event loop. The @ref io_context delegates all scheduling operations here. + The scheduler is a registry service keyed under this abstract + type, so services created on first use can locate it without + naming a concrete backend. + @see io_context */ struct BOOST_COROSIO_DECL scheduler + : capy::execution_context::service { - virtual ~scheduler() = default; + using key_type = scheduler; + + ~scheduler() override = default; /// Post a coroutine handle for deferred execution. virtual void post(std::coroutine_handle<>) const = 0; @@ -126,6 +135,19 @@ struct BOOST_COROSIO_DECL scheduler virtual void configure_threading(threading_config) noexcept = 0; }; +/** Return the scheduler registered with the context. + + @throws std::logic_error If the context has no backend installed. +*/ +inline scheduler& +get_scheduler(capy::execution_context& ctx) +{ + auto* sched = ctx.find_service(); + if (!sched) + throw_logic_error("no scheduler installed"); + return *sched; +} + } // namespace boost::corosio::detail #endif diff --git a/include/boost/corosio/native/detail/endpoint_convert.hpp b/include/boost/corosio/native/detail/endpoint_convert.hpp index 1e4578f43..9e7c3f0b4 100644 --- a/include/boost/corosio/native/detail/endpoint_convert.hpp +++ b/include/boost/corosio/native/detail/endpoint_convert.hpp @@ -298,7 +298,9 @@ to_sockaddr(local_endpoint const& ep, sockaddr_storage& storage) noexcept un_sa_t sa{}; sa.sun_family = AF_UNIX; auto path = ep.path(); - auto copy_len = (std::min)(path.size(), sizeof(sa.sun_path)); + auto copy_len = (std::min)( + path.size(), + (std::min)(local_endpoint::max_path_length, sizeof(sa.sun_path))); if (copy_len > 0) std::memcpy(sa.sun_path, path.data(), copy_len); std::memcpy(&storage, &sa, sizeof(sa)); diff --git a/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp b/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp index 3e385a63e..574c25665 100644 --- a/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp +++ b/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp @@ -24,10 +24,6 @@ #include #include #include -#include -#include -#include -#include #include @@ -215,11 +211,6 @@ inline epoll_scheduler::epoll_scheduler(capy::execution_context& ctx, int) self->interrupt_reactor(); })); - get_resolver_service(ctx, *this); - get_signal_service(ctx, *this); - get_stream_file_service(ctx, *this); - get_random_access_file_service(ctx, *this); - completed_ops_.push(&task_op_); } diff --git a/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp b/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp index 4f08e9cbc..39aafb6d3 100644 --- a/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp @@ -43,8 +43,8 @@ class BOOST_COROSIO_DECL win_local_stream_acceptor_service final : public local_stream_acceptor_service { public: - win_local_stream_acceptor_service( - capy::execution_context& ctx, win_local_stream_service& svc); + explicit win_local_stream_acceptor_service( + capy::execution_context& ctx); io_object::implementation* construct() override; @@ -577,8 +577,8 @@ win_local_stream_acceptor::get_internal() const noexcept // ============================================================ inline win_local_stream_acceptor_service::win_local_stream_acceptor_service( - capy::execution_context& /*ctx*/, win_local_stream_service& svc) - : svc_(svc) + capy::execution_context& ctx) + : svc_(ctx.use_service()) { } diff --git a/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp b/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp index 0663c8b67..1125931d4 100644 --- a/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -55,8 +55,7 @@ class BOOST_COROSIO_DECL win_local_stream_service final void close(io_object::handle& h) override; - explicit win_local_stream_service( - capy::execution_context& ctx, win_tcp_service& tcp_svc); + explicit win_local_stream_service(capy::execution_context& ctx); ~win_local_stream_service(); @@ -877,8 +876,8 @@ win_local_stream_socket::get_internal() const noexcept // ============================================================ inline win_local_stream_service::win_local_stream_service( - capy::execution_context& ctx, win_tcp_service& tcp_svc) - : tcp_svc_(tcp_svc) + capy::execution_context& ctx) + : tcp_svc_(ctx.use_service()) , sched_(ctx.use_service()) , iocp_(sched_.native_handle()) { diff --git a/include/boost/corosio/native/detail/iocp/win_resolver_service.hpp b/include/boost/corosio/native/detail/iocp/win_resolver_service.hpp index 7f4c77683..fffa05d13 100644 --- a/include/boost/corosio/native/detail/iocp/win_resolver_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_resolver_service.hpp @@ -17,6 +17,7 @@ #if BOOST_COROSIO_HAS_IOCP #include +#include #include #include @@ -60,9 +61,8 @@ class BOOST_COROSIO_DECL win_resolver_service final /** Construct the resolver service. @param ctx Reference to the owning execution_context. - @param sched Reference to the scheduler for posting completions. */ - win_resolver_service(capy::execution_context& ctx, scheduler& sched); + explicit win_resolver_service(capy::execution_context& ctx); /** Destroy the resolver service. */ ~win_resolver_service(); @@ -580,8 +580,8 @@ win_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept // win_resolver_service inline win_resolver_service::win_resolver_service( - capy::execution_context& ctx, scheduler& sched) - : sched_(sched) + capy::execution_context& ctx) + : sched_(get_scheduler(ctx)) , pool_(ctx) { } diff --git a/include/boost/corosio/native/detail/iocp/win_scheduler.hpp b/include/boost/corosio/native/detail/iocp/win_scheduler.hpp index 57cf6c03d..c7ed01c0e 100644 --- a/include/boost/corosio/native/detail/iocp/win_scheduler.hpp +++ b/include/boost/corosio/native/detail/iocp/win_scheduler.hpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -53,10 +52,8 @@ class win_wait_reactor; class BOOST_COROSIO_DECL win_scheduler final : public scheduler - , public capy::execution_context::service { public: - using key_type = scheduler; win_scheduler(capy::execution_context& ctx, int concurrency_hint = -1); ~win_scheduler(); @@ -726,7 +723,6 @@ inline win_scheduler::win_scheduler( { timers_ = make_win_timers(iocp_, &dispatch_required_); set_timer_service(&get_timer_service(ctx, *this)); - ctx.make_service(*this); // A scheduler whose wait reactor could not be built would // answer every wait with a parked op, so it refuses to exist diff --git a/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp b/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp index 0c2ecc050..651de18ba 100644 --- a/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp @@ -47,8 +47,7 @@ class BOOST_COROSIO_DECL win_tcp_acceptor_service final public: using key_type = win_tcp_acceptor_service; - win_tcp_acceptor_service( - capy::execution_context& ctx, win_tcp_service& svc); + explicit win_tcp_acceptor_service(capy::execution_context& ctx); io_object::implementation* construct() override; @@ -1737,8 +1736,8 @@ win_tcp_acceptor::get_internal() const noexcept // win_tcp_acceptor_service inline win_tcp_acceptor_service::win_tcp_acceptor_service( - [[maybe_unused]] capy::execution_context& ctx, win_tcp_service& svc) - : svc_(svc) + capy::execution_context& ctx) + : svc_(ctx.use_service()) { } diff --git a/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp b/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp index bf02a605e..4471646d2 100644 --- a/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp +++ b/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp @@ -24,10 +24,6 @@ #include #include #include -#include -#include -#include -#include #include @@ -201,11 +197,6 @@ inline kqueue_scheduler::kqueue_scheduler(capy::execution_context& ctx, int) static_cast(p)->interrupt_reactor(); })); - get_resolver_service(ctx, *this); - get_signal_service(ctx, *this); - get_stream_file_service(ctx, *this); - get_random_access_file_service(ctx, *this); - completed_ops_.push(&task_op_); } diff --git a/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp b/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp index d980dcf42..4729fab1d 100644 --- a/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp @@ -30,9 +30,8 @@ class BOOST_COROSIO_DECL posix_random_access_file_service final : public random_access_file_service { public: - posix_random_access_file_service( - capy::execution_context& ctx, scheduler& sched) - : sched_(&sched) + explicit posix_random_access_file_service(capy::execution_context& ctx) + : sched_(&get_scheduler(ctx)) , pool_(ctx) { } @@ -152,13 +151,6 @@ class BOOST_COROSIO_DECL posix_random_access_file_service final file_ptrs_; }; -/** Get or create the random-access file service for the given context. */ -inline posix_random_access_file_service& -get_random_access_file_service(capy::execution_context& ctx, scheduler& sched) -{ - return ctx.make_service(sched); -} - // --------------------------------------------------------------------------- // posix_random_access_file inline implementations (require complete service) // --------------------------------------------------------------------------- diff --git a/include/boost/corosio/native/detail/posix/posix_resolver_service.hpp b/include/boost/corosio/native/detail/posix/posix_resolver_service.hpp index b11ca6e56..83b90230a 100644 --- a/include/boost/corosio/native/detail/posix/posix_resolver_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_resolver_service.hpp @@ -35,8 +35,8 @@ class BOOST_COROSIO_DECL posix_resolver_service final public: using key_type = posix_resolver_service; - posix_resolver_service(capy::execution_context& ctx, scheduler& sched) - : sched_(&sched) + explicit posix_resolver_service(capy::execution_context& ctx) + : sched_(&get_scheduler(ctx)) , pool_(ctx) { } @@ -95,18 +95,6 @@ class BOOST_COROSIO_DECL posix_resolver_service final resolver_ptrs_; }; -/** Get or create the resolver service for the given context. - - This function is called by the concrete scheduler during initialization - to create the resolver service with a reference to itself. - - @param ctx Reference to the owning execution_context. - @param sched Reference to the scheduler for posting completions. - @return Reference to the resolver service. -*/ -posix_resolver_service& -get_resolver_service(capy::execution_context& ctx, scheduler& sched); - // --------------------------------------------------------------------------- // Inline implementation // --------------------------------------------------------------------------- @@ -608,14 +596,6 @@ posix_resolver_service::post(scheduler_op* op) sched_->post(op); } -// Free function to get/create the resolver service - -inline posix_resolver_service& -get_resolver_service(capy::execution_context& ctx, scheduler& sched) -{ - return ctx.make_service(sched); -} - } // namespace boost::corosio::detail #endif // BOOST_COROSIO_POSIX diff --git a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp index 110b4b090..420b24df5 100644 --- a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp @@ -37,8 +37,8 @@ Concrete signal service implementation for POSIX backends. Manages signal registrations via sigaction() and dispatches completions through the - scheduler. One instance per execution_context, created by - get_signal_service(). + scheduler. One instance per execution_context, created on first use + by the public signal_set. See the block comment further down for the full architecture overview. */ @@ -157,7 +157,7 @@ class BOOST_COROSIO_DECL posix_signal_service final public: using key_type = posix_signal_service; - posix_signal_service(capy::execution_context& ctx, scheduler& sched); + explicit posix_signal_service(capy::execution_context& ctx); ~posix_signal_service() override; posix_signal_service(posix_signal_service const&) = delete; @@ -246,18 +246,6 @@ class BOOST_COROSIO_DECL posix_signal_service final posix_signal_service* prev_ = nullptr; }; -/** Get or create the signal service for the given context. - - This function is called by the concrete scheduler during initialization - to create the signal service with a reference to itself. - - @param ctx Reference to the owning execution_context. - @param sched Reference to the scheduler for posting completions. - @return Reference to the signal service. -*/ -posix_signal_service& -get_signal_service(capy::execution_context& ctx, scheduler& sched); - } // namespace detail } // namespace boost::corosio @@ -519,8 +507,8 @@ posix_signal::cancel() noexcept // posix_signal_service implementation inline posix_signal_service::posix_signal_service( - capy::execution_context&, scheduler& sched) - : sched_(&sched) + capy::execution_context& ctx) + : sched_(&get_scheduler(ctx)) { for (int i = 0; i < max_signal_number; ++i) { @@ -1048,14 +1036,6 @@ posix_signal_service::remove_service(posix_signal_service* service) } } -// get_signal_service - factory function - -inline posix_signal_service& -get_signal_service(capy::execution_context& ctx, scheduler& sched) -{ - return ctx.make_service(sched); -} - } // namespace detail } // namespace boost::corosio diff --git a/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp b/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp index 2f0358463..268143474 100644 --- a/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp @@ -32,8 +32,8 @@ namespace boost::corosio::detail { class BOOST_COROSIO_DECL posix_stream_file_service final : public file_service { public: - posix_stream_file_service(capy::execution_context& ctx, scheduler& sched) - : sched_(&sched) + explicit posix_stream_file_service(capy::execution_context& ctx) + : sched_(&get_scheduler(ctx)) , pool_(ctx) { } @@ -149,13 +149,6 @@ class BOOST_COROSIO_DECL posix_stream_file_service final : public file_service file_ptrs_; }; -/** Get or create the stream file service for the given context. */ -inline posix_stream_file_service& -get_stream_file_service(capy::execution_context& ctx, scheduler& sched) -{ - return ctx.make_service(sched); -} - // --------------------------------------------------------------------------- // posix_stream_file inline implementations (require complete service type) // --------------------------------------------------------------------------- diff --git a/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp b/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp index 09d1b21be..6b77c5193 100644 --- a/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp +++ b/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp @@ -107,10 +107,8 @@ reactor_find_context(reactor_scheduler const* self) noexcept */ class reactor_scheduler : public scheduler - , public capy::execution_context::service { public: - using key_type = scheduler; using context_type = reactor_scheduler_context; using mutex_type = conditionally_enabled_mutex; using lock_type = mutex_type::scoped_lock; diff --git a/include/boost/corosio/native/detail/reactor/reactor_service_finals.hpp b/include/boost/corosio/native/detail/reactor/reactor_service_finals.hpp index 32f3758a9..1d06d83f7 100644 --- a/include/boost/corosio/native/detail/reactor/reactor_service_finals.hpp +++ b/include/boost/corosio/native/detail/reactor/reactor_service_finals.hpp @@ -436,9 +436,11 @@ class reactor_acceptor_service_impl explicit reactor_acceptor_service_impl(capy::execution_context& ctx) : base_service(ctx) { - // Look up the concrete stream service directly by its type. + // The acceptor hands accepted fds to the stream service, so + // get-or-create it: with services created on first use it may + // not exist yet when the acceptor service comes up. this->stream_svc_ = - this->ctx_.template find_service(); + &this->ctx_.template use_service(); } public: diff --git a/include/boost/corosio/native/detail/select/select_scheduler.hpp b/include/boost/corosio/native/detail/select/select_scheduler.hpp index a1505c661..d1a8731d3 100644 --- a/include/boost/corosio/native/detail/select/select_scheduler.hpp +++ b/include/boost/corosio/native/detail/select/select_scheduler.hpp @@ -24,10 +24,6 @@ #include #include #include -#include -#include -#include -#include #include @@ -194,11 +190,6 @@ inline select_scheduler::select_scheduler(capy::execution_context& ctx, int) static_cast(p)->interrupt_reactor(); })); - get_resolver_service(ctx, *this); - get_signal_service(ctx, *this); - get_stream_file_service(ctx, *this); - get_random_access_file_service(ctx, *this); - completed_ops_.push(&task_op_); } diff --git a/include/boost/corosio/native/detail/uring/uring_random_access_file.hpp b/include/boost/corosio/native/detail/uring/uring_random_access_file.hpp index b5baaa446..773b049bf 100644 --- a/include/boost/corosio/native/detail/uring/uring_random_access_file.hpp +++ b/include/boost/corosio/native/detail/uring/uring_random_access_file.hpp @@ -314,8 +314,8 @@ class BOOST_COROSIO_DECL uring_random_access_file_service final public: explicit uring_random_access_file_service( - capy::execution_context& /*ctx*/, uring_scheduler& sched) - : base_service(sched) + capy::execution_context& ctx) + : base_service(ctx.use_service()) { } diff --git a/include/boost/corosio/native/detail/uring/uring_scheduler.hpp b/include/boost/corosio/native/detail/uring/uring_scheduler.hpp index 8425d0dfe..87c67b408 100644 --- a/include/boost/corosio/native/detail/uring/uring_scheduler.hpp +++ b/include/boost/corosio/native/detail/uring/uring_scheduler.hpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -119,10 +118,8 @@ extern thread_local uring_scheduler_frame* tl_running_scheduler_frame_; */ class BOOST_COROSIO_DECL uring_scheduler final : public scheduler - , public capy::execution_context::service { public: - using key_type = scheduler; using mutex_type = conditionally_enabled_mutex; using lock_type = mutex_type::scoped_lock; using event_type = conditionally_enabled_event; @@ -597,9 +594,6 @@ inline uring_scheduler::uring_scheduler( static_cast(p)->interrupt_reactor(); })); - get_resolver_service(ctx, *this); - get_signal_service(ctx, *this); - // Ring init is deferred so the options the io_context applies // after this constructor — the locking tier and SQPOLL — can feed // the flags io_uring_queue_init_params is given. The io_context diff --git a/include/boost/corosio/native/detail/uring/uring_stream_file.hpp b/include/boost/corosio/native/detail/uring/uring_stream_file.hpp index e0e80ae72..05bb2ccf6 100644 --- a/include/boost/corosio/native/detail/uring/uring_stream_file.hpp +++ b/include/boost/corosio/native/detail/uring/uring_stream_file.hpp @@ -330,9 +330,8 @@ class BOOST_COROSIO_DECL uring_stream_file_service final uring_stream_file>; public: - explicit uring_stream_file_service( - capy::execution_context& /*ctx*/, uring_scheduler& sched) - : base_service(sched) + explicit uring_stream_file_service(capy::execution_context& ctx) + : base_service(ctx.use_service()) { } diff --git a/include/boost/corosio/native/native_local_datagram_socket.hpp b/include/boost/corosio/native/native_local_datagram_socket.hpp index 31171062c..319a0413c 100644 --- a/include/boost/corosio/native/native_local_datagram_socket.hpp +++ b/include/boost/corosio/native/native_local_datagram_socket.hpp @@ -241,7 +241,7 @@ class native_local_datagram_socket : public local_datagram_socket @param ctx The execution context that owns this socket. */ explicit native_local_datagram_socket(capy::execution_context& ctx) - : local_datagram_socket(create_handle(ctx)) + : local_datagram_socket(handle(ctx, ctx.use_service())) { } diff --git a/include/boost/corosio/native/native_local_stream_acceptor.hpp b/include/boost/corosio/native/native_local_stream_acceptor.hpp index 605aa9d82..7e5402f5e 100644 --- a/include/boost/corosio/native/native_local_stream_acceptor.hpp +++ b/include/boost/corosio/native/native_local_stream_acceptor.hpp @@ -164,7 +164,7 @@ class native_local_stream_acceptor : public local_stream_acceptor @param ctx The execution context that owns this acceptor. */ explicit native_local_stream_acceptor(capy::execution_context& ctx) - : local_stream_acceptor(create_handle(ctx), ctx) + : local_stream_acceptor(handle(ctx, ctx.use_service()), ctx) { } diff --git a/include/boost/corosio/native/native_local_stream_socket.hpp b/include/boost/corosio/native/native_local_stream_socket.hpp index dba151926..6fef772f2 100644 --- a/include/boost/corosio/native/native_local_stream_socket.hpp +++ b/include/boost/corosio/native/native_local_stream_socket.hpp @@ -170,7 +170,7 @@ class native_local_stream_socket : public local_stream_socket @param ctx The execution context that owns this socket. */ explicit native_local_stream_socket(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle(ctx, ctx.use_service())) { } diff --git a/include/boost/corosio/native/native_random_access_file.hpp b/include/boost/corosio/native/native_random_access_file.hpp index 4ea06a27c..aa434b06f 100644 --- a/include/boost/corosio/native/native_random_access_file.hpp +++ b/include/boost/corosio/native/native_random_access_file.hpp @@ -137,7 +137,7 @@ class native_random_access_file : public random_access_file @param ctx The execution context that owns this file. */ explicit native_random_access_file(capy::execution_context& ctx) - : random_access_file(create_handle(ctx)) + : random_access_file(handle(ctx, ctx.use_service())) { } diff --git a/include/boost/corosio/native/native_stream_file.hpp b/include/boost/corosio/native/native_stream_file.hpp index c580930a5..496372b54 100644 --- a/include/boost/corosio/native/native_stream_file.hpp +++ b/include/boost/corosio/native/native_stream_file.hpp @@ -127,7 +127,7 @@ class native_stream_file : public stream_file @param ctx The execution context that owns this file. */ explicit native_stream_file(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle(ctx, ctx.use_service())) { } diff --git a/include/boost/corosio/native/native_tcp_acceptor.hpp b/include/boost/corosio/native/native_tcp_acceptor.hpp index 9077973b9..be754e036 100644 --- a/include/boost/corosio/native/native_tcp_acceptor.hpp +++ b/include/boost/corosio/native/native_tcp_acceptor.hpp @@ -151,7 +151,7 @@ class native_tcp_acceptor : public tcp_acceptor @param ctx The execution context that owns this acceptor. */ explicit native_tcp_acceptor(capy::execution_context& ctx) - : tcp_acceptor(create_handle(ctx)) + : tcp_acceptor(handle(ctx, ctx.use_service())) { } diff --git a/include/boost/corosio/native/native_tcp_socket.hpp b/include/boost/corosio/native/native_tcp_socket.hpp index 9ab417f70..955f4fec1 100644 --- a/include/boost/corosio/native/native_tcp_socket.hpp +++ b/include/boost/corosio/native/native_tcp_socket.hpp @@ -165,7 +165,7 @@ class native_tcp_socket : public tcp_socket @param ctx The execution context that owns this socket. */ explicit native_tcp_socket(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle(ctx, ctx.use_service())) { } diff --git a/include/boost/corosio/native/native_udp_socket.hpp b/include/boost/corosio/native/native_udp_socket.hpp index 60dadf4da..ad8a88f84 100644 --- a/include/boost/corosio/native/native_udp_socket.hpp +++ b/include/boost/corosio/native/native_udp_socket.hpp @@ -236,7 +236,7 @@ class native_udp_socket : public udp_socket @param ctx The execution context that owns this socket. */ explicit native_udp_socket(capy::execution_context& ctx) - : udp_socket(create_handle(ctx)) + : udp_socket(handle(ctx, ctx.use_service())) { } diff --git a/src/corosio/src/detail/use_backend_service.hpp b/src/corosio/src/detail/use_backend_service.hpp new file mode 100644 index 000000000..2f55f2188 --- /dev/null +++ b/src/corosio/src/detail/use_backend_service.hpp @@ -0,0 +1,145 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// 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) +// +// Official repository: https://github.com/cppalliance/corosio +// + +#ifndef BOOST_COROSIO_SRC_DETAIL_USE_BACKEND_SERVICE_HPP +#define BOOST_COROSIO_SRC_DETAIL_USE_BACKEND_SERVICE_HPP + +#include +#include +#include + +#include + +// Each enabled backend's service definitions must be complete here: +// use_service constructs T, and which T runs is decided at runtime. +// Only the services a translation unit actually instantiates generate +// code; completeness alone costs nothing at link time. +#if BOOST_COROSIO_HAS_IOCP +#include +#include +#include +#include +#include +#include +#endif +#if BOOST_COROSIO_HAS_EPOLL +#include +#endif +#if BOOST_COROSIO_HAS_KQUEUE +#include +#endif +#if BOOST_COROSIO_HAS_SELECT +#include +#endif +#if BOOST_COROSIO_POSIX +#include +#include +#endif +#if BOOST_COROSIO_HAS_URING +#include +#include +#include +#endif + +namespace boost::corosio::detail { + +// One trait per protocol so each public translation unit references +// only its own protocol's services. Reachability then follows the +// protocols a program names: a program that never names a protocol +// links none of that protocol's service code, for any backend. + +template +struct tcp_service_of +{ + using type = typename Tag::tcp_service_type; +}; + +template +struct tcp_acceptor_service_of +{ + using type = typename Tag::tcp_acceptor_service_type; +}; + +template +struct udp_service_of +{ + using type = typename Tag::udp_service_type; +}; + +template +struct local_stream_service_of +{ + using type = typename Tag::local_stream_service_type; +}; + +template +struct local_stream_acceptor_service_of +{ + using type = typename Tag::local_stream_acceptor_service_type; +}; + +template +struct local_datagram_service_of +{ + using type = typename Tag::local_datagram_service_type; +}; + +template +struct stream_file_service_of +{ + using type = typename Tag::stream_file_service_type; +}; + +template +struct random_access_file_service_of +{ + using type = typename Tag::random_access_file_service_type; +}; + +/** Get or create the backend-specific service registered under `Base`. + + The concrete service type comes from the backend tag's alias, + selected by probing which scheduler the context was constructed + with. The probe runs only on the first use of a protocol; after + that the service is found under its abstract key directly. + + @throws std::logic_error If the context has no backend installed. +*/ +template class SvcOf, class Base> +Base& +use_backend_service(capy::execution_context& ctx) +{ + if (auto* svc = ctx.find_service()) + return *svc; +#if BOOST_COROSIO_HAS_IOCP + if (ctx.find_service()) + return ctx.use_service::type>(); +#endif +#if BOOST_COROSIO_HAS_EPOLL + if (ctx.find_service()) + return ctx.use_service::type>(); +#endif +#if BOOST_COROSIO_HAS_URING + if (ctx.find_service()) + return ctx.use_service::type>(); +#endif +#if BOOST_COROSIO_HAS_KQUEUE + if (ctx.find_service()) + return ctx.use_service::type>(); +#endif +#if BOOST_COROSIO_HAS_SELECT + if (ctx.find_service()) + return ctx.use_service::type>(); +#endif + throw_logic_error("service not installed"); +} + +} // namespace boost::corosio::detail + +#endif diff --git a/src/corosio/src/io_context.cpp b/src/corosio/src/io_context.cpp index 1f9e9ace8..70527f19e 100644 --- a/src/corosio/src/io_context.cpp +++ b/src/corosio/src/io_context.cpp @@ -17,36 +17,23 @@ #include #if BOOST_COROSIO_HAS_EPOLL -#include +#include #endif #if BOOST_COROSIO_HAS_SELECT -#include +#include #endif #if BOOST_COROSIO_HAS_KQUEUE -#include +#include #endif #if BOOST_COROSIO_HAS_URING -#include -#include -#include -#include -#include #include -#include -#include #endif #if BOOST_COROSIO_HAS_IOCP #include -#include -#include -#include -#include -#include -#include #endif namespace boost::corosio { @@ -57,14 +44,6 @@ epoll_t::construct(capy::execution_context& ctx, unsigned concurrency_hint) { auto& sched = ctx.make_service( static_cast(concurrency_hint)); - - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - return sched; } #endif @@ -75,14 +54,6 @@ select_t::construct(capy::execution_context& ctx, unsigned concurrency_hint) { auto& sched = ctx.make_service( static_cast(concurrency_hint)); - - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - return sched; } #endif @@ -93,14 +64,6 @@ kqueue_t::construct(capy::execution_context& ctx, unsigned concurrency_hint) { auto& sched = ctx.make_service( static_cast(concurrency_hint)); - - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - return sched; } #endif @@ -111,17 +74,6 @@ iocp_t::construct(capy::execution_context& ctx, unsigned concurrency_hint) { auto& sched = ctx.make_service( static_cast(concurrency_hint)); - - auto& tcp_svc = ctx.make_service(); - ctx.make_service(tcp_svc); - ctx.make_service(); - auto& local_svc = - ctx.make_service(tcp_svc); - ctx.make_service(local_svc); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - return sched; } #endif @@ -132,16 +84,6 @@ uring_t::construct(capy::execution_context& ctx, unsigned concurrency_hint) { auto& sched = ctx.make_service( static_cast(concurrency_hint)); - - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(); - ctx.make_service(sched); - ctx.make_service(sched); - return sched; } #endif diff --git a/src/corosio/src/local_datagram_socket.cpp b/src/corosio/src/local_datagram_socket.cpp index 8a8bbf50d..471a23eff 100644 --- a/src/corosio/src/local_datagram_socket.cpp +++ b/src/corosio/src/local_datagram_socket.cpp @@ -14,6 +14,8 @@ #include #include #include + +#include "src/detail/use_backend_service.hpp" #include #include @@ -27,7 +29,11 @@ local_datagram_socket::~local_datagram_socket() } local_datagram_socket::local_datagram_socket(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::local_datagram_service_of, + detail::local_datagram_service>(ctx))) { } diff --git a/src/corosio/src/local_stream_acceptor.cpp b/src/corosio/src/local_stream_acceptor.cpp index 4a71fd49e..022f078c3 100644 --- a/src/corosio/src/local_stream_acceptor.cpp +++ b/src/corosio/src/local_stream_acceptor.cpp @@ -12,6 +12,8 @@ #include #include +#include "src/detail/use_backend_service.hpp" + #include #if BOOST_COROSIO_POSIX @@ -39,7 +41,11 @@ local_stream_acceptor::~local_stream_acceptor() } local_stream_acceptor::local_stream_acceptor(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::local_stream_acceptor_service_of, + detail::local_stream_acceptor_service>(ctx))) , ctx_(ctx) { } diff --git a/src/corosio/src/local_stream_socket.cpp b/src/corosio/src/local_stream_socket.cpp index 5775d54e3..19d9ab387 100644 --- a/src/corosio/src/local_stream_socket.cpp +++ b/src/corosio/src/local_stream_socket.cpp @@ -14,6 +14,8 @@ #include #include #include + +#include "src/detail/use_backend_service.hpp" #include #if BOOST_COROSIO_POSIX @@ -34,7 +36,11 @@ local_stream_socket::~local_stream_socket() } local_stream_socket::local_stream_socket(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::local_stream_service_of, + detail::local_stream_service>(ctx))) { } diff --git a/src/corosio/src/random_access_file.cpp b/src/corosio/src/random_access_file.cpp index 9f3760387..b3eb5b35d 100644 --- a/src/corosio/src/random_access_file.cpp +++ b/src/corosio/src/random_access_file.cpp @@ -17,6 +17,8 @@ #include #endif +#include "src/detail/use_backend_service.hpp" + namespace boost::corosio { random_access_file::~random_access_file() @@ -25,11 +27,16 @@ random_access_file::~random_access_file() } random_access_file::random_access_file(capy::execution_context& ctx) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::random_access_file_service_of, #if BOOST_COROSIO_HAS_IOCP - : io_object(create_handle(ctx)) + detail::win_random_access_file_service #else - : io_object(create_handle(ctx)) + detail::random_access_file_service #endif + >(ctx))) { } diff --git a/src/corosio/src/resolver.cpp b/src/corosio/src/resolver.cpp index f4a736375..f5ecdcc4b 100644 --- a/src/corosio/src/resolver.cpp +++ b/src/corosio/src/resolver.cpp @@ -48,7 +48,7 @@ using resolver_service = detail::posix_resolver_service; resolver::~resolver() = default; resolver::resolver(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle(ctx, ctx.use_service())) { } diff --git a/src/corosio/src/signal_set.cpp b/src/corosio/src/signal_set.cpp index ed61af7d9..94abd3192 100644 --- a/src/corosio/src/signal_set.cpp +++ b/src/corosio/src/signal_set.cpp @@ -59,7 +59,7 @@ get_signal_state() signal_set::~signal_set() = default; signal_set::signal_set(capy::execution_context& ctx) - : io_signal_set(create_handle(ctx)) + : io_signal_set(handle(ctx, ctx.use_service())) { } diff --git a/src/corosio/src/stream_file.cpp b/src/corosio/src/stream_file.cpp index ead93ffd6..1e2fbcb80 100644 --- a/src/corosio/src/stream_file.cpp +++ b/src/corosio/src/stream_file.cpp @@ -17,6 +17,8 @@ #include #endif +#include "src/detail/use_backend_service.hpp" + namespace boost::corosio { stream_file::~stream_file() @@ -25,11 +27,16 @@ stream_file::~stream_file() } stream_file::stream_file(capy::execution_context& ctx) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::stream_file_service_of, #if BOOST_COROSIO_HAS_IOCP - : io_object(create_handle(ctx)) + detail::win_file_service #else - : io_object(create_handle(ctx)) + detail::file_service #endif + >(ctx))) { } diff --git a/src/corosio/src/tcp_acceptor.cpp b/src/corosio/src/tcp_acceptor.cpp index 978c83b6f..2df6a9936 100644 --- a/src/corosio/src/tcp_acceptor.cpp +++ b/src/corosio/src/tcp_acceptor.cpp @@ -21,6 +21,8 @@ #include +#include "src/detail/use_backend_service.hpp" + namespace boost::corosio { #if BOOST_COROSIO_HAS_IOCP @@ -61,11 +63,16 @@ tcp_acceptor::~tcp_acceptor() } tcp_acceptor::tcp_acceptor(capy::execution_context& ctx) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::tcp_acceptor_service_of, #if BOOST_COROSIO_HAS_IOCP - : io_object(create_handle(ctx)) + detail::win_tcp_acceptor_service #else - : io_object(create_handle(ctx)) + detail::tcp_acceptor_service #endif + >(ctx))) { } diff --git a/src/corosio/src/tcp_socket.cpp b/src/corosio/src/tcp_socket.cpp index b3e647648..f01d9ae15 100644 --- a/src/corosio/src/tcp_socket.cpp +++ b/src/corosio/src/tcp_socket.cpp @@ -13,6 +13,8 @@ #include #include +#include "src/detail/use_backend_service.hpp" + #if BOOST_COROSIO_HAS_IOCP #include #else @@ -21,17 +23,26 @@ namespace boost::corosio { +namespace { + +#if BOOST_COROSIO_HAS_IOCP +using tcp_service_key = detail::win_tcp_service; +#else +using tcp_service_key = detail::tcp_service; +#endif + +} // namespace + tcp_socket::~tcp_socket() { close(); } tcp_socket::tcp_socket(capy::execution_context& ctx) -#if BOOST_COROSIO_HAS_IOCP - : io_object(create_handle(ctx)) -#else - : io_object(create_handle(ctx)) -#endif + : io_object(handle( + ctx, + detail::use_backend_service( + ctx))) { } diff --git a/src/corosio/src/udp_socket.cpp b/src/corosio/src/udp_socket.cpp index 66cd15005..b1f66dfbc 100644 --- a/src/corosio/src/udp_socket.cpp +++ b/src/corosio/src/udp_socket.cpp @@ -14,6 +14,8 @@ #include +#include "src/detail/use_backend_service.hpp" + namespace boost::corosio { udp_socket::~udp_socket() @@ -22,7 +24,11 @@ udp_socket::~udp_socket() } udp_socket::udp_socket(capy::execution_context& ctx) - : io_object(create_handle(ctx)) + : io_object(handle( + ctx, + detail::use_backend_service< + detail::udp_service_of, + detail::udp_service>(ctx))) { } diff --git a/test/unit/fault/iocp_faults.cpp b/test/unit/fault/iocp_faults.cpp index b041e75cc..8f4bbc238 100644 --- a/test/unit/fault/iocp_faults.cpp +++ b/test/unit/fault/iocp_faults.cpp @@ -425,12 +425,13 @@ struct iocp_faults void testTcpExtensionPointerMissing() { // load_extension_functions runs once, from the tcp service's - // constructor, so the arm has to precede the io_context. + // constructor, which runs when the first tcp object is + // created, so the arm has to precede the acceptor. fault_scope f(sys::WSAIoctl, WSAEOPNOTSUPP); io_context ioc(iocp); - BOOST_TEST(f.fired()); tcp_acceptor acc(ioc, loopback()); + BOOST_TEST(f.fired()); auto port = acc.local_endpoint().port(); tcp_socket s(ioc); BOOST_TEST(!s.open(family::v4)); @@ -1750,16 +1751,18 @@ struct iocp_faults */ void testExtensionPointersMissing() { - // The bootstrap's socket is the first WSASocketW of the - // construction; the wakeup pair uses ::socket. The arm is - // spent by the time the rest of the test runs. + // The bootstrap's socket is the first WSASocketW after the + // arm: services are created on first use, so it runs when the + // first tcp object is created, not at context construction. + // The wakeup pair uses ::socket. The arm is spent by the time + // the rest of the test runs. fault_scope arm(sys::WSASocketW, WSAEMFILE); io_context ioc(iocp); - BOOST_TEST(arm.fired()); temp_socket_dir dir; auto const ep = corosio::local_endpoint(dir.path()); tcp_acceptor acc(ioc, loopback()); + BOOST_TEST(arm.fired()); local_stream_acceptor lacc(ioc); BOOST_TEST(!lacc.open()); BOOST_TEST(!lacc.bind(ep, bind_option::unlink_existing)); diff --git a/test/unit/lazy_services.cpp b/test/unit/lazy_services.cpp new file mode 100644 index 000000000..0ccc6d802 --- /dev/null +++ b/test/unit/lazy_services.cpp @@ -0,0 +1,208 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// 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) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Services are created on first use, not at context construction. +// Linking a program that never names a protocol must not require that +// protocol's service code, so constructing an io_context must not +// instantiate any I/O service eagerly. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if BOOST_COROSIO_HAS_IOCP +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include "context.hpp" +#include "test_suite.hpp" + +namespace boost::corosio { + +namespace { + +// The service types the public objects look up, per platform. +#if BOOST_COROSIO_HAS_IOCP +using tcp_service_t = detail::win_tcp_service; +using tcp_acceptor_service_t = detail::win_tcp_acceptor_service; +using udp_service_t = detail::win_udp_service; +using local_stream_service_t = detail::win_local_stream_service; +using local_stream_acceptor_service_t = + detail::win_local_stream_acceptor_service; +using resolver_service_t = detail::win_resolver_service; +using signal_service_t = detail::win_signals; +using file_service_t = detail::win_file_service; +using random_access_file_service_t = + detail::win_random_access_file_service; +#else +using tcp_service_t = detail::tcp_service; +using tcp_acceptor_service_t = detail::tcp_acceptor_service; +using udp_service_t = detail::udp_service; +using local_stream_service_t = detail::local_stream_service; +using local_stream_acceptor_service_t = + detail::local_stream_acceptor_service; +using local_datagram_service_t = detail::local_datagram_service; +using resolver_service_t = detail::posix_resolver_service; +using signal_service_t = detail::posix_signal_service; +using file_service_t = detail::file_service; +using random_access_file_service_t = detail::random_access_file_service; +#endif + +} // namespace + +template +struct lazy_services_test +{ + void testConstructionCreatesNoIoServices() + { + io_context ioc(Backend); + + BOOST_TEST(ioc.template find_service() == nullptr); + BOOST_TEST( + ioc.template find_service() == nullptr); + BOOST_TEST(ioc.template find_service() == nullptr); + BOOST_TEST( + ioc.template find_service() == nullptr); + BOOST_TEST( + ioc.template find_service() == + nullptr); +#if !BOOST_COROSIO_HAS_IOCP + BOOST_TEST( + ioc.template find_service() == nullptr); +#endif + BOOST_TEST(ioc.template find_service() == nullptr); + BOOST_TEST(ioc.template find_service() == nullptr); + BOOST_TEST(ioc.template find_service() == nullptr); + BOOST_TEST( + ioc.template find_service() == + nullptr); + + // The scheduler's own infrastructure stays eager. + BOOST_TEST( + ioc.template find_service() != nullptr); + } + + void testObjectCreatesOnlyItsOwnService() + { + io_context ioc(Backend); + + tcp_socket sock(ioc); + BOOST_TEST(ioc.template find_service() != nullptr); + BOOST_TEST(ioc.template find_service() == nullptr); + BOOST_TEST( + ioc.template find_service() == nullptr); + } + + void testEachObjectCreatesItsService() + { + io_context ioc(Backend); + + tcp_acceptor acc(ioc); + BOOST_TEST( + ioc.template find_service() != nullptr); + + udp_socket us(ioc); + BOOST_TEST(ioc.template find_service() != nullptr); + + local_stream_socket ls(ioc); + BOOST_TEST( + ioc.template find_service() != nullptr); + + local_stream_acceptor la(ioc); + BOOST_TEST( + ioc.template find_service() != + nullptr); + +#if !BOOST_COROSIO_HAS_IOCP + local_datagram_socket ld(ioc); + BOOST_TEST( + ioc.template find_service() != nullptr); +#endif + + resolver res(ioc); + BOOST_TEST(ioc.template find_service() != nullptr); + + signal_set sigs(ioc); + BOOST_TEST(ioc.template find_service() != nullptr); + + stream_file sf(ioc); + BOOST_TEST(ioc.template find_service() != nullptr); + + random_access_file raf(ioc); + BOOST_TEST( + ioc.template find_service() != + nullptr); + } + + void testConcurrentFirstUse() + { + // Get-or-create must be safe when the first sockets are + // constructed from multiple threads at once. + io_context ioc(Backend); + + std::vector threads; + std::vector> socks(8); + for (std::size_t i = 0; i < socks.size(); ++i) + threads.emplace_back([&, i] { + socks[i] = std::make_unique(ioc); + }); + for (auto& t : threads) + t.join(); + + BOOST_TEST(ioc.template find_service() != nullptr); + for (auto& s : socks) + BOOST_TEST(s != nullptr); + } + + void run() + { + testConstructionCreatesNoIoServices(); + testObjectCreatesOnlyItsOwnService(); + testEachObjectCreatesItsService(); + testConcurrentFirstUse(); + } +}; + +COROSIO_BACKEND_TESTS(lazy_services_test, "boost.corosio.lazy_services") + +} // namespace boost::corosio