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