From 4e163b8ac1d69fa77de6734e04bd0f05cb2f2a35 Mon Sep 17 00:00:00 2001 From: Christopher Kohlhoff Date: Wed, 2 Mar 2022 21:50:26 +1100 Subject: [PATCH] Add cancellation_slot support to signal sets. --- doc/overview/cancellation.qbk | 1 + include/boost/asio/basic_signal_set.hpp | 10 +++++ .../asio/detail/impl/signal_set_service.ipp | 27 ++++++++++++ include/boost/asio/detail/signal_op.hpp | 4 ++ .../boost/asio/detail/signal_set_service.hpp | 42 +++++++++++++++++++ 5 files changed, 84 insertions(+) diff --git a/doc/overview/cancellation.qbk b/doc/overview/cancellation.qbk index 474214d3..0d2ef034 100644 --- a/doc/overview/cancellation.qbk +++ b/doc/overview/cancellation.qbk @@ -229,6 +229,7 @@ operations, or composed operations, is currently supported by: * sockets on POSIX and Windows * POSIX descriptors * Windows HANDLEs +* signal sets * SSL streams * all Boost.Asio-provided composed operations such as `async_read` and `async_write` * compositions based on `async_compose` diff --git a/include/boost/asio/basic_signal_set.hpp b/include/boost/asio/basic_signal_set.hpp index f6fba13d..df6f48a6 100644 --- a/include/boost/asio/basic_signal_set.hpp +++ b/include/boost/asio/basic_signal_set.hpp @@ -519,6 +519,16 @@ public: * * @par Completion Signature * @code void(boost::system::error_code, int) @endcode + * + * @par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following + * boost::asio::cancellation_type values: + * + * @li @c cancellation_type::terminal + * + * @li @c cancellation_type::partial + * + * @li @c cancellation_type::total */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code, int)) diff --git a/include/boost/asio/detail/impl/signal_set_service.ipp b/include/boost/asio/detail/impl/signal_set_service.ipp index 51d0211d..c9061ee9 100644 --- a/include/boost/asio/detail/impl/signal_set_service.ipp +++ b/include/boost/asio/detail/impl/signal_set_service.ipp @@ -528,6 +528,33 @@ boost::system::error_code signal_set_service::cancel( return ec; } +void signal_set_service::cancel_ops_by_key( + signal_set_service::implementation_type& impl, void* cancellation_key) +{ + op_queue ops; + { + op_queue other_ops; + signal_state* state = get_signal_state(); + static_mutex::scoped_lock lock(state->mutex_); + + while (signal_op* op = impl.queue_.front()) + { + impl.queue_.pop(); + if (op->cancellation_key_ == cancellation_key) + { + op->ec_ = boost::asio::error::operation_aborted; + ops.push(op); + } + else + other_ops.push(op); + } + + impl.queue_.push(other_ops); + } + + scheduler_.post_deferred_completions(ops); +} + void signal_set_service::deliver_signal(int signal_number) { signal_state* state = get_signal_state(); diff --git a/include/boost/asio/detail/signal_op.hpp b/include/boost/asio/detail/signal_op.hpp index 0edb90fe..0e12dc5b 100644 --- a/include/boost/asio/detail/signal_op.hpp +++ b/include/boost/asio/detail/signal_op.hpp @@ -31,12 +31,16 @@ public: // The error code to be passed to the completion handler. boost::system::error_code ec_; + // The operation key used for targeted cancellation. + void* cancellation_key_; + // The signal number to be passed to the completion handler. int signal_number_; protected: signal_op(func_type func) : operation(func), + cancellation_key_(0), signal_number_(0) { } diff --git a/include/boost/asio/detail/signal_set_service.hpp b/include/boost/asio/detail/signal_set_service.hpp index 547535ca..704861c3 100644 --- a/include/boost/asio/detail/signal_set_service.hpp +++ b/include/boost/asio/detail/signal_set_service.hpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -154,17 +156,31 @@ public: BOOST_ASIO_DECL boost::system::error_code cancel(implementation_type& impl, boost::system::error_code& ec); + // Cancel a specific operation associated with the signal set. + BOOST_ASIO_DECL void cancel_ops_by_key(implementation_type& impl, + void* cancellation_key); + // Start an asynchronous operation to wait for a signal to be delivered. template void async_wait(implementation_type& impl, Handler& handler, const IoExecutor& io_ex) { + typename associated_cancellation_slot::type slot + = boost::asio::get_associated_cancellation_slot(handler); + // Allocate and construct an operation to wrap the handler. typedef signal_handler op; typename op::ptr p = { boost::asio::detail::addressof(handler), op::ptr::allocate(handler), 0 }; p.p = new (p.v) op(handler, io_ex); + // Optionally register for per-operation cancellation. + if (slot.is_connected()) + { + p.p->cancellation_key_ = + &slot.template emplace(this, &impl); + } + BOOST_ASIO_HANDLER_CREATION((scheduler_.context(), *p.p, "signal_set", &impl, 0, "async_wait")); @@ -191,6 +207,32 @@ private: // Helper function to start a wait operation. BOOST_ASIO_DECL void start_wait_op(implementation_type& impl, signal_op* op); + // Helper class used to implement per-operation cancellation + class signal_op_cancellation + { + public: + signal_op_cancellation(signal_set_service* s, implementation_type* i) + : service_(s), + implementation_(i) + { + } + + void operator()(cancellation_type_t type) + { + if (!!(type & + (cancellation_type::terminal + | cancellation_type::partial + | cancellation_type::total))) + { + service_->cancel_ops_by_key(*implementation_, this); + } + } + + private: + signal_set_service* service_; + implementation_type* implementation_; + }; + // The scheduler used for dispatching handlers. #if defined(BOOST_ASIO_HAS_IOCP) typedef class win_iocp_io_context scheduler_impl;