> When I await(), do I need to do it in a loop, or just once?

It depends on what is being awaited.
On one hand, it would probably be convenient to have many different
operations for different cases, but then we make the language
semantics more complex.

> Coroutines become Future that only await once, while Signal is something that 
> can be awaited many times.
At the moment, only such objects exist. It’s hard to say whether there
will be others.
Although one can imagine an Interval object, there are some doubts
about whether such an object should be used in a while await loop,
because from a performance standpoint, it’s not very efficient.

> But the example setChildScopeExceptionHandler does exactly this!
The Scope-level handler does not interfere with coroutine completion.
And it is not called because the cancellation exception is "absorbed"
by the coroutine.

> Further, much framework/app code uses the $previous to wrap exceptions as 
> they bubble up,

If a programmer wants to wrap an exception in their own one let them.
No one forbids catching exceptions; they just shouldn’t be suppressed.

> Async\isCancellation(Throwable): bool
Why make a separate function if you can just walk through the chain?

> Minor nit: in the Async\protect section, it would be nice to say that 
> cancellations being AFTER the protect() are guaranteed, and also specify 
> reentry/nesting of protect(). Like what happens here:

That’s a good case! Re-entering protect should be forbidden that must
not be allowed.

> Also, if I'm reading this correctly, a coroutine can mark itself as canceled, 
> yet run to completion; however anyone await()'ing it, will get a 
> CancellationException instead of the completed value?
If a coroutine is canceled, its return value will be ignored.
However, of course, it can still call return, and that will work
without any issues.
I considered issuing a warning for such behavior but later removed it,
since I don’t see it as particularly dangerous.
This point requires attention, because there’s a certain “flexibility”
here that can be confusing. However, the risk in this case is low.

> Allowing destructors to spawn feels extremely dangerous to me (but powerful). 
> These typically -- but not always -- run between the return statement and the 
> next line (typically best to visualize that > as the "}" since it runs in the 
> original scope IIRC). That could make it 'feel like' methods/functions are 
> hanging or never returning if a library abuses this by suspending or awaiting 
> something.

Launching coroutines in destructors is indeed a relatively dangerous
operation, but for different reasons mainly related to who owns such
coroutines. However, I didn’t quite understand what danger you were
referring to?

Asynchronous operations, as well as coroutine launching, are indeed
used in practice. The code executes properly, so I don’t quite see
what risks there could be, apart from potential resource leaks caused
by faulty coroutines.

> async.zombie_coroutine_timeout says 2 seconds in the text, but 5 seconds in 
> the php.ini section.
Thanks.

> What is defined as "application considered finished?" FrankenPHP workers, for 
> instance, don’t "finish" — is there a way to reap zombies manually?

The Scheduler keeps track of the number of coroutines being executed.
When the number of active coroutines reaches zero, the Scheduler stops
execution. Zombie coroutines are not counted among those that keep the
execution running. If PHP is running in worker mode, then the worker
code must correctly keep the execution active. But even workers
sometimes need to shutdown.

>  it would be good to specify ordering and finally/onFinally execution here
Doesn’t the RFC define the order of onFinally handler execution?
onFinally handlers are executed after the coroutine or the Scope has completed.
onFinally is not directly related to dispose() in any way.

When dispose() is called, coroutine cancellation begins. This process
may take some time. Only after the last coroutine has stopped will
onFinally be invoked. In other words, you should not attempt to link
the calls of these methods in any way.

> Maybe something like Async\isEnabled() to know whether I should use fibers or 
> not.
Good idea!,
considering that such a function actually exists at the C code level.

> Is this "same exception" mean this literally, or is it a clone? If it is the 
> same, what prevents another code path from mutating the original exception 
> before it gets to me?
It’s the exact same object that is, a reference to the same instance.
So if someone modifies it, those changes will, of course, take effect.

> Is there also a timeout on Phase 1 shutdown? Otherwise, if it is only an 
> exception, then this could hang forever.

That’s true! A hang is indeed possible. I’m still not sure whether
it’s worth adding an auxiliary mechanism to handle such cases, because
that would effectively make PHP “smarter” than the programmer. I
believe that a language should not try to be smarter than the
programmer if the application runs in a certain way, then it’s
probably meant to be that way.

> - Sometimes you use AwaitCancelledException and other times 
> CancellationError. Which is it?
The old exception name apparently hasn’t been updated to the new one everywhere.

> Nit: the error message has a grammatical error: "Cannot create a fiber while 
> an True Async is active" should be "Cannot create a fiber while True Async is 
> active"?
>
Thanks!

Reply via email to