On Mon, Jul 6, 2026 at 2:53 PM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
Slightly as a nit-pick, I have just learnt that you also have to study the not-proposed Boost.HTTP to be able to review the concept BufferSing, as it has no real model in Capy or Corosio.
Yeah, that's fair. And I think maybe we can include an example or something so there is some review surface.
Why does the doc page describing the launching of coroutines (
https://develop.capy.cpp.al/capy/4.coroutines/4b.launching.html) have a red warning box?
Well the two-call syntax can be abused. Don't do that.
Some people do not like thread_local based on its implementation difficulties: requires dynamic memory allocation (and no custom allocator can be provided), is not implementable on some architectures.
Okay but that's for implementors. The user doesn't have to worry about that. If thread_local is not available on a particular architecture, the implementation can fall back to the global allocator. Or in more practical terms it simply uses a plain global variable. It turns out that the platforms without thread_local also tend to not have threads (P4182R2 which I can't seem to find at the moment... Mungo?)
My discomfort is not about the feature itself, but how it is used. The usage is based on the assumption that the read from the variable happens (1) immediately after the write (no intervening accesses) and (2) on the same thread. And since you cannot enforce that, you have to trust the user, and maybe even trust your luck.
We *can* guarantee it. Section 8.3: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p4172r1.pdf
But because this hack is made to move the complexity out of the user's sight, the user will not cooperate (they do not know that they have to).
The user doesn't have to cooperate. The launch function has the burden. P4172 describes three audiences: 1. Application developer 2. Framework author 3. I/O library author The Framework author has the responsibility of making sure that the launch functions and task types they create follow the rules. These authors are fewer in number with a higher level of skill. The expectations are reasonable.
I am not even sure how this can be implemented for non-task IoAwaitables when in a thread executing the coroutine may be different than the thread resuming the coroutine, in a thread pool executor.
Non-task awaitables do not have an associated coroutine, so they don't have to handle it. They just put their state into the calling coroutines frame, without requiring an allocation. These are written by I/O library authors (group 3 above). Corosio is full of them. read_some_awaitable, and so on.
Also, as with any other hacks, they work under the assumption that no-one else is doing their own hacks at the same time. Boost.LEAF uses the TLS hack, and it only works until you have to interoperate with a coroutine library.
There is no monolithic "TLS hack." It is not some light switch that is turned on and off. Boost.LEAF and Capy each use their own thread local variables to achieve their ends. Since the variables have different linkages, they are separate from each other and work together Just Fine. Show me the specific interaction where you think these libraries do not compose?
In fact, I wish that the problem of environment propagation were addressed by a dedicated language feature that indicates the intention explicitly and allows the compiler to verify the correct usage.
Changing the language should be DIFFICULT and EARNED. Engineers are too hasty to beg the language for features to make their lives easier. Note, we did not go running hat in hand to wg21 saying "please solve this in the language." We especially do not say "we need co_yield to act as a final suspend." Instead, we come up with our own solution which does not require changes to the language. It is not perfect, but it gives 16.3 million potential users of networking a path to use coroutines for composition, instead of callbacks. And then we can get field experience will may LATER justify a change to the language. But only after we have at least 9 years under our belt.
My discomfort is not about the strangeness of the notation but about how easy it is to misuse it. The red warning box in the docs confirms this. BTW, the example in the warning box is wrong. That code would not compile (and rightly so). But there are others that would compile and silently get a different behavior.
You say "there are others" plural. But only give one example. Maybe it is a legitimate example but the way you phrase it implies a wider scope than exist. Please enumerate actual problems so they can be engaged one by one, and not unfalsifiable generalizations. 3. Having to use pass they stop_token via await_ready (and therefore risk UB
when stop_token is queried before the first suspend).
What? Show the undefined behavior you think is happening, please.
I imagine a coroutine where a part of its body is executed
immediately, without suspension. I can imagine a stop being requested from a different thread executing a different continuation. Say, calling .cancel() on some IO object.
You can imagine all you like but please, show concrete examples. The example you just gave, calling cancel() on "some I/O object" from a different thread, is undefined behavior. ALL I/O objects in Corosio are NOT THREAD SAFE. You cannot access them concurrently. Please show a concrete example of undefined behavior.
I did not mean C++26 executors as the alternative. I was thinking more in the vein of https://github.com/ClickHouse/silk.
Stackful coroutines ("fiber" in silk parlance) are wonderful. And Nat Goodspeed should not have waited over twelve years to get them in the standard. They do not replace stackless coroutines, nor do they compete with them. They are a legitimate tool which solves the same problem with a different set of tradeoffs. C++ needs them, just as much as it needs frame-opaque stackless coroutines. And I would argue the language needs frame visible coroutines as well. This is not contested and I have written a paper saying so: *Benefits of Frame-Visible Coroutines for Senders* https://isocpp.org/files/papers/P4166R0.pdf However, they are unsuited to a large class of I/O problems, networking in particular. Jamie, do you want to build your system on fibers, or stackless coroutines?
I am in favour of doing the right thing rather than doing what the Standard does. They can of course coincide, but the guidance from the standard alone is insufficient motivation.
That's a false premise. In this case the Right thing and the Standard thing are the same thing: error_code for common outcomes and transient states, and exceptions for exceptional outcomes.
Who said they are supposed to be "errors"? `error_code` is just a label.
You are attaching semantic significance to the type name.
I am not sure if you are serious. I indeed attach semantic significance to the type name. Why did you call the type providing access to a UDP socket in Corosio `udp_socket`? You could have called it `sun_flower` and then insist that people should not attach semantic significance to type names.
Of course I am serious. And I did not choose the name error_code. It is what it is, and it cannot be changed. That said, this is how it is being used. To argue "you shouldn't use it that way" is not a serious debate when the customs have already been adopted. And I would say, that returning distinguishable, successful outcomes from error_code is fully in scope. Again I point to Boost.System: https://github.com/boostorg/system/blob/bc7c00fa67501ceadfde8e920835502340e8... Why does `bool failed()` exist, if according to you there should only be one value of "success?" Was Peter Dimov wrong?
In this case I do not buy the "ASIO got that right" argument.
Asio doesn't have any dual-stream interfaces.
ASIO uses type error_code to signal non-errors.
A quarter-century of established practice. Show me your alternative. Then let's compare. Thanks