Skip to content

Commit c074250

Browse files
committed
实现可变结构化任务作用域
1 parent 4467ab0 commit c074250

13 files changed

Lines changed: 1218 additions & 108 deletions

‎.agent/HANDOFF.md‎

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
## 项目概览
44

55
CMP 是使用 mcpp 构建的 C++23 Modules 协程运行时库。当前公开核心包括惰性、唯一所有权的
6-
`Task<T>`,支持变参与同类型动态集合的结构化 `when_all()`,单消费者 `RunLoop`,以及可复制
7-
的 `RunLoop::Scheduler`。Scheduler 支持普通
6+
`Task<T>`,支持变参与同类型动态集合的结构化 `when_all()`,eager 可变结构化 `TaskGroup`,
7+
单消费者 `RunLoop`,以及可复制的 `RunLoop::Scheduler`。Scheduler 支持普通
88
`schedule()`、相对期限 `schedule_after()` 和绝对期限 `schedule_at()`;定时等待可显式接收
99
`std::stop_token`,取消获胜时抛出 `OperationCancelled`。由 Scheduler 入队的 continuation
1010
均由调用 `RunLoop::run()` 的线程恢复。
@@ -15,10 +15,9 @@ LLVM 22.1.8,测试依赖为 `compat.gtest` 1.15.2。实现位于 `src/`,契
1515

1616
## 当前目标与状态
1717

18-
当前分支为 `main`,已与远端同步到 PR #6 的 squash 合并提交 `5c202c8`。variadic
19-
`when_all` v1 已通过 PR #5 合并;运行时同类型 Task 集合的
20-
`when_all(std::vector<Task<T>>)` 已完成实现、验证、示例、三语文档和三平台交付。下一阶段尚未
21-
创建分支,准备独立设计可变结构化任务作用域。
18+
当前分支为 `feature/task-group-v1`,基于已与远端同步的 `main` 提交 `4467ab0`。variadic 与
19+
vector `when_all` 均已合并;最小可变结构化任务作用域 `TaskGroup` 已完成实现、公开契约测试、
20+
独立示例、三语文档和本地严格验证,尚未提交或推送。
2221

2322
## 已完成工作
2423

@@ -47,6 +46,15 @@ LLVM 22.1.8,测试依赖为 `compat.gtest` 1.15.2。实现位于 `src/`,契
4746
- 新增 7 项动态集合契约测试和 5 万次立即汇合栈安全压力;独立示例改为在协程内构造并汇合
4847
`std::vector<Task<int>>`。
4948
- README、架构说明、设计文档和实施计划已同步动态集合 API 与 53 项测试基线。
49+
- 已导出不可移动的 `TaskGroup`;`spawn(Task<void>)` 接管并 eager 启动子任务,单次 `join()`
50+
关闭接纳、等待全部终态,再按接纳顺序传播第一个异常。
51+
- TaskGroup 使用一个 mutex 保护状态、计数、wrapper 存储和 join continuation;最后一个子任务
52+
在锁外通过对称转移恢复 join,不引入后台线程或 detached 生命周期。
53+
- TaskGroup 直接复用 `std::stop_source` / `std::stop_token` 提供显式共享取消通道,不自动向
54+
Task 注入 token。
55+
- 新增 9 项 TaskGroup 契约测试,覆盖 eager/lazy 边界、并发接纳、join 后关闭、异常顺序、
56+
取消、跨线程发布和 5 万次即时完成栈安全。
57+
- 独立示例和英文、简体中文、繁体中文 README / 架构文档已同步 TaskGroup v1。
5058

5159
## 重要决策
5260

@@ -56,20 +64,27 @@ LLVM 22.1.8,测试依赖为 `compat.gtest` 1.15.2。实现位于 `src/`,契
5664
- 取消通过现有 Task 异常路径传播;调用方可以在协程内捕获 `OperationCancelled`。
5765
- Timer entry 只增加一个非 owning 取消状态指针;外部销毁已发布协程帧仍属于无效使用。
5866
- 取消当前用 O(n) 扫描并重建堆;只有实测为瓶颈后才引入可删除或带索引的堆。
59-
- 不增加后台线程、依赖、公开 Timer 句柄、TaskGroup、detached 或测试专用公共接口。
67+
- 不增加后台线程、依赖、公开 Timer 句柄、detached 或测试专用公共接口。
6068
- `when_all` v1 使用“子任务数 + 1”原子计数哨兵,覆盖同步完成和跨线程完成竞态。
6169
- `when_all` 等待全部子任务收尾后再传播异常,不允许因 fail-fast 提前销毁仍在运行的帧。
62-
- `when_all` v1 先实现固定参数包汇合;动态 TaskGroup、spawn 和隐式取消传播继续独立设计。
70+
- `when_all` 负责一次性结果汇合;TaskGroup v1 只接纳 `Task<void>`,需要结果时继续使用
71+
`when_all()` 或由调用方持有至 join 结束的状态。
6372
- 范围阶段只接受标准 `std::vector<Task<T>>`,不为尚无需求的任意 range 增加模板层。
73+
- TaskGroup 的 join 仅能使用一次;join 开始后不允许继续或递归接纳,未 join 的 open/joining
74+
group 在析构时终止进程,避免销毁仍被发布的子协程帧。
75+
- TaskGroup 取消保持显式:`request_stop()` 不会自动取消不接收 token 的子任务,也不提供
76+
`cancel_and_join()`、结果 future、scheduler 选择或 completed-wrapper 回收。
6477

6578
## 修改 / 重要文件
6679

6780
- `src/run_loop.cppm`:公开异常、token 重载、取消状态、回调和显式 Timer 堆。
6881
- `src/when_all.cppm`:join coroutine、原子计数哨兵和 variadic/vector 公开 API。
69-
- `src/cmp.cppm`:导出 `:when_all` 分区。
82+
- `src/task_group.cppm`:eager void 子任务作用域、单次 join、显式 stop 通道和生命周期状态机。
83+
- `src/cmp.cppm`:导出 `:when_all` 与 `:task_group` 分区。
7084
- `tests/run_loop_test.cpp`:Cancellation v1 契约、边界、竞态和栈安全测试。
7185
- `tests/when_all_test.cpp`:变参/vector 的结构化所有权、结果、异常、线程和栈安全测试。
72-
- `examples/basic/src/main.cpp`:协程内正常输出和预取消输出示例。
86+
- `tests/task_group_test.cpp`:TaskGroup 接纳、join、异常、取消、线程和栈安全测试。
87+
- `examples/basic/src/main.cpp`:协程内正常、并发、TaskGroup 和预取消输出示例。
7388
- `README.md`、`README.zh.md`、`README.zh.hant.md`:当前 API、示例和路线图。
7489
- `docs/architecture.md`、`docs/architecture.zh.md`、`docs/architecture.zh.hant.md`:实现契约、
7590
边界与验证基线。
@@ -79,6 +94,8 @@ LLVM 22.1.8,测试依赖为 `compat.gtest` 1.15.2。实现位于 `src/`,契
7994
- `docs/superpowers/plans/2026-08-24-cmp-when-all-v1.md`:当前实施与交付清单。
8095
- `docs/superpowers/specs/2026-08-24-cmp-when-all-range-design.md`:当前动态集合设计。
8196
- `docs/superpowers/plans/2026-08-24-cmp-when-all-range.md`:当前动态集合实施清单。
97+
- `docs/superpowers/specs/2026-08-24-cmp-task-group-v1-design.md`:当前 TaskGroup v1 设计。
98+
- `docs/superpowers/plans/2026-08-24-cmp-task-group-v1.md`:当前 TaskGroup v1 实施清单。
8299
- `.agent/HANDOFF.md`:本文件。
83100

84101
## 验证情况
@@ -117,7 +134,16 @@ LLVM 22.1.8,测试依赖为 `compat.gtest` 1.15.2。实现位于 `src/`,契
117134
连续执行 5,000 轮全部通过。
118135
- PR #6 最终 head `ee378aa` 的 Linux x86_64、macOS arm64、Windows x86_64 CI 均通过构建、
119136
53 项测试和独立示例;已 squash 合并为 `5c202c8`,功能分支保留。
120-
- 本地 `main` 已快进同步到 `5c202c8`;合并后的交接状态提交和最终 `main` CI 尚待完成。
137+
- 本地与远端 `main` 已同步到交接提交 `4467ab0`;该提交的 Linux、macOS、Windows 最终 CI
138+
均成功。
139+
- TaskGroup 新测试先因缺少公开类型按预期编译失败;实现后聚焦 Dev 测试 9/9 通过。
140+
- TaskGroup 阶段 Dev/Release 严格无缓存构建通过;两个 profile 的完整测试均为 62/62(8 个
141+
Task、30 个 RunLoop、15 个 when_all、9 个 TaskGroup)。
142+
- Release 的完整 TaskGroup 套件额外连续执行 50 轮,跨线程完成测试 5,000 轮、并发接纳测试
143+
1,000 轮,全部通过。
144+
- 更新后的独立示例运行成功,依次输出 `Coroutine result: 42`、`Concurrent result: 42`、
145+
`Task group result: 42` 和 `Coroutine cancelled`。
146+
- TaskGroup v1 本地实现与验证阶段完成于 2026-08-24 02:14:36 CST;尚未提交或触发远端 CI。
121147

122148
## 已知问题 / 风险
123149

@@ -129,13 +155,16 @@ LLVM 22.1.8,测试依赖为 `compat.gtest` 1.15.2。实现位于 `src/`,契
129155
awaiter。
130156
- pending `when_all`(包括 vector 重载)仍遵循现有 Task 边界:子任务发布 continuation 后,外部提前销毁聚合
131157
帧属于无效使用。
158+
- TaskGroup 会保留所有 wrapper 帧至析构,空间复杂度为 O(n);v1 不为尚无实测需求增加完成后
159+
回收机制。
160+
- TaskGroup 遗漏 join 会确定性终止,且取消不会自动注入子任务;这两项均是已记录的公开契约。
132161

133162
## 剩余工作
134163

135-
1. 提交并推送本次合并状态更新,确认最终 `main` 三平台 CI。
136-
2. 读取本机时间;未到停止窗口则创建新分支,开始可变结构化任务作用域的独立设计。
164+
1. 提交并推送 `feature/task-group-v1`,创建 PR,等待 Linux、macOS、Windows CI 全绿后合并。
165+
2. 同步本地 `main`,更新交接状态并验证最终 main CI。
137166

138167
## 推荐下一步
139168

140-
下一步先验证合并后的 `main`;随后只设计最小可变结构化任务作用域,明确 admission、等待、
141-
异常与取消边界,不在设计确认前加入 detached 生命周期。
169+
下一步提交当前 TaskGroup v1 阶段并完成三平台 PR 交付;不要加入 detached、结果 future 或
170+
递归 shutdown admission。

‎README.md‎

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
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
2424
CMP 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

7676
The 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;
8888
using mcpplibs::cmp::Task;
8989
using mcpplibs::cmp::RunLoop;
9090
using mcpplibs::cmp::OperationCancelled;
91+
using mcpplibs::cmp::TaskGroup;
9192
using mcpplibs::cmp::when_all;
9293

9394
using 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+
123144
Task<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
154176
homogeneous collection can be passed as `std::vector<Task<T>>`; it returns a result vector in the
155177
same 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+
157186
A translation unit that defines a coroutine must import `std` so the compiler can see the standard
158187
coroutine protocol types. CMP imports `std` privately and does not re-export the whole standard
159188
library.
@@ -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:
221252
1. package identity and importable-module bootstrap — implemented;
222253
2. coroutine task and lifetime semantics — initial `Task` implemented;
223254
3. 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;
226258
5. multi-worker scheduling and work stealing;
227259
6. asynchronous I/O integration and a blocking pool.
228260

0 commit comments

Comments
 (0)