03:30 | <littledan> | I talked through what AsyncContext was with Jordan and he said he understands it better now than previously. I focused on how it was used; he said he found the pseudo-implementation confusing (sorry for the bad recommendation there!) When presenting, I recommend you spend a bunch of the time reviewing what the proposal is, somehow in terms a little different from last time, and what the use cases are, before going ahead. The open questions to answer during Stage 2 should probably go faster without dwelling too much. |
03:31 | <littledan> | Maybe we could have a break in the middle to go through the queue and answer questions about what the proposal is and why we want it, before going on |
03:34 | <littledan> | I think this lack of understanding all of the basics is a common pattern in proposals and doesn’t really reflect on the quality of your first presentation, or on Jordan. I heard similar lack of understanding of the motivation for a couple other proposals whose motivation had been extensively discussed. |
05:21 | <Justin Ridgewell> | Chengzhong Wu: I added you as an editor of the slide deck, if you want to add some slides on OTEL |
05:23 | <Justin Ridgewell> | You can use https://carbon.now.sh/aXSZjJ58QreRZMzMLEfP to generate code images (use 17 lines of code to get optimal size for slides) |
05:24 | <Justin Ridgewell> | I can explain a fair bit of usage and usecases between slides 2 and 3 |
05:31 | <Justin Ridgewell> | I think having a before and after might be helpful. Eg, normal use code that might be written, and how the code has to change (or rather doesn’t have to change) if we add OTEL |
05:32 | <Justin Ridgewell> | A bit like the logs example I used in the last deck, user code doesn’t need to be written with telemetry in mind, it just gets added seamlessly |
06:00 | <Chengzhong Wu> | Thanks, I'll add the slides on OTEL |
07:09 | <littledan> | Note, code images aren’t accessible by themselves but you can fix this by putting the code in the speakers notes |
15:31 | <littledan> | I wonder if the presentation should mention this layering question (about whether it should be a web API) and our plan to investigate it (by trying out implementing it in Chrome) |
15:32 | <littledan> | Chrome has put in a lot of time to articulate that concern, so we should probably speak to it somehow |
15:32 | <littledan> | (I am happy to explain this part if people want) |
15:56 | <Andreu Botella> | whether AsyncContext should be a web API ≠ whether Chrome (or browsers in general) should implement it as a web API |
15:56 | <Andreu Botella> | but yeah, it might be good to discuss that as well |
16:00 | <littledan> | whether AsyncContext should be a web API ≠ whether Chrome (or browsers in general) should implement it as a web API |
16:00 | <littledan> | But I believe Yoav if he says it is easier for him to implement and expose it as a web API |
16:00 | <Justin Ridgewell> | Slides updated, and I added the code as speaker notes |
16:01 | <Justin Ridgewell> | I wonder if the presentation should mention this layering question (about whether it should be a web API) and our plan to investigate it (by trying out implementing it in Chrome) |
16:43 | <Mathieu Hofman> | I for one would prefer seeing this in the language itself |
16:46 | <James M Snell> | While I think I'd prefer language level also, I'm sympathetic to the web API approach and it's likely easier for us to implement that way anyway. Specifically, even if it were in the language we'd want v8 to treat it largely as an embedder API anyway to make it easier for us to integrate with other embedder use cases |
16:49 | <Andreu Botella> | Could you be more specific about those other embedder use cases? |
17:00 | <littledan> | I for one would prefer seeing this in the language itself |
17:01 | <littledan> | While I think I'd prefer language level also, I'm sympathetic to the web API approach and it's likely easier for us to implement that way anyway. Specifically, even if it were in the language we'd want v8 to treat it largely as an embedder API anyway to make it easier for us to integrate with other embedder use cases |
17:04 | <Mathieu Hofman> | I need to articulate it better, but I think what I'll need is to extend the current API to support capturing both the registering context AND the resolving context, mostly to support join type use case like Promise.all . |
17:10 | <littledan> | I need to articulate it better, but I think what I'll need is to extend the current API to support capturing both the registering context AND the resolving context, mostly to support join type use case like |
17:11 | <Mathieu Hofman> | I don't believe it's that big, we already need to capture the rejection context for unhandled rejections |
17:14 | <Ben Newman (Apollo, @benjamn on GH)> | when a context is captured, how are we imagining it can be accessed later? |
17:14 | <Ben Newman (Apollo, @benjamn on GH)> | maybe it's an AsyncContext.wrap -bound function that runs other functions in that bound context? |
17:15 | <Mathieu Hofman> | I have a sketch I'll try to clean up |
17:16 | <Ben Newman (Apollo, @benjamn on GH)> | are you hoping these registering/resolving contexts will be preserved separately, or merged into one context? |
17:18 | <Mathieu Hofman> | I just need to be able to get the list of my values associated to the different executions if there are multiple. Aka asyncContext.getAll(): Array<T> |
17:19 | <Ben Newman (Apollo, @benjamn on GH)> | that's nice because it lets consuming/application code decide how to merge the multiple values |
17:20 | <littledan> | I don't understand in what situations we'd provide multiple contexts, and how we'd expose them. It seems to complicate what model we should use for embedders. So I'm looking forward to more concreteness and use cases here. |
17:20 | <Justin Ridgewell> | Instead of introducing a new API, why can't the context be used with an array of values? |
17:20 | <littledan> | Let's focus right now on what we need to get to Stage 2, and then open this question up more aftewards |
17:20 | <Justin Ridgewell> | AsyncContext<T> -> AsyncContext<T[]> |
17:21 | <Mathieu Hofman> |
|
17:21 | <Ben Newman (Apollo, @benjamn on GH)> | say a function is bound with AsyncContext.wrap in one context and then called in another context, where some of the AsyncContext values in the two contexts are not the same |
17:22 | <Mathieu Hofman> | Yes, I'm not trying to ask to change anything now, but it's something I'd like us to consider at stage 2 |
17:22 | <Andreu Botella> | I think we've been referring to those as "snapshots" or "maps", rather than "contexts" |
17:23 | <littledan> | OK, good, I'm looking forward to discussing this during Stage 2 once you're able to articulate the motivation |
17:23 | <Ben Newman (Apollo, @benjamn on GH)> | I'm all for avoiding the naked word "context" since it has so many overloads 😅 |
17:23 | <Ben Newman (Apollo, @benjamn on GH)> | I like "snapshots" here |
17:23 | <littledan> | maybe we should make, say, monthly AsyncContext calls which would give an easy slot for this discussion |
17:23 | <Mathieu Hofman> | The motivation is join like execution flows, of which the prominent example is Promise.all |
17:25 | <littledan> | It will be good to hear what problem you're trying to solve concretely, just to give an example. But I'm not asking for an answer right now. |
17:25 | <littledan> | when you do Promise.all(...).then, the execution context is clear (it's inherited from where Promise.all is called, and has nothing to do with the promises passed into Promise.all) |
17:25 | <Chengzhong Wu> | maybe we should make, say, monthly AsyncContext calls which would give an easy slot for this discussion |
17:27 | <Justin Ridgewell> | Also, the context that's active when the all'd promise's resolve() is invoked is only the last promise item to resolve. |
17:27 | <littledan> | Sure but this is also not used in the .then |
17:27 | <Ben Newman (Apollo, @benjamn on GH)> | ongoing/open question: what is the best data structure to represent the snapshots? |
17:28 | <Mathieu Hofman> | Right, I said it's the most obvious case, but basically it boils down to I need to learn the data associated with the context that resolved the promise |
17:28 | <littledan> | Yeah, I just am missing how this applies at all. So I'm looking forward to an example application, maybe just concretely what motivates you to start thinking about this (even if that's not a relevant motivation for everyone) |
17:31 | <Mathieu Hofman> | I'm actually surprised no one wants this. It's invaluable for diagnostics to understand the 2 causality paths on how you got where you are. Sure the registration path is often the most important, but the resolving path is also relevant. |
17:32 | <littledan> | OK, good, we're getting concrete: diagnostics. |
17:32 | <Mathieu Hofman> | That is one of my use cases yes |
17:32 | <littledan> | OK, looking forward to the writeup giving the set of your use cases; sorry for the distraction from Shu's presentation. |
17:33 | <Ben Newman (Apollo, @benjamn on GH)> | I think I share/want this use case, though I need more details for alignment |
17:34 | <Ben Newman (Apollo, @benjamn on GH)> | looking forward to digging into use cases during stage 2 |
17:34 | <bakkot> | This feels like something which shouldn't be shared to me? I don't expect that resolving a promise exposes anything to consumers of the promise except the value of the promise |
17:34 | <bakkot> | the async context which I was in when I did the resolving isn't theirs to see, it's mine |
17:34 | <littledan> | (My intuition matches bakkot's. I'm really baffled by this whole thread. That's why I'm asking for the motivation.) |
17:35 | <Mathieu Hofman> | to clarify, you can only learn information if you associated a value through your own asyncContext.run |
17:36 | <Ben Newman (Apollo, @benjamn on GH)> | my general motivation is that it should be feasible/cheap to keep around (potentially many) snapshots of past context, so you can inspect them for diagnostic purposes later (in development, especially) |
17:36 | <littledan> | wrap is the capability to snapshot contexts |
17:36 | <Ben Newman (Apollo, @benjamn on GH)> | I'm less opinionated about exactly which snapshots should be preserved/exposed/reported for promises |
17:37 | <Ben Newman (Apollo, @benjamn on GH)> | you might not want to allocate a function for every snapshot you capture? |
17:37 | <littledan> | It's frequently been posited that wrap may be less efficient than something like AsyncResource. I think this is something we should investigate in the context of an actual implementation during Stage 2. |
17:37 | <littledan> | It's clear that whatever mechanism we adopt here has to be "efficient enough" |
17:38 | <littledan> | this still doesn't seem to relate to the idea of multiple inheritance of AsyncContext stuff |
17:39 | <littledan> | It's frequently been posited that wrap is too inefficient, then we should definitely switch to a different API, maybe one more shaped like AsyncResource. |
17:40 | <bakkot> | Mathieu Hofman [moving off thread for readability]: also if you want to pass data from the promise-resolver to the consumer-of-the-resolved-promise, you can just... stick it in the promise? that seems like a very very different thing than the async contexts proposal |
17:40 | <Ben Newman (Apollo, @benjamn on GH)> | I think there are some other valid critiques of wrap , but it's possible to build many of the patterns you might want in userland, using only the current proposal |
17:40 | <Justin Ridgewell> | I agree, I think the promise should be responsible for passing data |
17:41 | <Mathieu Hofman> | I do not control intermediary code that creates and resolves these promises |
17:41 | <littledan> | you might not want to allocate a function for every snapshot you capture? |
17:41 | <littledan> | but wrap captures the expressiveness of snapshotting |
17:41 | <Mathieu Hofman> | Hence I cannot pass data throughout |
17:41 | <Justin Ridgewell> | And in most cases, because you own the AsyncContext that you invoked .run() on, it should be possible for you to chain onto the promise and pass the value |
17:41 | <Mathieu Hofman> | That's what async context is for in the first place |
17:41 | <ljharb> | wouldn't async context Just Work for what mathieu's talking about tho? shouldn't every promise spun off of "one that has the context" also have it? |
17:42 | <Mathieu Hofman> | As I mention in the thread, I do not control intermediary promises |
17:42 | <ljharb> | i'd assume that "adopting the state of another promise" also adopts/merges its context map or whatever the term is |
17:42 | <Justin Ridgewell> | No, but you control the promise as it's returned from your .run() |
17:42 | <Mathieu Hofman> | You're assuming the promise returned by run is related? |
17:43 | <Ben Newman (Apollo, @benjamn on GH)> | another issue is that wrap always ignores the current calling context of the function, when you might potentially want to merge the originally bound snapshot with the calling snapshot |
17:43 | <Justin Ridgewell> | Yes? If it doesn't escape the run, then the context should be whatever is currently in the context. |
17:43 | <Mathieu Hofman> | run creates an execution flow. the program can store / share promises of their own creation between flows |
17:43 | <Justin Ridgewell> | If it does escape the run, then you control it. |
17:44 | <littledan> | yeah, this is what I'd like to understand better: the use cases for merging. E.g., do you need to merge over all variables, or just one or two that you're thinking about. |
17:44 | <Mathieu Hofman> | I don't control promises resolved from that promise |
17:45 | <littledan> | This discussion is just too abstract for me to understand it. Let's pause and continue once we have this written example, as Mathieu Hofman has already promised. |
17:45 | <Mathieu Hofman> | I'm baffled by the request to explicitly attach information to promises given the whole premise of this proposal is based on the fact it's pretty much impossible to do that when you don't control the program |
17:48 | <Mathieu Hofman> | This "merging" is exactly what I have in mind |
17:48 | <Ben Newman (Apollo, @benjamn on GH)> | I'm hoping it's the latter, so merging can be cheap |
17:49 | <littledan> | (I just still don't understand what's being discussed) |
17:49 | <Ben Newman (Apollo, @benjamn on GH)> | if the merge is lazy, so the system does not have to eagerly merge all the variables up-front, you could potentially wait to see which variables are accessed, and resolve merge conflicts then |
17:52 | <littledan> | (yeah still confused; looking forward to concrete applications) |
17:58 | <bakkot> | I guess basically my thought process is:
maybe I'm missing something because we don't have a concrete example in front of us though. |
18:03 | <ljharb> | right but isn't the point of asynccontext that the person doing the resolving doesn't have to know to pass stuff down, it's just ambiently available as long as you have the key? |
18:04 | <ljharb> | in react, context's entire purpose is to replace prop drilling in a way that still avoids globals by requiring the context key, and this seems similar to me |
18:10 | <bakkot> | the thing which gets passed down is created when you create the callback, not when you resolve the promise |
18:10 | <bakkot> | capturing state when you resolve the promise is a very different thing than capturing state when you create the callback |
18:11 | <bakkot> | though again hard to talk about without something concrete |
18:28 | <ljharb> | +1 for a description (like symbols have) |
18:29 | <Lenz Weber-Tronic (phryneas)> | default value could be a getter method - so there could be a choice to either return a value, or throw a customized Error |
18:43 | <Chengzhong Wu> | default value could be a getter method - so there could be a choice to either return a value, or throw a customized Error |
18:45 | <Ben Newman (Apollo, @benjamn on GH)> | while defaultValue is super convenient, you could implement a subclass of AsyncContext that takes a defaultValue in the constructor and overrides the get method to return that default |
18:47 | <Lenz Weber-Tronic (phryneas)> | true. |
18:47 | <James M Snell> | eek... I'm not sure I'm a fan of this at all, especially with the values being just an array like this. It also greatly complicates the implementation which currently allows each context frame to be independent of all others, including the frames they inherited from |
19:12 | <rbuckton> | ljharb: agreed it needs to be easy, but I also believe we need ways of opting out (which snapshot() and wrap() help with). |
19:14 | <Ben Newman (Apollo, @benjamn on GH)> | congrats on stage 2!! 🥳 |
19:15 | <littledan> | Yay congrats!!! |
19:16 | <littledan> | The next steps for next meeting are clear: Focus completely on use cases |
19:16 | <Lenz Weber-Tronic (phryneas)> | 🎉 |
19:16 | <ljharb> | ljharb: agreed it needs to be easy, but I also believe we need ways of opting out (which |
19:16 | <littledan> | In our new regular meetings, we can dig down towards proposed answers to the many questions Justin explained. I think we demonstrated to committee that we are looking into details, but I think we should be proposing answers at the same time as we explain the issues. |
19:17 | <littledan> | so some time later than next meeting (where we focus on use cases) we can present details on those. |
19:27 | <James M Snell> | If someone really wanted multiple values like this they could easily do something like this with the current API...
|
19:34 | <Mathieu Hofman> | I do not control the sites where resolve is called, my example was an simplification |
19:38 | <Mathieu Hofman> | aka the run call is where I invoke the user program, which can do all sorts of things with promises. The important part is that the program will be invoked with different run contexts, and is thus able to "join" these. When they call back into my supervisor, I need to be able to look up all the associated run values, not just one (which usually would be the last in a join) |
19:39 | <rbuckton> | totally agree; i'd prefer the default be "include everything" and the methods to be for opting out wrap() is for. |
19:41 | <rbuckton> | "capture context at declaration" would also make snapshot completely useless. One of the benefits of snapshot() is that you can capture a "clean/empty" environment from which to start fresh, much like how some code today captures primordials early. |
19:50 | <rbuckton> | Unless I am misunderstanding your point about capturing context |
20:00 | <ljharb> | i mean like, arr.map(() => { /* context should be available here no matter when or where this function is called */ }) or replace arr.map( with someAPI( |
20:01 | <littledan> | "capture context at declaration" would also make |
20:03 | <rbuckton> | i mean like, .wrap() is intended to address. This whole feature is related to async flow through call stacks. A function declaration doesn't have a call stack. |
20:05 | <ljharb> | JS has deep dep trees, and code i don't control will be wrapping my functions, and/or calling them, all the time - it seems really unfortunate if i have to .wrap() manually in order to ensure my context works |
20:06 | <littledan> | JS has deep dep trees, and code i don't control will be wrapping my functions, and/or calling them, all the time - it seems really unfortunate if i have to .wrap is pretty obscure and things should inherit correctly for the most part. The exception is if you're somehow making your own system for an event loop/queueing/batching, for callbacks (promises are already handled comprehensively) |
20:07 | <littledan> | AsyncContext inherits automatically over deep dependency trees |
20:07 | <Ben Newman (Apollo, @benjamn on GH)> | should host environments use AsyncContext.wrap to wrap setTimeout and setInterval callbacks? (since they're the ones implementing those scheduling tools) |
20:07 | <littledan> | should host environments use |
20:15 | <Justin Ridgewell> | This should really not be the case; |
20:16 | <ljharb> | if that's actually the case then that's great! |
20:17 | <ljharb> | i look forward to being convinced by more examples in the future :-) perhaps a temporal-like "cookbook" would help? |
20:21 | <littledan> | i look forward to being convinced by more examples in the future :-) perhaps a temporal-like "cookbook" would help? |
20:24 | <bakkot> | is AsyncContext.wrap expected to be free if there's no active contexts? |
20:24 | <bakkot> | or as free as like a .bind or whatever, at least |
20:25 | <bakkot> | I would hope so but just want to confirm |
20:25 | <bakkot> | (otherwise we probably want a "is there an active context" so you can conditionally wrap) |
20:25 | <Andreu Botella> | I expect implementations could make it free |
20:26 | <Andreu Botella> | say, make the snapshot an optional/nullable weak map |
20:26 | <Justin Ridgewell> | It should be as fast as bind, but it could be optimized for memory to return a blank wrapper function if there's not context |
20:27 | <Justin Ridgewell> | But that could be done as an impl optimization, not something we need to explicitly design in the spec. |
20:28 | <Justin Ridgewell> | Note that the pre/post steps of the wrapper need to run, setting the global state to the empty context, so that can't go away |
21:32 | <Ben Newman (Apollo, @benjamn on GH)> | are we considering dependency injection as a potential application of AsyncContext ? |
21:33 | <littledan> | are we considering dependency injection as a potential application of |
21:33 | <ljharb> | that doesn't seem like a good strategic choice to market it that way |
21:34 | <Lenz Weber-Tronic (phryneas)> | People will definitely use it that way, after having done that for half a decade with a React Feature of the same name :) Although, technically, "DI" is the wrong term for it ^^ |
21:35 | <Ben Newman (Apollo, @benjamn on GH)> | another technical term with almost as many flavors as "context" |
21:35 | <littledan> | hehe look you've already been called wrong two different ways |
21:35 | <Lenz Weber-Tronic (phryneas)> | I had a twitter argument (I was calling it DI) with someone making very good arguments for "if you have to call out for it, it's not injection" - I still keep using the term DI though :) |
21:37 | <Ben Newman (Apollo, @benjamn on GH)> | cool, I appreciate that kind of feedback |
21:37 | <ljharb> | DI in JS is just function arguments, and i like it that way :-) |
21:38 | <littledan> | sometimes! |
21:39 | <Ben Newman (Apollo, @benjamn on GH)> | I like how static(ally analyzable) the parameter decorators for DI could be, compared to the more dynamic version you'd get with AsyncContext |
23:29 | <rbuckton> | is |
23:31 | <rbuckton> | DI in JS is just function arguments, and i like it that way :-) |
23:33 | <rbuckton> | I think DI patterns are far better for improving testability than hacking the module loader like Jest does to perform mocking. |