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
29 changes: 29 additions & 0 deletions doc/modules/ROOT/pages/4.guide/4c.io-context.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,35 @@ work across threads.
WARNING: Individual I/O objects (sockets, timers) are not thread-safe.
Don't access the same socket from multiple threads without synchronization.

== Lifetime and Teardown

The `io_context` must outlive every operation posted or dispatched through its
executor, and no thread may still be inside a `run()` call when the context is
destroyed. Posting to the context — from a thread it does not track —
concurrently with, or after, its destruction is undefined behavior.

Work launched with `capy::run` / `capy::run_async` is work-tracked, so a normal
`run()` completion already waits for it to finish. The safe teardown pattern is
therefore: stop submitting new work, let every `run()` return, join the threads
that ran the loop, and only then destroy the context.

[source,cpp]
----
corosio::io_context ioc(4);

std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
threads.emplace_back([&ioc] { ioc.run(); });

// Join every run() thread before ioc leaves scope.
for (auto& t : threads)
t.join();
----

WARNING: `stop()` abandons pending work rather than draining it. Destroying the
context after `stop()` while other threads may still post to it is undefined
behavior — join those threads first.

== Inheritance from execution_context

`io_context` inherits from `capy::execution_context`, providing service
Expand Down
27 changes: 27 additions & 0 deletions include/boost/corosio/io_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ class timer_service;
io_context ioc2(corosio::epoll); // explicit backend
@endcode

@par Preconditions
The context must outlive every operation posted or dispatched
through its executor, and no thread may be executing a run
variant when the context is destroyed. Posting to the context
concurrently with, or after, its destruction is undefined
behavior. The safe teardown pattern is to stop submitting new
work, let every `run()` call return (each returns once no
outstanding work remains), and join the threads that ran the
loop before destroying the context. Work launched with
`capy::run` / `capy::run_async` is work-tracked, so a normal
`run()` completion already waits for it.

@par Thread Safety
Distinct objects: Safe.@n
Shared objects: Safe, if using a concurrency hint greater
Expand Down Expand Up @@ -572,6 +584,11 @@ class io_context::executor_type
@param c The continuation to dispatch.

@return A handle for symmetric transfer or `std::noop_coroutine()`.

@par Preconditions
The associated context must outlive this call. Dispatching
concurrently with, or after, the context's destruction is
undefined behavior.
*/
std::coroutine_handle<> dispatch(capy::continuation& c) const
{
Expand All @@ -585,6 +602,11 @@ class io_context::executor_type

Enqueues `c` directly on the scheduler's ready queue.
No heap allocation occurs.

@par Preconditions
The associated context must outlive this call. Posting
concurrently with, or after, the context's destruction is
undefined behavior.
*/
void post(capy::continuation& c) const
{
Expand All @@ -598,6 +620,11 @@ class io_context::executor_type
the `post(scheduler_op*)` overload to avoid the allocation.

@param h The coroutine handle to post.

@par Preconditions
The associated context must outlive this call. Posting
concurrently with, or after, the context's destruction is
undefined behavior.
*/
void post(std::coroutine_handle<> h) const
{
Expand Down
Loading