08:40
<Jack Works>
🤔
08:41
<Jack Works>
take use of AbortSignal into the language without bring EventTarget into the language. How do you think this
08:41
<Jack Works>
FYI, it happens at https://github.com/ajvincent/proposal-mass-proxy-revocation/pull/12
11:18
<annevk>
Jack Works: why not use the contract from DOM? As in you hand the abort signal some set of steps to run upon aborting
11:19
<annevk>
Jack Works: also a slot containing a slot seems dubious
11:45
<Jack Works>
Jack Works: why not use the contract from DOM? As in you hand the abort signal some set of steps to run upon aborting
DOM contract uses async callback which we want to avoid
11:49
<Jack Works>
And it's more dubious if we write ? Get(signal, "addEventListener")
12:06
<annevk>
Jack Works: that would be dubious, but there's a non-script-exposed mechanism which is what I was referring to
12:07
<annevk>
And if you want this to be useful beyond this API you'd need some kind of callback-style thing I'd think, not everything can lazily check whether the signal is aborted
12:34
<Jack Works>
Yeah I agree. Since it will be much harder to design a general signal (with callback) I think it's somehow ok since we don't have anywhere else that requires a signal
14:04
<rbuckton>
I've wanted to push cancellation forward, but I keep getting the same response from Domenic and he seems to have no interest in discussing the point further.
14:07
<rbuckton>
There are multiple issues with AbortController that make it not ideal for inclusion in ECMA-262
14:08
<rbuckton>
Yeah I agree. Since it will be much harder to design a general signal (with callback) I think it's somehow ok since we don't have anywhere else that requires a signal
There are other places where it would be useful
15:52
<annevk>
I think Domenic gave a reasonable way forward that Jack Works seems to be pursuing? You make the signal host-supplied and define a compatible not-script-visible contract with it.
15:55
<rbuckton>
Wasn't part of Jack Works concern that AbortSignal is an async callback? IIRC, AbortSignal also uses EventTarget for abort registration, which holds onto the callback even after the signal is aborted, which can prevent GC
15:56
<annevk>
You can add a callback to an AbortSignal but you can also lazily check whether it's aborted. I think Jack Works said that for their thing the latter is sufficient.
15:56
<annevk>
The callback is a spec-concept btw, it's not exposed. Any event listeners would be user code.
15:57
<rbuckton>
I was able to make a version of @esfx/async-canceltoken that works with the DOM but uses my proposed cancellation API, so I may end up going with Domenic's suggestion despite my reservations
15:58
<rbuckton>
But I find it very unfortunate we can't have an actual cancellation primitive in the core language because of this
15:59
<rbuckton>
(my "compatible" CancelToken just creates an AbortSignal and repoints the prototype so that it has the correct internal slots the DOM expects)
16:00
<annevk>
It doesn't seem inconceivable for JS to embrace EventTarget one day, but I personally don't get super excited about artificial boundaries imposed by standards bodies 🙂
16:00
<rbuckton>
And, honestly, that might be an alternative to a host hook as well...
16:01
<rbuckton>
It's not just EventTarget, it's the exceptions that are thrown as well. I had the same issue looking into bringing a portion of the Web crypto api to TC39
16:03
<rbuckton>
I'd like to bring a slew of async coordination primitive proposals to committee as well, but they need a cancellation primitive to be useful.
16:05
<rbuckton>
About 70% of @esfx/* are prototypes and proofs-of-concept for existing and future proposals
16:34
<Jack Works>
There are multiple issues with AbortController that make it not ideal for inclusion in ECMA-262

Yes. I cannot bring it into the language, but the massive proxy revocation needs a "revoke controller". The author of this proposal wants to add a synchronous revoke controller that is only used for Proxy revocation, but I don't want to have many different styles of controllers in the language meanwhile in the Host.

This is my answer, I didn't add it, but borrow it from the host

16:38
<Jack Works>
The callback is a spec-concept btw, it's not exposed. Any event listeners would be user code.
Yes. In our case it is enough. Once aborted, the engine can revoke and GC all proxies.
16:40
<Jack Works>
It doesn't seem inconceivable for JS to embrace EventTarget one day, but I personally don't get super excited about artificial boundaries imposed by standards bodies 🙂
I heard EventTarget is heavily connected to the whole DOM stuff...
16:41
<Jack Works>
It's not just EventTarget, it's the exceptions that are thrown as well. I had the same issue looking into bringing a portion of the Web crypto api to TC39
Yeah, DOMException right? 😂 Maybe "throw a host defined error"
16:46
<annevk>
(https://dom.spec.whatwg.org/#interface-eventtarget isn't really, though some stuff could be abstracted more I suppose.)
16:47
<annevk>
But fair point about DOMException, I wish they had used TypeError for everything, but alas, Java was important to the W3C back in the day.
16:47
<Luca Casonato>
I heard EventTarget is heavily connected to the whole DOM stuff...
It's pretty easy to implement in a DOM-less environment. We do it in Deno, and Node does it too. There is just no bubbling then.
17:04
<annevk>
And bubbling is somewhat abstracted, just not userland-exposed atm
17:20
<annevk>
rbuckton: FWIW, I tend to agree with Domenic that we don't want competing primitives. I see where you're coming from, but JS does not exist in a vacuum. We've had similar arguments about TextEncoder/TextDecoder and they'll undoubtedly resurface from time-to-time. In practice Deno and Node.js have demonstrated that these APIs can be adopted by other non-browser hosts.
17:26
<rbuckton>
Its unfortunate that AbortSignal couldn't have been written as a primitive that didn't leverage EventTarget, because it uses almost none of the EventTarget capabilities, and those that it does use are problematic (re: the GC issue)
17:28
<rbuckton>
Before Promise was adopted into ECMA-262 it was originally proposed as Future for DOM, and Future didn't leverage EventTarget (likely due to the prevalence of Promise/A+ libraries), but at least there was precedent for a non-EventTarget-based callback mechanism
17:29
<Kris Kowal>
Yeah, I remember Alex made an attempt to bypass TC39.
17:30
<Kris Kowal>
I know that our boss at Agoric, Dean Tribble had some experience with something like an abort primitive in Microsoft’s Midori. His opinion in general is that there should be a primitive for a cancellation token, since it affords certain optimization oportunities.
17:30
<rbuckton>
I understand there was frustration at the slow pace of TC39 wrt cancellation (I dealt with it myself when I took up the banner for a cancellation proposal), but I do think we could have resolved it back in 2017/2018.
17:31
<Kris Kowal>
Same. I tried to pave a cow path for cancellation in Q, but it proved to be a bad design. We’re in a better place with AbortController than we would have been getting cancellation into promises.
17:31
<rbuckton>
The cancellation proposal explainer has a number of quotes from Dean about cancellation.
17:32
<rbuckton>
I was opposed to cancelable promises from the get-go, hence https://github.com/tc39/proposal-cancellation.
17:34
<rbuckton>
The two things that held up cancellation were: a) WHATWG pushing hard on AbortController while I was trying to get CancelToken through committee, and b) Yehuda wanting some kind of syntax and automatic flow-through
17:34
<Kris Kowal>
As was Mark. I had to learn the hard way as usual, in order to know how to articulate why it was a bad idea: promises broadcast to multiple consumers, so no individual producer should be in a position to interfere with all of the consumers.
17:35
<rbuckton>
And the caller of the async operation should control when something can be canceled, not the consumers of the result. If you need the consumer to have that responsibility, you let them pass on the token explicitly (which is also why I was opposed to automatic flow-through)
17:36
<Kris Kowal>
Yeah, we’re on the same page.
17:36
<Kris Kowal>
I also naively believed all this could be done by passing a promise-to-throw as a cancel token, but Dean’s pretty adamant about being able to synchronously test the token. We’re all adamantly opposed to synchronous notification.
17:37
<Kris Kowal>
And also…opportunities to optimize if it’s special.
17:38
<Kris Kowal>
This topic came up at recent SES meetings regarding bulk proxy revocation, by the by. Bradley and Alex have a proposal brewing.
17:38
<rbuckton>
I would be fine with AbortSignal (and a host hook) if it didn't hold onto the registered callback after the signal was canceled (the GC issue). I know you can pass something like { once: true } to addEventListener, but that should be the default behavior for an event that can fire at most once (aside from user's manually dispatching the event)
17:38
<rbuckton>
This topic came up at recent SES meetings regarding bulk proxy revocation, by the by. Bradley and Alex have a proposal brewing.
That's also how the topic came up here :)
17:40
<rbuckton>
AbortController and AbortSignal are getting close to the API I proposed for CancelToken/Source back in 2018, but AbortSignal still feels way to heavy to me due to EventTarget.
17:41
<Kris Kowal>
Agreed.
17:43
<rbuckton>
I may just end up proposing the host hook approach for cancellation. If we ever decide an actual primitive is worthwhile (despite the duplication between 262 and DOM), we can always change it.
17:47
<rbuckton>
My current approach to "compatibility" for my own CancelToken (using an AbortSignal but changing its prototype) works with that approach in the DOM and NodeJS, since both use instance-specific state rather than calling methods on the prototype
17:48
<Kris Kowal>
And can coëxist under different names in every options bag that accepts either. Yeah, it’s a good approach.
17:49
<Kris Kowal>
I did not take the time to scroll up. Is there a possibility that cancel token and bulk revocation token are the same thing?
17:50
<rbuckton>
I do wonder if it makes sense to define a Host hooks for the whole thing, or move some of the logic into ECMA-262 with a host hook for allocating the prototype chain. That would allow us to internally spec and use cancellation without relying on a host hook
17:52
<rbuckton>
I did not take the time to scroll up. Is there a possibility that cancel token and bulk revocation token are the same thing?
I'd have to look at the proposal, but it sounds likely. I consider cancellation a "primitive" for a reason. It's not directly tied to async execution/promises, but is a general purpose mechanism for canceling things.
17:53
<rbuckton>
I think the issue with using AbortSignal for proxy revocation is the async callback nature of EventTarget. When you revoke a proxy you want it revoked immediately. Yes, a proxy can check if the AbortSignal has been aborted when the proxy is used, but that still introduces GC issues.
17:54
<rbuckton>
Ideally, when you would use abort for bulk revocation, not only should everything be revoked immediately but you should be able to clean up references to targets and proxy handlers so they can be garbage collected.
17:55
<rbuckton>
You could do both, but if EventTarget callbacks are async, there's a delay in that cleanup.
17:55
<Kris Kowal>
Agreed. I think we’re aligned and would support your proposal.
17:56
<rbuckton>
Something could be spec'd internally that could support both sync abort and deferred callbacks, with host hooks for things the DOM needs
17:56
<Kris Kowal>
It might also be worth scanning ahead for how deadline, timeout, and distributed context propagation might work with your primitive. Valid answers include 'no'
17:57
<rbuckton>
So maybe user code can't have synchronous notification of cancellation, but spec operations could.
17:57
<rbuckton>
It might also be worth scanning ahead for how deadline, timeout, and distributed context propagation might work with your primitive. Valid answers include 'no'
Is there something specific I should be looking at for these topics?
17:57
<Kris Kowal>
But derivation of child contexts and propagation of cancelation to children but not to parents would be a useful primitive. Also being able to walk the parent chain.
17:58
<Kris Kowal>
Yeah, Go has a context package that it uses for cancelation propagation and all these other things.
17:58
<rbuckton>
I'm not sure about walking the parent chain, unless its only walking the parent signals (and not the sources/controllers)
17:58
<Kris Kowal>
The one thing we’d want to do differently for sure would keeping the prototype powerless wrt timers.
17:58
<Kris Kowal>
Yes, just the signals.
17:59
<Kris Kowal>
https://pkg.go.dev/context
17:59
<rbuckton>
The one thing we’d want to do differently for sure would keeping the prototype powerless wrt timers.
Can you expand on this?
18:01
<Kris Kowal>
Yeah, suppose we had token, we would not want const {cancel, token: childToken} = token.withTimeout(100) because that would mean Token.prototype.withTimeout would provide a timing side-channel that’s hard to scour in a lockdown(). We already do this kind of work for Date.now and Math.random, but not growing that burden is ideal. For this reason, there’s no reasonable way to get Intl into a lockdown environment. The API has to be different almost entirely so the prototype can’t be shared. Something like Token.withTimeout(token, ms) is better for this purpose.
18:02
<Kris Kowal>
Pardon, const childToken = token.withTimeout(ms) and const {cancel, childToken} = token.spawn() is a more coherent strawman.
18:04
<Kris Kowal>
In any case, withTimeout is trivial to implement in userspace in terms of token, as long as you have something like token.spawn() to create a child that gets synchronously cancelled when its parent is cancelled.
18:05
<Kris Kowal>
Tragically, what you end up with is a subset of a Promise implementation, but that particular subset is good because it can be sync where Promise cannot 😅
18:06
<rbuckton>

The way I do it in @esfx/async-canceltoken would be something like:

const timeoutToken = CancelToken.timeout(ms);
const childToken = CancelToken.race([token, timeoutToken]);
18:06
<rbuckton>

Or just

const childToken = CancelToken.race([token, CancelToken.timeout(ms)]);
18:07
<rbuckton>
No new source is created in this case.
18:07
<Kris Kowal>
Yeah, likewise, it’s analogous to Promise.race([Promise.delay(ms, context), context]), where Promise.delay is also cancellable.
18:07
<rbuckton>
(see @esfx/async-delay, same idea)
18:09
<Kris Kowal>
I’d suggest CancelToken.timeout(ms, token) to cancel the timeout if the parent gets cancelled faster.
18:09
<rbuckton>
Though I really need to rename @esfx/async-canceltoken to just @esfx/canceltoken (since, like I said, cancellation isn't solely for async operations)
18:10
<rbuckton>
I’d suggest CancelToken.timeout(ms, token) to cancel the timeout if the parent gets cancelled faster.
That's fair, esp. since one of my biggest concerns has been GC and cleanup.
18:10
<rbuckton>
I also have a .close() (nee. Symbol.dispose) method on a cancellation source so you can indicate a token can never be canceled (and thus also clean up callbacks that will never be invoked).
22:09
<rbuckton>
Kris Kowal: I've started on a rough sketch of a host-hook version of cancellation: https://github.com/tc39/proposal-cancellation/issues/31
22:13
<rbuckton>
Its fairly simplistic, and nothing is exposed to the end user. Essentially, when the DOM would create an AbortController/AbortSignal, they would call CreateAbortCapability() and store that in the object, and replace some of the algorithms in AbortController with calls to AddAbortReaction and Abort. This gives the ECMAScript spec enough room to support cancellation as a Spec-only primitive regardless of host, but allows the host to control operations (such as the DOM triggering an abort event), etc.