diff --git a/include/boost/corosio/native/detail/iocp/win_signal.hpp b/include/boost/corosio/native/detail/iocp/win_signal.hpp index 8d38aa3b..a0fe30c7 100644 --- a/include/boost/corosio/native/detail/iocp/win_signal.hpp +++ b/include/boost/corosio/native/detail/iocp/win_signal.hpp @@ -89,7 +89,8 @@ class win_signal final win_signals& svc_; signal_registration* signals_ = nullptr; signal_op pending_op_; - bool waiting_ = false; + bool waiting_ = false; + bool cancelled_ = false; public: explicit win_signal(win_signals& svc) noexcept; diff --git a/include/boost/corosio/native/detail/iocp/win_signals.hpp b/include/boost/corosio/native/detail/iocp/win_signals.hpp index aaf43b73..6563f058 100644 --- a/include/boost/corosio/native/detail/iocp/win_signals.hpp +++ b/include/boost/corosio/native/detail/iocp/win_signals.hpp @@ -598,6 +598,7 @@ win_signals::cancel_wait(win_signal& impl) { std::lock_guard lock(mutex_); + impl.cancelled_ = true; if (impl.waiting_) { was_waiting = true; @@ -621,32 +622,53 @@ win_signals::cancel_wait(win_signal& impl) inline void win_signals::start_wait(win_signal& impl, signal_op* op) { + bool was_cancelled = false; + { std::lock_guard lock(mutex_); - // Check for queued signals first - signal_registration* reg = impl.signals_; - while (reg) + // Check if cancel() was called before this wait started + if (impl.cancelled_) + { + was_cancelled = true; + impl.cancelled_ = false; + if (op->ec_out) + *op->ec_out = make_error_code(capy::error::canceled); + if (op->signal_out) + *op->signal_out = 0; + op->cont_op.cont.h = op->h; + } + else { - if (reg->undelivered > 0) + // Check for queued signals first + signal_registration* reg = impl.signals_; + while (reg) { - --reg->undelivered; - op->signal_number = reg->signal_number; - op->svc = nullptr; // No extra work_finished needed - // Post for immediate completion - post() handles work tracking - post(op); - return; + if (reg->undelivered > 0) + { + --reg->undelivered; + op->signal_number = reg->signal_number; + op->svc = nullptr; // No extra work_finished needed + // Post for immediate completion - post() handles work tracking + post(op); + return; + } + reg = reg->next_in_set; } - reg = reg->next_in_set; - } - // No queued signals, wait for delivery - // We call work_started() to keep io_context alive while waiting. - // Set svc so signal_op::operator() will call work_finished(). - impl.waiting_ = true; - op->svc = this; - sched_.work_started(); + // No queued signals, wait for delivery + // We call work_started() to keep io_context alive while waiting. + // Set svc so signal_op::operator() will call work_finished(). + impl.waiting_ = true; + op->svc = this; + sched_.work_started(); + } } + + // Dispatch outside the lock to avoid deadlock if the resumed + // coroutine re-enters cancel()/add()/remove() + if (was_cancelled) + dispatch_coro(op->d, op->cont_op.cont).resume(); } inline void diff --git a/include/boost/corosio/native/detail/posix/posix_signal.hpp b/include/boost/corosio/native/detail/posix/posix_signal.hpp index 55e97329..deb4fc4b 100644 --- a/include/boost/corosio/native/detail/posix/posix_signal.hpp +++ b/include/boost/corosio/native/detail/posix/posix_signal.hpp @@ -79,7 +79,8 @@ class posix_signal final posix_signal_service& svc_; signal_registration* signals_ = nullptr; signal_op pending_op_; - bool waiting_ = false; + bool waiting_ = false; + bool cancelled_ = false; public: explicit posix_signal(posix_signal_service& svc) noexcept; 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 bf8c805e..913cf4a4 100644 --- a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp @@ -641,6 +641,7 @@ posix_signal_service::cancel_wait(posix_signal& impl) { std::lock_guard lock(mutex_); + impl.cancelled_ = true; if (impl.waiting_) { was_waiting = true; @@ -667,6 +668,19 @@ posix_signal_service::start_wait(posix_signal& impl, signal_op* op) { std::lock_guard lock(mutex_); + // Check if cancel() was called before this wait started + if (impl.cancelled_) + { + impl.cancelled_ = false; + if (op->ec_out) + *op->ec_out = make_error_code(capy::error::canceled); + if (op->signal_out) + *op->signal_out = 0; + op->cont_op.cont.h = op->h; + op->d.post(op->cont_op.cont); + return; + } + // Check for queued signals first (signal arrived before wait started) signal_registration* reg = impl.signals_; while (reg) diff --git a/test/unit/signal_set.cpp b/test/unit/signal_set.cpp index 2e5555ef..1c0e6815 100644 --- a/test/unit/signal_set.cpp +++ b/test/unit/signal_set.cpp @@ -264,6 +264,32 @@ struct signal_set_test BOOST_TEST(result_ec == capy::cond::canceled); } + void testCancelBeforeWait() + { + io_context ioc(Backend); + signal_set s(ioc, SIGINT); + + bool completed = false; + std::error_code result_ec; + + auto wait_task = [](signal_set& s_ref, std::error_code& ec_out, + bool& done_out) -> capy::task<> { + auto [ec, signum] = co_await s_ref.wait(); + ec_out = ec; + done_out = true; + (void)signum; + }; + capy::run_async(ioc.get_executor())( + wait_task(s, result_ec, completed)); + + // Cancel before io_context::run() — coroutine hasn't reached wait() yet + s.cancel(); + + ioc.run(); + BOOST_TEST(completed); + BOOST_TEST(result_ec == capy::cond::canceled); + } + void testCancelNoWaiters() { io_context ioc(Backend); @@ -723,6 +749,7 @@ struct signal_set_test // Cancellation tests testCancel(); + testCancelBeforeWait(); testCancelNoWaiters(); testCancelMultipleTimes();