07:21
<littledan>
Component functiosn aren’t executed recusively, they’re pushed into a stack to be processed later on. When you return the <Ctx.Provider value=…><Foo /></Ctx.Provider> VDOM, that Foo component won’t be executed within the sync execution of Ctx.Provider. If Foo were to useContext(Ctx), that would be tracked as part of the component’s internal state (which itself is stored in a AC), it woulnd’t store the context on its own AC variable.
I don’t get it; isn’t this what AsyncContext.Snapshot is for?
07:22
<littledan>
I thought, in the server, an AsyncContext variable for the hooks state
07:27
<littledan>
In general, I think conceptually, React Context is doing the same thing as AsyncContext. It just depends on the framework saving and restoring snapshots all over the place
07:30
<littledan>
This is an important question because if we wanted to use AsyncContext variables for other things, like having an ambient AbortSignal, we would be depending on frameworks doing this snapshotting. Otherwise it wouldn’t work.
16:00
<Steve Hicks>
right, this is basically "the ecosystem adoption problem".
16:03
<Chengzhong Wu>
We are on the call now
17:23
<littledan>
How did the call go? I am having some trouble understanding the strength of the requirement that Matteo and Stephen were talking about in the notes
17:23
<littledan>
I couldn’t really understand why taking a callback as a parameter wouldn’t work for those cases
17:23
<littledan>
Sorry I missed it
17:25
<Andreu Botella>
it's not that it wouldn't work, but that it wouldn't be idiomatic, or it'd take a lot more boilerplate than the alternative
17:25
<littledan>
I actually don’t understand what is being asked for. How broad should the .set take effect?
17:25
<Andreu Botella>
for tests, if you have set/enterWith, you could set the context in beforeEach
17:25
<littledan>
Sure, I can see that, I guess what I don’t understand is how bad it is
17:26
<littledan>
How bad would it be if we deferred enterWith/set for “later”?
17:28
<littledan>
In general, there is an idiom where you store a mutable object in the asynccontext variable, and accessing the variable gets that object and then gets what it is wrapping. Every time you are tempted to do .set, you .get the variable and then set what it is wrapping. Are these semantics what we want for enterWith, or is it supposed to take effect within a more narrow scope?
17:29
<littledan>
This is the difference between “trivial” and “a massive design change”
17:29
<Andreu Botella>
I think Stephen was arguing for a more narrow scope
17:29
<littledan>
(Because we could make some sugar for the idiom I described, if desired)
17:30
<littledan>
I think Stephen was arguing for a more narrow scope
It would be good to hear from both Stephen Belanger and Matteo Collina here
17:30
<littledan>
If you do want a narrower scope: how would you like to define that scope?
17:33
<littledan>
(In case I wasn’t clear: the broad scoped version is easier)
17:34
<Stephen Belanger>
it's not that it wouldn't work, but that it wouldn't be idiomatic, or it'd take a lot more boilerplate than the alternative

No, there’s a lot of cases where taking a callback just doesn’t work. The example of storing a database connection in the top-level of one file and then trying to use it in another, for example. A common bootstrapping practice with top-level await.

There are also quite a few cases where APMs need to use enterWith because we just don’t have the ability to wrap a desired scope in any sort of callback—we can’t change how user code behaves yet need to be able to flow context around it.

17:35
<littledan>

No, there’s a lot of cases where taking a callback just doesn’t work. The example of storing a database connection in the top-level of one file and then trying to use it in another, for example. A common bootstrapping practice with top-level await.

There are also quite a few cases where APMs need to use enterWith because we just don’t have the ability to wrap a desired scope in any sort of callback—we can’t change how user code behaves yet need to be able to flow context around it.

Great, do you think you could reference a case where DataDog needs to do this? I had trouble tracing through what it was used for in the open source code
17:35
<littledan>
it definitely is used there, I just don’t understand the usage
17:35
<Stephen Belanger>
As I described in the call, the semantics are essentially the same—the scopes still exist—just decoupling the changing of the value from the providing of a scope means the scope can be raised upward or made implicit in many cases. An implicit scope around the execution of the application, for example, would solve the database connection sharing problem.
17:36
<littledan>
“The scopes still exist” when is a scope created?
17:36
<Stephen Belanger>
I need to find time to write up a proper explanation of exactly how and why this works…hopefully I can find some time for that at some point…
17:37
<littledan>
Maybe you can fill in parts of the notes if you said something that wasn’t captured
17:38
<littledan>
I don’t understand what an implicit scope around the application means… doesn’t the default value handle that?
17:40
<Andreu Botella>
not if the AsyncContext.Variable is created by a tracing library rather than by user code
17:42
<littledan>
What is the difference in what the tracing library does, in this case?
17:42
<littledan>
Is it one variable shared for many things?
17:43
<littledan>
I mean, are we running multiple applications in the same process?
17:44
<Stephen Belanger>
Imagine a variable is created in fastify for it to manage its context. A user then wants to store their database connection in that after the fastify app has already been constructed and therefore already has its variable set up. They then expect that database connection to be readable in the top-level of another file that runs after that point.
17:44
<Stephen Belanger>
You can’t do that without an implicit scope, and default values don’t work either because the variable already exists.
17:48
<littledan>
Is the scope narrower than global here? Is this per request/response? Just trying to understand the scenario
17:48
<littledan>
Is Fastify in a position to say, “this is the outer bounds, so future set calls apply only here”?
17:49
<littledan>
What I don’t understand is where implicit scope bounds need to be set
22:00
<snek>
this is about whether the proposal should support an api like AsyncLocalStorage#enterWith? What's the reason to not support it?
22:16
<Steve Hicks>

For the beforeEach case, I expect test runners could adapt pretty easily by providing a wrapping adapter - something like

aroundEach((test) => v.run(value, test));

would get the job done.

22:32
<Steve Hicks>
this is about whether the proposal should support an api like AsyncLocalStorage#enterWith? What's the reason to not support it?

The reason is that it breaks encapsulation. The current proposal means that you have strong guarantees that a variable won't change during the course of a function, but if a child task can enterWith a new value, then it can affect the parent task in unexpected ways. It also runs afoul of the requirements imposed by SES, and is unlikely to be accepted by the committee.

That said, if you want to make your own enterWithable variable, it's easy enough:

class EnterableVar<T> {
  private readonly internal: AsyncContext.Variable<[T]>;
  constructor(opts = {}) {
    this.internal = new AsyncContext.Variable({...opts, defaultValue: [opts.defaultValue]});
  }
  run(val, fn) {
    return this.internal.run([val], fn);
  }
  get() {
    return this.internal.get()[0];
  }
  enterWith(val) {
    this.internal.get()[0] = val;
  }
}
23:54
<Stephen Belanger>

The reason is that it breaks encapsulation. The current proposal means that you have strong guarantees that a variable won't change during the course of a function, but if a child task can enterWith a new value, then it can affect the parent task in unexpected ways. It also runs afoul of the requirements imposed by SES, and is unlikely to be accepted by the committee.

That said, if you want to make your own enterWithable variable, it's easy enough:

class EnterableVar<T> {
  private readonly internal: AsyncContext.Variable<[T]>;
  constructor(opts = {}) {
    this.internal = new AsyncContext.Variable({...opts, defaultValue: [opts.defaultValue]});
  }
  run(val, fn) {
    return this.internal.run([val], fn);
  }
  get() {
    return this.internal.get()[0];
  }
  enterWith(val) {
    this.internal.get()[0] = val;
  }
}
That's not necessarily true with implicit scopes. If any function call is made an implicit scope then breaking out would be impossible.