You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: BoostToStdCoroutineSwitchPlan.md
+23Lines changed: 23 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -108,6 +108,7 @@ C++20 stackless coroutines have well-known limitations compared to stackful coro
108
108
**Claim**: Stackless coroutines cannot yield from arbitrary stack depths. If `fn_a()` calls `fn_b()` calls `yield()`, only stackful coroutines can suspend the entire chain.
109
109
110
110
**Analysis**: An exhaustive codebase audit found:
111
+
111
112
-**1 production yield() call**: `RipplePathFind.cpp:131` — directly in the handler function body
112
113
-**All test yield() calls**: directly in `postCoro` lambda bodies (Coroutine_test.cpp, JobQueue_test.cpp)
113
114
-**The `push_type*` architecture** makes deep-nested yield() structurally impossible — the `yield_` pointer is only available inside the `postCoro` lambda via the `shared_ptr<Coro>`, and handlers call `context.coro->yield()` at the top level
@@ -119,6 +120,7 @@ C++20 stackless coroutines have well-known limitations compared to stackful coro
119
120
**Claim**: Once a function needs to suspend, every caller up the chain must also be a coroutine. This "infects" the call chain.
120
121
121
122
**Analysis**: In rippled's case, the coloring is minimal:
123
+
122
124
-`postCoroTask()` launches a coroutine — this is the "root" colored function
123
125
- The `postCoro` lambda itself becomes the coroutine function (returns `CoroTask<void>`)
124
126
-`doRipplePathFind()` is the only handler that calls `co_await`
@@ -133,6 +135,7 @@ The "coloring" stops at the entry point lambda and the one handler that suspends
133
135
**Claim**: C++20 provides the language primitives but no standard task type, executor integration, or composition utilities.
134
136
135
137
**Analysis**: This is accurate — we need to write custom types:
@@ -154,6 +157,7 @@ However, these types are small, well-understood, and have extensive reference im
154
157
**Claim**: Coroutine frames are heap-allocated and outlive the calling scope, making references to locals dangerous.
155
158
156
159
**Analysis**: This is a real concern that requires engineering discipline:
160
+
157
161
- Coroutine parameters are copied into the frame (safe by default)
158
162
- References passed to coroutine functions can dangle if the referent's scope ends before the coroutine completes
159
163
- Our design mitigates this: `RPC::Context` is passed by reference but its lifetime is managed by `shared_ptr<Coro>` / the entry point lambda's scope, which outlives the coroutine
@@ -165,6 +169,7 @@ However, these types are small, well-understood, and have extensive reference im
**Analysis**: `yield_to.h` uses `boost::asio::spawn` with `boost::context::fixedsize_stack(2 * 1024 * 1024)` — this is a **completely separate** coroutine system:
172
+
168
173
- Different type: `boost::asio::yield_context` (not `push_type*`)
169
174
- Different purpose: test infrastructure for async I/O tests
170
175
- Different mechanism: Boost.Asio stackful coroutines (not Boost.Coroutine2)
@@ -175,6 +180,7 @@ However, these types are small, well-understood, and have extensive reference im
175
180
#### Overall Viability Conclusion
176
181
177
182
The migration IS viable because:
183
+
178
184
1. rippled's coroutine usage is **shallow** (no deep-nested yield)
179
185
2. The **colored function infection** is limited to 4 call sites
180
186
3. Custom types are **small and well-understood**
@@ -207,6 +213,7 @@ The migration IS viable because:
207
213
#### Alternative Considered: ASAN Annotations Only
208
214
209
215
Instead of full migration, one could keep Boost.Coroutine and add `__sanitizer_start_switch_fiber` / `__sanitizer_finish_switch_fiber` annotations to Coro.ipp to suppress ASAN false positives. This was evaluated and rejected because:
216
+
210
217
- It only fixes sanitizer false positives — does NOT reduce 1MB/coroutine memory usage
211
218
- Does NOT remove the deprecated Boost.Coroutine dependency
212
219
- Does NOT provide standard compliance or future-proofing
@@ -400,18 +407,21 @@ Coroutine = std::function<void(Suspend)> — given a Suspend, starts work
400
407
```
401
408
402
409
In practice, `JobQueue::Coro` simplifies this to:
410
+
403
411
-**Suspend** = `coro->yield()`
404
412
-**Continue** = `coro->post()` (async on JobQueue) or `coro->resume()` (sync on current thread)
0 commit comments