On 18 March 2025 05:59:07 GMT, Larry Garfield <la...@garfieldtech.com> 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]