Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions doc/modules/ROOT/pages/4.guide/4i.signals.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,8 @@ A stop request cancels only the wait that is pending when it arrives. Unlike
a stop request that arrives between waits is discarded. The following
`wait()` starts uncancelled.

NOTE: On Windows the cancelled wait resumes inline, on the thread that
called `request_stop()`. On POSIX it always resumes on a thread running the
execution context.
A cancelled wait resumes on a thread running the execution context, even
when `request_stop()` is called from another thread.

== Use Cases

Expand Down
4 changes: 0 additions & 4 deletions include/boost/corosio/io/io_signal_set.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,6 @@ class BOOST_COROSIO_DECL io_signal_set : public io_object

This signal set must outlive the returned awaitable.

@note On Windows a stop request resumes the awaiting coroutine
inline, on the thread that called `request_stop()`. On POSIX
it always resumes on a thread running the execution context.

@return An awaitable that completes with `results<int>`.
Returns the signal number when a signal is delivered,
or an error code on failure.
Expand Down
3 changes: 2 additions & 1 deletion include/boost/corosio/native/detail/iocp/win_signals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@ win_signal::wait(
stop_cb_.emplace(token, token_canceller{this});

svc_.start_wait(*this, &pending_op_);
// cancellation resumes inline here too; only signal delivery is posted.
// Every completion -- delivery, cancel() or a stop request -- is
// posted, so the waiter never resumes inline from here.
return std::noop_coroutine();
}

Expand Down
43 changes: 43 additions & 0 deletions test/unit/signal_set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,48 @@ struct signal_set_test
}
}

// A stop request made on another thread completes the parked wait,
// but the waiter resumes on a thread running the context, never
// inline on the thread that called request_stop().
void testStopFromOtherThreadResumesOnContext()
{
io_context ioc(Backend);
signal_set s(ioc, SIGINT);

std::stop_source src;
std::atomic<bool> parked{false};
bool completed = false;
std::error_code result_ec = capy::error::eof;
std::thread::id resumed_on;
auto const run_thread = std::this_thread::get_id();

auto wait_task = [&]() -> capy::task<> {
parked.store(true, std::memory_order_release);
[[maybe_unused]] auto [ec, signum] = co_await s.wait();
resumed_on = std::this_thread::get_id();
result_ec = ec;
completed = true;
};
capy::run_async(ioc.get_executor(), src.get_token())(wait_task());

std::thread stopper([&] {
while (!parked.load(std::memory_order_acquire))
std::this_thread::yield();
// Give the wait time to park so the stop takes the
// cancel-a-parked-wait path rather than the pre-park one.
std::this_thread::sleep_for(std::chrono::milliseconds(50));
src.request_stop();
});

// Bounded so a lost wakeup is a red assertion, not a hang.
std::ignore = ioc.run_for(std::chrono::seconds(5));
stopper.join();

BOOST_TEST(completed);
BOOST_TEST(result_ec == capy::cond::canceled);
BOOST_TEST(resumed_on == run_thread);
}

void testShutdownWithPendingSignalSet()
{
// A set destroyed in the documented order gives its registrations
Expand Down Expand Up @@ -1617,6 +1659,7 @@ struct signal_set_test
testNormalDeliveryWithLiveToken();
testDestroyWithArmedStopToken();
testConcurrentStopRequestRace();
testStopFromOtherThreadResumesOnContext();
testStopRacingTeardown();
testShutdownWithPendingSignalSet();

Expand Down
Loading