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.

Reply via email to