Skip to content

Commit 17fa5e5

Browse files
committed
Fix signal_set::cancel() having no effect when called before wait()
cancel() was a no-op if no coroutine had yet suspended in wait(), because cancel_wait() only checked the waiting_ flag. This meant calling cancel() before io_context::run() — or before the coroutine reached co_await — silently lost the cancellation, causing run() to block indefinitely. Add a cancelled_ latch to posix_signal and win_signal. cancel_wait() now sets it unconditionally; start_wait() checks and clears it before blocking, posting an immediate cancelled completion when set.
1 parent 8de6718 commit 17fa5e5

File tree

5 files changed

+85
-20
lines changed

5 files changed

+85
-20
lines changed

include/boost/corosio/native/detail/iocp/win_signal.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ class win_signal final
8989
win_signals& svc_;
9090
signal_registration* signals_ = nullptr;
9191
signal_op pending_op_;
92-
bool waiting_ = false;
92+
bool waiting_ = false;
93+
bool cancelled_ = false;
9394

9495
public:
9596
explicit win_signal(win_signals& svc) noexcept;

include/boost/corosio/native/detail/iocp/win_signals.hpp

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ win_signals::cancel_wait(win_signal& impl)
598598

599599
{
600600
std::lock_guard<win_mutex> lock(mutex_);
601+
impl.cancelled_ = true;
601602
if (impl.waiting_)
602603
{
603604
was_waiting = true;
@@ -621,32 +622,53 @@ win_signals::cancel_wait(win_signal& impl)
621622
inline void
622623
win_signals::start_wait(win_signal& impl, signal_op* op)
623624
{
625+
bool was_cancelled = false;
626+
624627
{
625628
std::lock_guard<win_mutex> lock(mutex_);
626629

627-
// Check for queued signals first
628-
signal_registration* reg = impl.signals_;
629-
while (reg)
630+
// Check if cancel() was called before this wait started
631+
if (impl.cancelled_)
632+
{
633+
was_cancelled = true;
634+
impl.cancelled_ = false;
635+
if (op->ec_out)
636+
*op->ec_out = make_error_code(capy::error::canceled);
637+
if (op->signal_out)
638+
*op->signal_out = 0;
639+
op->cont_op.cont.h = op->h;
640+
}
641+
else
630642
{
631-
if (reg->undelivered > 0)
643+
// Check for queued signals first
644+
signal_registration* reg = impl.signals_;
645+
while (reg)
632646
{
633-
--reg->undelivered;
634-
op->signal_number = reg->signal_number;
635-
op->svc = nullptr; // No extra work_finished needed
636-
// Post for immediate completion - post() handles work tracking
637-
post(op);
638-
return;
647+
if (reg->undelivered > 0)
648+
{
649+
--reg->undelivered;
650+
op->signal_number = reg->signal_number;
651+
op->svc = nullptr; // No extra work_finished needed
652+
// Post for immediate completion - post() handles work tracking
653+
post(op);
654+
return;
655+
}
656+
reg = reg->next_in_set;
639657
}
640-
reg = reg->next_in_set;
641-
}
642658

643-
// No queued signals, wait for delivery
644-
// We call work_started() to keep io_context alive while waiting.
645-
// Set svc so signal_op::operator() will call work_finished().
646-
impl.waiting_ = true;
647-
op->svc = this;
648-
sched_.work_started();
659+
// No queued signals, wait for delivery
660+
// We call work_started() to keep io_context alive while waiting.
661+
// Set svc so signal_op::operator() will call work_finished().
662+
impl.waiting_ = true;
663+
op->svc = this;
664+
sched_.work_started();
665+
}
649666
}
667+
668+
// Dispatch outside the lock to avoid deadlock if the resumed
669+
// coroutine re-enters cancel()/add()/remove()
670+
if (was_cancelled)
671+
dispatch_coro(op->d, op->cont_op.cont).resume();
650672
}
651673

652674
inline void

include/boost/corosio/native/detail/posix/posix_signal.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class posix_signal final
7979
posix_signal_service& svc_;
8080
signal_registration* signals_ = nullptr;
8181
signal_op pending_op_;
82-
bool waiting_ = false;
82+
bool waiting_ = false;
83+
bool cancelled_ = false;
8384

8485
public:
8586
explicit posix_signal(posix_signal_service& svc) noexcept;

include/boost/corosio/native/detail/posix/posix_signal_service.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ posix_signal_service::cancel_wait(posix_signal& impl)
641641

642642
{
643643
std::lock_guard lock(mutex_);
644+
impl.cancelled_ = true;
644645
if (impl.waiting_)
645646
{
646647
was_waiting = true;
@@ -667,6 +668,19 @@ posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
667668
{
668669
std::lock_guard lock(mutex_);
669670

671+
// Check if cancel() was called before this wait started
672+
if (impl.cancelled_)
673+
{
674+
impl.cancelled_ = false;
675+
if (op->ec_out)
676+
*op->ec_out = make_error_code(capy::error::canceled);
677+
if (op->signal_out)
678+
*op->signal_out = 0;
679+
op->cont_op.cont.h = op->h;
680+
op->d.post(op->cont_op.cont);
681+
return;
682+
}
683+
670684
// Check for queued signals first (signal arrived before wait started)
671685
signal_registration* reg = impl.signals_;
672686
while (reg)

test/unit/signal_set.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,32 @@ struct signal_set_test
264264
BOOST_TEST(result_ec == capy::cond::canceled);
265265
}
266266

267+
void testCancelBeforeWait()
268+
{
269+
io_context ioc(Backend);
270+
signal_set s(ioc, SIGINT);
271+
272+
bool completed = false;
273+
std::error_code result_ec;
274+
275+
auto wait_task = [](signal_set& s_ref, std::error_code& ec_out,
276+
bool& done_out) -> capy::task<> {
277+
auto [ec, signum] = co_await s_ref.wait();
278+
ec_out = ec;
279+
done_out = true;
280+
(void)signum;
281+
};
282+
capy::run_async(ioc.get_executor())(
283+
wait_task(s, result_ec, completed));
284+
285+
// Cancel before io_context::run() — coroutine hasn't reached wait() yet
286+
s.cancel();
287+
288+
ioc.run();
289+
BOOST_TEST(completed);
290+
BOOST_TEST(result_ec == capy::cond::canceled);
291+
}
292+
267293
void testCancelNoWaiters()
268294
{
269295
io_context ioc(Backend);
@@ -723,6 +749,7 @@ struct signal_set_test
723749

724750
// Cancellation tests
725751
testCancel();
752+
testCancelBeforeWait();
726753
testCancelNoWaiters();
727754
testCancelMultipleTimes();
728755

0 commit comments

Comments
 (0)