On 18 March 2025 05:59:07 GMT, Larry Garfield <[email protected]> wrote:
>My biggest issue, though, is that I honestly can't tell what the mental model is supposed
>to be. The RFC goes into detail about three different async models. Are those standard terms
>you're borrowing from elsewhere, or your own creation? If the former, please include
>citations. I cannot really tell which one the "playpen" model would fit into. I... think
>bottom up, but I'm not sure. Moreover, I then cannot tell which of those models is in use in
>the RFC. There's a passing reference to it being bottom up, I think, but it certainly looks
>like the No Limit model. There's a section called structured concurrency, but what it
>describes doesn't look a thing like the playpen-definition of structured concurrency, which as
>noted is my preference. It's not clear why the various positives and negatives are there;
>it's just presented as though self-evident. Why does bottom up lead to high memory usage, for
>instance? That's not clear to me. So really... I have no idea how to think about any of it.
I had a very different reaction to that section. I do agree that some citations and links to prior
art would be good - I mentioned in my first email that the "actor model" is mentioned in
passing without ever being defined - but in general, I thought this summary was very succinct:
> - No limitation. Coroutines are not limited in their lifetime and run as long as needed.
> - Top-down limitation: Parent coroutines limit the lifetime of their children
> - Bottom-up limitation: Child coroutines extend the execution time of their parents
Since you've described playpens as having an implicit "await all", they're
bottom-up: the parent lasts as long as its longest child. Top-down would be the same thing, but with
an implicit "cancel all" instead.
>Broadly speaking, I can think of three usage patterns for async in PHP (speaking, again, as
>someone who doesn't have a lot of async experience, so I may be missing some):
>
>1. Fan-out. This is the "fetch all these URLs at once" type use case, which in most
>cases could be wrapped up into a para_map() function. (Which is exactly what Rust does.)
>2. Request handlers, for persistent-process servers. Would also apply for a queue worker.
>3. Throw it over the wall. This would be the logging example, or sending an email on some
>trigger, etc. Importantly, these are cases where there is no result needed from the sub-routine.
I agree that using these as key examples would be good.
>// Creates an async scope, in which you can create coroutines.
>async {
The problem with using examples like this is that it's not clear what happens further down the
stack - are you not allowed to spawn/start/whatever anything? Does it get started in the
"inherited" scope?
You've also done exactly what you complained the RFC did and provided a completely artificial
example - which of the key use cases you identified is this version of scope trying to solve?
I actually think what you're describing is very similar to the RFC, just with different syntax;
but your examples are different, so you're talking past each other a bit.
>I honestly cannot see a use case at this point for starting coroutines in arbitrary scopes.
The way I picture it is mostly about choosing between creating a child within a narrow scope
you've just opened, vs creating a sibling in the scope created somewhere up the stack.
The "request handler" use case could easily benefit from a "pseudo-global" scope
for each request - i e. "tie this to the current request, but not to anything else that's
started a scope in between".
There were also some concrete examples given in the previous thread of explicitly managing a
context/scope/playpen in a library.
Rowan Tommins
[IMSoP]