Skip to content

Commit a319ffc

Browse files
committed
merge branch 'refactor-contexts': refactor execution contexts
1 parent 4221fe8 commit a319ffc

36 files changed

Lines changed: 2783 additions & 1797 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/bin/
99
/build/
1010
/out/
11+
/site/
1112

1213
# unrelated files
1314
CMakeSettings.json
@@ -30,4 +31,4 @@ compile_commands.json
3031
*.a
3132
*.dll
3233
*.so
33-
*.dylib
34+
*.dylib

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ A C++ **asynchronous I/O** library based on [sender/receiver model](https://wg21
1919

2020
- **Sender/Receiver model** — Composable asynchronous algorithms via `std::execution`
2121
- **Coroutine types**`task<T, Allocator, Scheduler>` and `generator<Ref, Val, Allocator>` for async computations and lazy sequences
22-
- **Execution contexts**`time_loop`, `epoll_context`, `uring_context` and `iocp_context` with thread-safe `run()`
22+
- **Execution contexts**`time_loop`, `epoll_context`, `uring_context` and `iocp_context`
2323
- **Networking** — TCP/UDP sockets with sync and async operations
2424
- **Synchronization**`async_mutex`, `async_semaphore`, `async_latch`
2525
- **Utilities** — Timers, concurrent queues, signal handling
2626

2727
> [!NOTE]
28-
> Some network and async-io facilities are currently only implemented using epoll and io_uring on Linux, and IOCP on Windows.
28+
> Some network and async-io facilities are currently only implemented using epoll and io_uring on Linux (the io_uring backend requires kernel 5.19 or newer), and IOCP on Windows.
2929
3030
## Build and Install
3131

docs/reference.md

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ This document describes the public API of **coio**. It is intended to be a stabl
4343
### Stop tokens and cancellation
4444

4545
- Many async operations support cooperative cancellation by [stop token](https://eel.is/c++draft/thread.stoptoken).
46-
- Cancellation follows the sender/receiver contract: cancellation completes with `set_stopped()`.
46+
- A stop request asks the backend to cancel; it does not overwrite an actual target-operation result.
47+
- The receiver gets `set_stopped()` only when the cancellation path wins, for example when a pending operation is synchronously removed or the target backend reports cancellation. A target success or ordinary error remains `set_value()` or `set_error()` even if stop was requested first.
48+
- A stop request that already exists at `start()` does not skip backend initiation. An immediate target result wins; if the operation remains pending, coio then asks the backend to cancel it.
4749

4850
---
4951

@@ -131,14 +133,12 @@ auto fibonacci(std::size_t n) -> coio::generator<int> {
131133
132134
### 4.1 Thread-safety model
133135
134-
All execution contexts (`time_loop`, `epoll_context`, `uring_context` and `iocp_context`) share these guarantees:
136+
All execution contexts (`time_loop`, `epoll_context`, `uring_context` and `iocp_context`) are **MPSC** (multi-producer, single-consumer):
135137
136-
- `run()` / `run_one()` can be called concurrently from multiple threads.
137-
- `poll()` / `poll_one()` can be called concurrently from multiple threads.
138-
- `get_scheduler()` is thread-safe.
139-
- `request_stop()` is thread-safe.
138+
- **Multi-producer**: any thread may concurrently start operations on a context (`schedule()`, `schedule_at()`/`schedule_after()`, and the `async_*` operations of its io objects), and `get_scheduler()`, `work_started()`/`work_finished()` and `request_stop()` are thread-safe.
139+
- **Single-consumer**: at most one thread may be inside `run()`, `run_one()`, `poll()` or `poll_one()` for a context at a time. This is a precondition and is not checked at runtime: concurrent consumer calls are undefined behavior. The consumer thread may change over the context's lifetime (e.g. `poll()` from one thread, later `run()` from another), provided the earlier call happens-before the later one (thread join, mutex, or similar synchronization).
140140
141-
Work submitted to the context may be executed by **any** thread currently calling `run()`/`poll()`.
141+
Work submitted to the context is completed by its active `run()`/`poll()` consumer thread.
142142
143143
### 4.2 time_loop
144144
@@ -283,8 +283,8 @@ Be careful with the term "concurrency":
283283
284284
Like Asio sockets/streams, coio socket/acceptor objects are **not thread-safe**. In other words, **member functions must not be called concurrently** on the same socket/acceptor from multiple threads unless you provide external synchronization.
285285
286-
If you drive the owning execution context from a single thread, and ensure all socket/acceptor
287-
operations are initiated from work running on that thread, that thread acts as an
286+
If all socket/acceptor operations are initiated from work running on the owning execution
287+
context's consumer thread, that thread acts as an
288288
"implicit strand" (operations are serialized by construction).
289289
290290
This thread-safety rule is independent of how many operations may be outstanding.
@@ -295,6 +295,13 @@ The async interface supports the following **outstanding-operation** limits (Asi
295295
- **Not allowed**: two reads outstanding simultaneously; likewise for writes.
296296
- **Acceptors**: at most one outstanding `accept` / `async_accept` per acceptor.
297297
298+
**Lifetime**: an I/O object must outlive all of its operations. A sender obtained from an
299+
I/O object (`async_read_some`, `async_receive`, ...) must be connected and started **before**
300+
the object is closed or destroyed; starting it afterwards is undefined behavior. On
301+
`epoll_context` in particular, `close()` returns the object's per-descriptor bookkeeping
302+
entry to an internal pool, so a stale start may silently corrupt the state of an unrelated
303+
I/O object that has since reused the entry, rather than failing cleanly with `EBADF`.
304+
298305
Example: the following is **malformed** because it starts two reads without waiting for the first to complete:
299306
300307
```cpp
@@ -315,9 +322,9 @@ co_await when_all(
315322
);
316323
```
317324

318-
If you drive an execution context from multiple threads, ensure **all initiating calls for a
319-
given socket/acceptor are serialized** (e.g. a mutex, or funneling initiation through a single
320-
owning thread/task).
325+
Operations may be initiated from threads other than the context consumer, but **all initiating
326+
calls for a given socket/acceptor must still be serialized** (e.g. a mutex, or funneling
327+
initiation through a single owning thread/task).
321328

322329
### EOF behavior
323330

@@ -347,7 +354,7 @@ These primitives suspend coroutines instead of blocking threads.
347354

348355
## 11. Thread safety (summary)
349356

350-
- Execution contexts: thread-safe `run/poll/get_scheduler/request_stop`.
357+
- Execution contexts: **MPSC** — one active `run/poll` consumer per context; `get_scheduler`, operation initiation, and `request_stop` may be used concurrently from other threads.
351358
- Sync primitives: safe across coroutines potentially running on different threads.
352359
- `async_scope`: safe to `spawn()` from multiple threads.
353-
- Sockets/acceptors: **not thread-safe**; do not call member functions concurrently on the same object without external synchronization. Also follow the per-object outstanding-operation limits described above.
360+
- Sockets/acceptors: **not thread-safe**; do not call member functions concurrently on the same object without external synchronization. Also follow the per-object outstanding-operation limits described above.

examples/multi-threading.cpp

Lines changed: 0 additions & 31 deletions
This file was deleted.

examples/tcp_echo_server-thread_pool.cpp

Lines changed: 0 additions & 100 deletions
This file was deleted.

0 commit comments

Comments
 (0)