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.