03:16
<ljharb>
that aspect may not have been made clear to those objectors tho; asynccontext is a tough proposal to grok imo
04:00
<kriskowal>
since then, i suppose those objectors have gotten over the philosophical objection that it was tantamount to unacceptabel dynamic scope, given the enthusiasm about AsyncContext
Mark did a rather rigorous analysis of AsyncContext last year to settle those objections preemptively. I was taking a walk with my neighbor Malte Ubl who mentioned Vercel had hired Justin and that he was excited to push AsyncContext. My first thought was that our friendship might not endure that tension, but I arranged for Mark and Justin to talk thru how the design addresses OCap concerns over four or so meetings and we’re good now.
04:02
<kriskowal>
Enthusiastic even, since we need such a thing for causal tracing.
04:11
<kriskowal>
the other half of what they do seems to be making it so the dynamic import in Promise.resolve('import(`./example.mjs`)').then(eval); resolves relative to the script active when the promise job is enqueued
I believe the dynamic import should be resolved in the context of the script associated with the environment record internal slot on the globalThis.eval in the lexical scope. Else, bug.
04:12
<kriskowal>
Also https://github.com/tc39/ecma262/issues/3160
06:00
<Mathieu Hofman>
since then, i suppose those objectors have gotten over the philosophical objection that it was tantamount to unacceptabel dynamic scope, given the enthusiasm about AsyncContext
I'd like to clarify that exposing AsyncContext as a feature is not the same as allowing a host or the 262 spec itself to use the AsyncContext mechanism to create a variable and make the state of that variable observable in any way. That is still completely unacceptable from an Ocap and dynamic scoping pov.
06:03
<Mathieu Hofman>
That is the same kind of concern I raised with the Signals proposal as currently designed.
09:39
<Andreu Botella>
I'd like to clarify that exposing AsyncContext as a feature is not the same as allowing a host or the 262 spec itself to use the AsyncContext mechanism to create a variable and make the state of that variable observable in any way. That is still completely unacceptable from an Ocap and dynamic scoping pov.
I don't fully understand the whole incumbent realms thing, but from what I've looked at, it doesn't seem like that would be any different from having a userland library and scheduler that uses AsyncContext variables under the hood to know whether to expose something or other
11:28
<littledan>
I believe the dynamic import should be resolved in the context of the script associated with the environment record internal slot on the globalThis.eval in the lexical scope. Else, bug.
Seems like we are all in agreement, great to have an idea about how this relates to evaluators, which I hadn’t thought through
11:29
<littledan>
That is the same kind of concern I raised with the Signals proposal as currently designed.
Oh I thought the signals concern was about certain leaks that were fixable, not the general mechanism
11:29
<littledan>
I'd like to clarify that exposing AsyncContext as a feature is not the same as allowing a host or the 262 spec itself to use the AsyncContext mechanism to create a variable and make the state of that variable observable in any way. That is still completely unacceptable from an Ocap and dynamic scoping pov.
Can you say why?
11:31
<littledan>
And if this is terrible for JS to do, is it terrible for the web to do?
13:46
<Mathieu Hofman>
I don't fully understand the whole incumbent realms thing, but from what I've looked at, it doesn't seem like that would be any different from having a userland library and scheduler that uses AsyncContext variables under the hood to know whether to expose something or other
And that is the problem. A library or user code can use AsyncContext all it wants. The spec could use an AsyncContext variable as long as it doesn't make it observable, which is roughly the same as saying it cannot. It's the same as saying user code can freely create global mutable variables, but the spec only can if it doesn't make them observable.
13:50
<Mathieu Hofman>
Can you say why?
Because observable global mutable state is bad? More specifically it enables 2 parties that have not been explicitly introduced to communicate using ambient powers.
13:53
<littledan>
So, are you upset about scheduler.yield(), which does this?
13:53
<Mathieu Hofman>
Oh I thought the signals concern was about certain leaks that were fixable, not the general mechanism
My concern with Signals are about leaks of global mutable state. Whether they're fixable or not, I still don't know, but you and the champions did seem optimistic. Maybe there was a misunderstanding in the nature of the leak?
13:54
<littledan>
My concern with Signals are about leaks of global mutable state. Whether they're fixable or not, I still don't know, but you and the champions did seem optimistic. Maybe there was a misunderstanding in the nature of the leak?
Yeah in particular I am wondering if there are any leaks without the subtle APIs, so without currentComputed or introspection
13:58
<Mathieu Hofman>
So, are you upset about scheduler.yield(), which does this?
I wasn't aware of this API but in general the web has a lot of APIs that are not Ocap friendly and ambiently expose I/O or mutable state. That's the prerogative of the host, as long as it doesn't affect 262 behavior. One problem with the above is that it affects module resolution.
14:08
<Andreu Botella>
As far as I can tell, incumbent realms are observable, but they shouldn't allow communication between any two parties
14:11
<Andreu Botella>
there is only some difference to observe if the two parties run in different realms, and it seems like when an ECMAScript function (that is, not a built-in) is called, its realm is the incumbent realm for that function and any built-in or host function it might call
14:12
<Andreu Botella>
but I'm no expert on this
14:15
<Andreu Botella>
oh wait, maybe if some script in realm A causes a script execution in realm B, realm A might be observable
14:22
<Mathieu Hofman>
So, are you upset about scheduler.yield(), which does this?
Looking at this API a little more, this seems fine? Is there any global mutable state that's ambiently observable through this?
14:25
<Andreu Botella>
if you don't pass the priority or signal options in the option bag, they'll be preserved from the previous call to scheduler.yield in the same task, even through awaits
14:26
<Mathieu Hofman>
So it carries some internal state, but it still doesn't make it observable, right?
14:27
<Andreu Botella>
I think the "current" value of those properties are not exposed, but you can use signal to abort a yield and not continue the task
14:28
<Andreu Botella>
I guess that wouldn't count as communication between parties though
14:29
<Andreu Botella>
actually, the promise would reject if the signal is aborted, so I guess that is communication
14:57
<Mathieu Hofman>
Yeah I just saw that. That is indeed a way for the caller to ambiently communicate with the logic it spawned. I'm a little surprised they didn't go with an explicit context since retrofitting this ambient state to existing APIs like fetch seems inappropriate.
15:00
<Mathieu Hofman>
Aka as a developer I could be surprised by either the abort signal of my task not being respected by fetch called within my task's execution, or by an existing fetch all the sudden aborting not realizing it became part of a posted continued task.
15:24
<Andreu Botella>
I haven't looked enough at the details, but I think aborting the signal would only implicitly abort any "inherited" scheduler.yield() calls – implicit signal propagation for e.g. fetch is not in scope for that proposal
16:19
<littledan>
It is limited in scope but still observable. But this is "deniable" by blocking APIs which do this sharing (just like all the other DOM APIs that deal with the document)
17:21
<bakkot>
if you don't pass the priority or signal options in the option bag, they'll be preserved from the previous call to scheduler.yield in the same task, even through awaits
sidebar, this behavior is very strange and I hate it for reasons having nothing to do with communications channels
17:21
<bakkot>
it just makes programs harder to reason about
17:21
<bakkot>
much harder
17:21
<shu>
love2global
17:23
<bakkot>
I don't know how it is that we have managed to get so many people doing API design who do not instinctively recoil from adding new global state
17:24
<Michael Ficarra>
the developer funnel will always be largest at the top
17:25
<shu>
why can't a developer love global state earnestly
17:28
<bakkot>
random developers can do whatever they want, it's only the platform designers whose bad opinions the rest of us have to live with
17:28
<bakkot>
speaking as a platform designer
17:28
<ljharb>
we used to love it, but then someone mutated it and now we don't
17:28
<Michael Ficarra>
TIL that ClassHeritage has access to the inner immutable class name binding but not private names
17:28
<Michael Ficarra>
that is so weird
17:29
<global_lover>
yeah i'm a class hater now
17:29
<bakkot>
class name is outer to the { and so is ClassHeritage while private names are inner to the {
17:30
<Michael Ficarra>
@bakkot inner class name binding
17:30
<Michael Ficarra>
the immutable one
17:30
<bakkot>
I mean that the name occurs syntactically outside of the {
17:30
<bakkot>
so it makes sense that the binding lives outside of it also
17:30
<Michael Ficarra>
remember that classes create 2 bindings: an outer mutable one and an inner immutable one
17:30
<bakkot>
yes I know
17:30
<bakkot>
that does not contradict what I just aid
17:31
<bakkot>
syntactically both bindings are created by a single identifier, and that identifier is outside of the {
17:31
<littledan>
I don't know how it is that we have managed to get so many people doing API design who do not instinctively recoil from adding new global state
The whole point of AsyncContext is that it's not global, but rather scoped in the appropriate way. And that's what scheduler.yield does. We just know, empirically, that if you ask everyone to put in the right priority, they will just forget a lot of the time. Just like if we asked them to explicitly pass around the Otel spanid/traceid.
17:31
<Michael Ficarra>
I think the inner one should only be available inside the {
17:33
<bakkot>
The whole point of AsyncContext is that it's not global, but rather scoped in the appropriate way. And that's what scheduler.yield does. We just know, empirically, that if you ask everyone to put in the right priority, they will just forget a lot of the time. Just like if we asked them to explicitly pass around the Otel spanid/traceid.
IIUC, with AsyncContext you have to have a reference to the context in order to see or affect the state. it's not going to be affected by calling into random libraries. whereas with scheduler.yield, if I am doing some random stuff, and then I do library.doWork(), and the library does scheduler.yield({ its own config }), now my future calls to scheduler.yield don't work how I expected. that's crazy.
17:33
<bakkot>
if I gave the library the context object then it is perfectly reasonable for it to see/affect it
17:33
<bakkot>
but for it to be global, that's very strange
17:35
<Jack Works>
remember that classes create 2 bindings: an outer mutable one and an inner immutable one
oh no I know this first time
17:35
<bakkot>
if you wanted to avoid the problem where people forget to pass the right priority for scheduler, the obvious fix is to require you to construct a scheduler object (and explicitly specify the priority) and pass it around, instead of there being a global one
17:36
<bakkot>
and to avoid the problem of requiring other libraries to thread it around, that's what AsyncContext is for
17:37
<global_lover>
look, even the functional people reconstructed implicit globals with monads to mimic their power
17:38
<Andreu Botella>
IIUC, with AsyncContext you have to have a reference to the context in order to see or affect the state. it's not going to be affected by calling into random libraries. whereas with scheduler.yield, if I am doing some random stuff, and then I do library.doWork(), and the library does scheduler.yield({ its own config }), now my future calls to scheduler.yield don't work how I expected. that's crazy.
I don't think it works like that
17:39
<Andreu Botella>
but maybe my understanding is wrong, because while I was trying to explain how it worked, I realized that according to my understanding scheduler.yield shouldn't be implementable based on CPED
17:40
<Andreu Botella>
but of course it is implemented that way
17:40
<littledan>
For context: https://github.com/WICG/scheduling-apis/blob/main/explainers/yield-and-continuation.md
17:49
<bakkot>

that lists as an open question:

Does yield({priority}) set the priority to be inherited in future calls, or is the original signal used?

if I understand what this is saying correctly, using the original priority would be fine by me - it's only the "calling a library function can change the priority used by the rest of my code" part I'm objecting to here

17:49
<Mathieu Hofman>

The whole point of AsyncContext is that it's not global, but rather scoped in the appropriate way. And that's what scheduler.yield does

Is it? AsyncContext allows you to propagate some data related to a reference you own. The web instead creates an internal reference, and exposes way to access information related to that reference to anyone. scheduler.yield is effectively equivalent to a myInternalAsyncContextVar.get()

17:51
<Mathieu Hofman>
IIUC, with AsyncContext you have to have a reference to the context in order to see or affect the state. it's not going to be affected by calling into random libraries. whereas with scheduler.yield, if I am doing some random stuff, and then I do library.doWork(), and the library does scheduler.yield({ its own config }), now my future calls to scheduler.yield don't work how I expected. that's crazy.
That is the outcome of having the equivalent of a single global asynccontext variable
17:53
<Andreu Botella>

that lists as an open question:

Does yield({priority}) set the priority to be inherited in future calls, or is the original signal used?

if I understand what this is saying correctly, using the original priority would be fine by me - it's only the "calling a library function can change the priority used by the rest of my code" part I'm objecting to here

I think it would only set the priority to be inherited in promise reactions that flow from the returned promise
17:53
<Andreu Botella>
so in an async function, only for the remainder of the function
17:54
<Andreu Botella>
it wouldn't affect an outer async function
17:54
<Andreu Botella>
I don't think
17:54
<littledan>
seems like scheduler.yield does the equivalent of enterWith, which AsyncContext currently omits
17:56
<bakkot>
it wouldn't affect an outer async function
ah, that would be fine too then, though I'm not sure how that follows from "only reactions that flow from the returned promise" - the remainder of an outer async function would still ultimately flow from that reaction
17:56
<bakkot>
it's weird in other ways though - we don't normally treat function boundaries as special at all, except for like using
18:01
<Andreu Botella>
it's not the function boundary that is special, it's the promise jobs that are
18:01
<snek>
is it treating function boundaries as special or treating promises as special, and async functions use promises
18:01
<snek>
oh lol
18:02
<Andreu Botella>
when you return from an async function, its reaction will run in a different promise job
18:02
<Andreu Botella>
the .then() was called before it, and since AsyncContext preserves the context when .then() was called, the context inside the async function can't flow back to its caller
18:04
<bakkot>
ah, makes sense
18:11
<littledan>
so the only boundary here is AsyncContext.Variable.prototype.run(callback) -- that's when something changes. Everything else is just propagating the map (as AsyncContext.Snapshot.prototype.run does)
18:11
<littledan>
both work by callbacks, not treating function boundaries or Promises special
18:12
<littledan>
Promises are just one of the cases where the snapshot is propagated (as a constant, not a subtask or something)
18:12
<littledan>
there's no way to mutate any variable in place, only to "fork" with variable.run
18:12
<littledan>
this all was pretty core to the SES analysis that AsyncContext was kosher
18:13
<littledan>
some people are coming into the AsyncContext group and proposing other semantics which would permit more like this yield pattern. I've generally been pushing back on it.
18:13
<snek>
as mentioned in the other channel you can still put a mutable thing into an async variable
18:23
<Andreu Botella>
btw, Dan and I only realized that scheduler.yield used basically enterWith or these alternative semantics during this conversation
18:24
<Andreu Botella>
so if it doesn't make too much sense that we brought up that API in this context, that's why