On 6/29/26 18:03, Steve Gerbino via Boost wrote:
On Monday, June 29th, 2026 at 5:01 PM, Rainer Deyke via Boost <boost@lists.boost.org> wrote:
The second escape hatch is immediate_executor. It looks something like this:
class immediate_executor { public: std::coroutine_handle<> ce.dispatch(capy::continuation &c) { // Obey the letter of the law by not just returning c.h... this->post(c); return {}; }
void post(capy::continuation &c) { // ...but violate the spirit of the law by calling h.resume(). c.h.resume(); } // ...other functions here... };
immediate_executor is a powerful way for opting out of costs, and the benefits, of more robust executors where they are not needed or wanted. And it appears to be completely legal. It works great in conjunction with resume_on, but it also reduces the cost of capy::run significantly, since switching to an immediate_executor is effective free.
Put big red warning signs on immediate_executor that it should only be used by experts who know what they are doing if you have to, but provide it.
As written I don't think it's viable. Calling resume() from post adds stack frames and introduces stack growth, which we deliberately avoid.
Yes, there's a risk of stack overflow. It can be mitigated by returning the coroutine handle from dispatch instead of calling resume, but it can't be eliminated entirely. This is definitely not a very safe, or otherwise very good, general purpose executor. It's an escape hatch for high performance code where the user really knows what he's doing.
The inline behavior already exists. When an executor is safe to run inline (already on the right thread), its dispatch returns the continuation handle for symmetric transfer.
As you said, only in contexts where you're already on the right thread.
Consider this example again:
task<int> f() { capy::run(ex1)([] { do_something_on_e1(); }); capy::run(ex2)([] { do_something_on_e2(); }); capy::run(ex3)([] { do_something_on_e3(); }); }
Running this on an immediate_executor effectively eliminates three of the six executor hops, and the amount of time it spends on the wrong thread is negligible.
This use of immediate_executor seems wasteful. If ex1, ex2, ex3 are all immediate executors, then run is adding trampolines and executor hops that don't go anywhere meaningful. The work runs on the current thread regardless. So my first instinct is just do the following:
task<int> f() { do_something_on_e1(); do_something_on_e2(); do_something_on_e3(); }
They're not. There is obviously no point in having multiple immediate_executors, since they are all functionally interchangeable. I only wanted to run the main task on an immediate_executor. immediate_executor is not a replacement for other executors. If it makes the example clearer to you, consider this: task<int> f() { capy::run(strand1)([] { do_something_on_strand1(); }); capy::run(strand2)([] { do_something_on_strand2(); }); capy::run(strand3)([] { do_something_on_strand3(); }); } Or this: task<int> f() { capy::run(thread1_ex)([] { do_something_on_thread1(); }); capy::run(thread2_ex)([] { do_something_on_thread2(); }); capy::run(thread3_ex)([] { do_something_on_thread3(); }); }
which is more efficient than the immediate_executor version and runs in exactly the same place. The only thing it leaves out is a suspension point between the stages.
That's not the goal. That's the opposite of the goal. The goal is to switch executors directly while minimizing suspension. -- Rainer Deyke - rainerd@eldwood.com