1717
1818> [ !IMPORTANT]
1919> CMP provides a lazy, single-consumer ` Task<T> ` / ` Task<void> ` , structured variadic and vector
20- > ` when_all() ` , and a caller-thread ` RunLoop ` with explicit and monotonic timed scheduling. Timed
21- > waits support cooperative cancellation with ` std::stop_token ` ; asynchronous I/O and detached
22- > execution are not implemented.
20+ > ` when_all() ` , an eager structured ` TaskGroup ` , and a caller-thread ` RunLoop ` with explicit and
21+ > monotonic timed scheduling. Timed waits and TaskGroup children can use explicit cooperative
22+ > cancellation with ` std::stop_token ` ; asynchronous I/O and detached execution are not implemented.
2323
2424CMP is being built as a modern coroutine runtime and library on standard stackless C++
25- coroutines. Its explicit ` co_await ` model now covers structured concurrent joins, scheduling ,
26- monotonic timers, and cancellable timed waits and can grow, in small verified steps, toward
27- asynchronous I/O and safe handling of blocking work.
25+ coroutines. Its explicit ` co_await ` model now covers fixed and incremental structured concurrency ,
26+ scheduling, monotonic timers, and cancellable timed waits and can grow, in small verified steps,
27+ toward asynchronous I/O and safe handling of blocking work.
2828
2929## Why CMP?
3030
@@ -74,10 +74,10 @@ mcpp run
7474```
7575
7676The example prints ` Coroutine result: 42 ` , joins two timed Tasks and prints
77- ` Concurrent result: 42 ` , then prints ` Coroutine cancelled ` from a pre-cancelled timed wait. All
78- messages come from ` Task<void> ` coroutines. It proves that an independent mcpp package can import
79- ` mcpplibs.cmp ` , compose and join Tasks, and use timed scheduling and cancellation through the
80- public RunLoop.
77+ ` Concurrent result: 42 ` , eagerly spawns two scoped Tasks and prints ` Task group result: 42 ` , then
78+ prints ` Coroutine cancelled ` from a pre-cancelled timed wait. All messages come from ` Task<void> `
79+ coroutines. It proves that an independent mcpp package can import ` mcpplibs.cmp ` , compose, spawn,
80+ and join Tasks, and use timed scheduling and cancellation through the public RunLoop.
8181
8282## Current API
8383
@@ -88,6 +88,7 @@ import mcpplibs.cmp;
8888using mcpplibs::cmp::Task;
8989using mcpplibs::cmp::RunLoop;
9090using mcpplibs::cmp::OperationCancelled;
91+ using mcpplibs::cmp::TaskGroup;
9192using mcpplibs::cmp::when_all;
9293
9394using namespace std ::chrono_literals;
@@ -120,6 +121,26 @@ Task<void> print_concurrent_results(RunLoop::Scheduler scheduler) {
120121 co_return;
121122}
122123
124+ Task<void > add_delayed(
125+ RunLoop::Scheduler scheduler,
126+ std::chrono::milliseconds delay,
127+ int value,
128+ int& total) {
129+ co_await scheduler.schedule_after(delay);
130+ total += value;
131+ co_return;
132+ }
133+
134+ Task<void > print_task_group(RunLoop::Scheduler scheduler) {
135+ int total { 0 };
136+ TaskGroup group {};
137+ group.spawn(add_delayed(scheduler, 10ms, 20, total));
138+ group.spawn(add_delayed(scheduler, 1ms, 22, total));
139+ co_await group.join();
140+ std::println("Task group result: {}", total);
141+ co_return;
142+ }
143+
123144Task<void > print_cancellation(RunLoop::Scheduler scheduler, std::stop_token token) {
124145 try {
125146 co_await scheduler.schedule_after(1s, token);
@@ -133,6 +154,7 @@ int main() {
133154 RunLoop loop {};
134155 loop.run(print_answer(loop.get_scheduler()));
135156 loop.run(print_concurrent_results(loop.get_scheduler()));
157+ loop.run(print_task_group(loop.get_scheduler()));
136158
137159 std::stop_source source {};
138160 source.request_stop();
@@ -154,6 +176,13 @@ parameter order is rethrown. Named Tasks must be moved into `when_all()`. A runt
154176homogeneous collection can be passed as `std::vector<Task<T>>`; it returns a result vector in the
155177same index order, and a named input vector must also be moved.
156178
179+ `TaskGroup::spawn()` takes ownership of a `Task<void>` and starts it immediately. Awaiting the
180+ single-use `join()` closes admission and waits for every accepted child before rethrowing the first
181+ exception in admission order. The group must be joined before destruction. `get_stop_token()` and
182+ `request_stop()` provide one explicit standard cancellation channel; the token is not injected
183+ automatically, so developers pass it to children that support cancellation. Use `when_all()` when
184+ child results must be returned.
185+
157186A translation unit that defines a coroutine must import `std` so the compiler can see the standard
158187coroutine protocol types. CMP imports `std` privately and does not re-export the whole standard
159188library.
@@ -186,9 +215,11 @@ await the desired Scheduler to return to its RunLoop.
186215├── src/task.cppm # Task module partition
187216├── src/run_loop.cppm # RunLoop and Scheduler partition
188217├── src/when_all.cppm # structured concurrent Task join
218+ ├── src/task_group.cppm # eager mutable structured Task scope
189219├── tests/cmp_test.cpp # Task contract and lifetime tests
190220├── tests/run_loop_test.cpp # scheduler, boundary, and threading tests
191221├── tests/when_all_test.cpp # join ownership, result, and race tests
222+ ├── tests/task_group_test.cpp # mutable scope lifetime and race tests
192223├── examples/basic/ # standalone path-dependency consumer
193224├── docs/architecture.md # current structure, boundaries, and evolution
194225└── .github/workflows/ # Linux, macOS, and Windows CI
@@ -221,8 +252,9 @@ Runtime work is split into independently reviewable phases:
2212521 . package identity and importable-module bootstrap — implemented;
2222532 . coroutine task and lifetime semantics — initial ` Task ` implemented;
2232543 . a root runner and minimal single-thread scheduler — initially implemented;
224- 4 . monotonic Timer v1, cancellable timed waits, and variadic/vector structured joins — implemented;
225- mutable task scopes, cancellation propagation, and more wake-up paths remain;
255+ 4 . monotonic Timer v1, cancellable timed waits, variadic/vector joins, and TaskGroup v1 —
256+ implemented; recursive scope admission, broader cancellation propagation, and more wake-up
257+ paths remain;
2262585 . multi-worker scheduling and work stealing;
2272596 . asynchronous I/O integration and a blocking pool.
228260
0 commit comments