I like context.WithCancel from Go, but it can essentially be implemented directly in PHP land since all the necessary tools are available.
Note, this is precisely the problem, implement cancellation propagation to child fibers in userland PHP requires writing a bunch of boilerplate try-catch blocks to propagate CancellationExceptions to child FutureHandle::cancel()s (spawning multiple fibers to execute subtasks concurrently during an async method call is pretty common, and the current implicit cancellation mode requires writing a bunch of try-catch blocks to propagate cancellation, instead of just passing a cancellation object, or a flag to inherit the cancellation of the current fiber when spawning a new one). Catching CancellationException is only necessary if there is some defer code. If there isn't, then there's no need to catch it. Try-catch blocks are not mandatory. We can create a Cancellation object, pass it via use or as a parameter to all child fibers, and check it in await(). This is the most explicit approach. In this case, try-catch would only be needed if we want to clean up some resources. Otherwise, we can omit it. According to the RFC, if a fiber does not catch CancellationException, it will be handled by the Scheduler. Therefore, catching this exception is not strictly necessary. If this solution also seems too verbose, there is another one that can be implemented without modifying this RFC. For example, implementing a cancellation operation for a Context. All coroutines associated with this context would be canceled. From an implementation perspective, this is essentially iterating over all coroutines and checking which context they belong to. Note the explicit use case I listed is that of an unlock() in a finally block that *requires spawning a new fiber* in order to execute the actual unlock() RPC call: this is explicitly in contrast with the RFC, which specifies that So, if I understand correctly, the code in question looks like this: try { lock(); ... } finally { unlock(); } function unlock() { async\run(); } If I got it right, then the following happens: The code inside try {} allocates resources. The code inside finally {} also allocates resources. So, what do we get? We're trying to terminate the execution of a fiber, and instead, it creates a new one. It seems like there's a logical error here. Instead of creating a new fiber, it would be better to use microtasks. I would really prefer it to be always enabled, no fallback at all, because as I said, it will make absolutely no difference to legacy, non-async projects that do not use fibers, but it will avoid a split ecosystem scenario. I'm not arguing at all that avoiding the call to this function is a good solution. I’m on your side. The only question is how to achieve this technically. Could you describe an example of "ecosystem split" in the context of this function? What exactly is the danger? I see no reason why it should break the contract, if implemented by isolating the global state of each fiber, it can be treated as a mere implementation detail of the (eventually new) SAPI. So, I can take NGINX and FCGI, and without changing the FCGI interface itself, but modifying its internal implementation, get a working application. Yes, but... that means all global variables, including static ones, need to be tied to the context. It's not that it can't be done, but what about memory consumption. I'm afraid that if the code wasn't designed for a LongRunning APP, it's unlikely to handle this task correctly. -- Ed.