-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Normative: Add Atomics.waitAsync #3049
Conversation
syg
commented
Apr 21, 2023
•
edited
Loading
edited
- Shipped in Chrome 87 and Safari 16.4.
- HTML PR to implement hooks.
- test262
spec.html
Outdated
<p>At any time, an ECMAScript implementation may perform the following steps atomically:</p> | ||
<emu-alg> | ||
1. Let _now_ be the time value (UTC) identifying the current time. | ||
1. For each WaiterList _WL_, do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So every WaiterList in every agent cluster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...yeah?
spec.html
Outdated
<emu-alg> | ||
1. Let _now_ be the time value (UTC) identifying the current time. | ||
1. For each WaiterList _WL_, do | ||
1. Perform EnterCriticalSection(_WL_). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first step of EnterCriticalSection
asserts that the surrounding agent is not in the critical section for any WaiterList Record, so there's a couple problems:
- As it stands, there's no surrounding agent.
- Even if there were, you haven't ensured that it's not in any critical section.
Also, it seems odd to me that an agent would enter the critical section of a WaiterList Record that it isn't waiting on.
The second step of EnterCriticalSection
says to wait until no agent is in _WL_
's critical section, so it's unclear how these steps can be performed atomically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't there a surrounding agent? This runs in some agent.
Good call on the "atomically" thing, which seems to be wrong since the critical section already ensures mutual exclusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't there a surrounding agent? This runs in some agent.
"While an agent's executing thread executes jobs, the agent is the surrounding agent for the code in those jobs." Are these steps being performed as part of a job?
It seems wrong that one agent in one agent cluster would be tweaking WaiterLists in all agent clusters. I would expect that an agent can't affect any agent cluster other than its own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh agent cluster. Sure we can qualify it as "intra agent cluster" but that seems definitionally true. WaiterLists are associated with SAB blocks, so how could an agent have gotten WaiterLists from another agent cluster?
This is the same thing as whatever finalization registry is doing, which is admittedly handwavy but I don't really see a problem. I don't think there's any confusion there that the set of non-live objects S is across agents, just like here it's not across agent clusters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"While an agent's executing thread executes jobs, the agent is the surrounding agent for the code in those jobs." Are these steps being performed as part of a job?
...yes, sure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any agent at any time can check if any async waiter in its agent cluster has had its timeout expired, and if so, call the host hook.
Ah, via NotifyWaiter
, got it.
The Job execution guarantees are for the enqueued Job that resolves the promise, not for the code that calls the host to enqueue the Job.
(That's assuming that the latter isn't itself a job, which I gather is how you're thinking about it now. But earlier, you indicated it was a job, so that's what I based my question on.)
So, updated question: if the algorithm of "Processing Model of Atomics.waitAsync" isn't part of a job, then the job-execution guarantees don't apply, so how does it satisfy EnterCriticalSection
's assertion that the surrounding agent is not in any critical section?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, updated question: if the algorithm of "Processing Model of Atomics.waitAsync" isn't part of a job, then the job-execution guarantees don't apply, so how does it satisfy EnterCriticalSection's assertion that the surrounding agent is not in any critical section?
Yeah, that's true, there's no re-entrancy guard currently.
We can make those steps have job-like guarantees without it making a job, I guess? Another alternative formulation is perhaps to do away with the current host hook entirely and directly encode the the timeout and promise fulfillment into the "processing model". Something like,
At any time in an agent _agent_, when the execution context stack is empty, an implementation may
perform the following steps atomically:
1. For each WaiterList _WL_, do
1. Enter critical section for _WL_.
1. For each _waiter_ in _WL_ whose [[AgentSignifier]] is _agent_'s signifier, do
1. If timeout expired, resolve the [[PromiseCapability]]. (We can do this directly since it's the right agent.)
1. Exit the critical section for _WL_.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make those steps have job-like guarantees without it making a job, I guess?
But then why not just say it is a job? I.e., is there something about the definition/handling of a job that needs to not apply to this algorithm?
Another alternative formulation is perhaps to do away with the current host hook entirely and directly encode the the timeout and promise fulfillment into the "processing model".
[...]
1. If timeout expired, resolve the [[PromiseCapability]]. (We can do this directly since it's the right agent.)
So that 'resolve' would be something like:
Call(_waiter_.[[PromiseCapability]].[[Resolve]], *undefined*, « *"timed-out"* »).
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then why not just say it is a job? I.e., is there something about the definition/handling of a job that needs to not apply to this algorithm?
We can. My initial hesitation was from how the HostEnqueueResolveInAgentJob hook was originally formulated. All that job does is to resolve the promise in the right agent, because an agent can only resolve its own promises. So to make the timer watchdog thing itself a job, like HostEnqueueWatchdogTimerJobForSomeWaiter, we'd either need 2 jobs or something more complicated, like a requirement that the host re-enqueue the job if the timer is not yet expired.
The HostEnqueueWatchdogTimerJobForSomeWaiter thing is also fine by me, and is probably closer to how real implementations work. Now that I've talked through it, I think that version is the cleanest on top of our existing machinery without having to modify much.
So that 'resolve' would be something like:
Call(waiter.[[PromiseCapability]].[[Resolve]], undefined, « "timed-out" »).
?
Yep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored the timeout thing to a Job and removed the "processing model" section entirely. PTAL.
@@ -11982,12 +11982,12 @@ <h1>InitializeHostDefinedRealm ( ): either a normal completion containing ~unuse | |||
<emu-clause id="sec-agents"> | |||
<h1>Agents</h1> | |||
|
|||
<p>An <dfn id="agent" variants="agents">agent</dfn> comprises a set of ECMAScript execution contexts, an execution context stack, a running execution context, an <dfn id="agent-record" variants="Agent Records">Agent Record</dfn>, and an <dfn id="executing-thread" variants="executing threads">executing thread</dfn>. Except for the executing thread, the constituents of an agent belong exclusively to that agent.</p> | |||
<p>An agent's executing thread executes a job on the agent's execution contexts independently of other agents, except that an executing thread may be used as the executing thread by multiple agents, provided none of the agents sharing the thread have an Agent Record whose [[CanBlock]] field is *true*.</p> | |||
<p>An <dfn id="agent" variants="agents">agent</dfn> comprises a set of ECMAScript execution contexts, an execution context stack, a running execution context, an <dfn id="agent-record" variants="Agent Records">Agent Record</dfn>, and an <dfn id="executing-thread" variants="executing threads">executing thread</dfn>. Except for the executing thread, Shared Data Blocks, and specification values related to Shared Data Blocks, the constituents of an agent belong exclusively to that agent.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This insertion is odd. Shared Data Blocks etc weren't just listed as constituents of an agent, so it doesn't make sense to say that they're among the constituents that aren't exclusive to the agent.
I think it would be more helpful to expand the Agent Clusters section to say that every Shared Data Block etc 'belongs to' an agent cluster. (Or, an agent cluster comprises zero or more Shared Data Blocks, WaiterList Records, Waiter Records, whatever.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion, I've added a list of spec values to the agent cluster section.
<p>An <dfn id="agent" variants="agents">agent</dfn> comprises a set of ECMAScript execution contexts, an execution context stack, a running execution context, an <dfn id="agent-record" variants="Agent Records">Agent Record</dfn>, and an <dfn id="executing-thread" variants="executing threads">executing thread</dfn>. Except for the executing thread, the constituents of an agent belong exclusively to that agent.</p> | ||
<p>An agent's executing thread executes a job on the agent's execution contexts independently of other agents, except that an executing thread may be used as the executing thread by multiple agents, provided none of the agents sharing the thread have an Agent Record whose [[CanBlock]] field is *true*.</p> | ||
<p>An <dfn id="agent" variants="agents">agent</dfn> comprises a set of ECMAScript execution contexts, an execution context stack, a running execution context, an <dfn id="agent-record" variants="Agent Records">Agent Record</dfn>, and an <dfn id="executing-thread" variants="executing threads">executing thread</dfn>. Except for the executing thread, Shared Data Blocks, and specification values related to Shared Data Blocks, the constituents of an agent belong exclusively to that agent.</p> | ||
<p>An agent's executing thread executes code on the agent's execution contexts independently of other agents, except that an executing thread may be used as the executing thread by multiple agents, provided none of the agents sharing the thread have an Agent Record whose [[CanBlock]] field is *true*.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain your thinking behind this change and the one below?
I tried writing a comment, but I'm having trouble without knowing your intent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention is to make this section more generic than about jobs. The history wrt jobs IIRC was that we had a very generic job queue scheduler system that no embedder actually used. This was dead code that no host used, so it was very misleading, and we removed it. So when agents talk about "jobs" I'm pretty sure they're talking about this old notion of a job that has been ripped out.
Since then we've redefined a "Job" to be something more specific: an Abstract Closure that's given to the host to schedule and run. So it seems odd to me to say that "surrounding agent" is only well-defined when an agent is executing a Job. The initial script evaluation, for instance, is not part of a Job in the Abstract Closure sense.
So my intention here was to make the notion of agent executing JS code to be more broad than just the restricted notion of Job. I think "executes code" suffices to broad enough. I had originally thought something like "evaluating ECMAScript code", but I want the "surrounding agent" notion to be well-defined in the waitAsync timer stuff, which is arguably not executing ECMAScript code. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks.
I think "executes code" suffices to broad enough. I had originally thought something like "evaluating ECMAScript code", but I want the "surrounding agent" notion to be well-defined in the waitAsync timer stuff, which is arguably not executing ECMAScript code. Thoughts?
In the spec, the word "code" (outside of "code unit" and "code point") almost always means ECMAScript source code anyway, so "executes code" doesn't have the meaning you want. I think "executes algorithmic steps" would be better. (You could maybe drop the "algorithmic" for subsequent uses.)
<p>An agent's executing thread executes code on the agent's execution contexts independently of other agents, except that an executing thread may be used as the executing thread by multiple agents, provided none of the agents sharing the thread have an Agent Record whose [[CanBlock]] field is *true*.</p> | |
<p>An agent's executing thread executes algorithmic steps on the agent's execution contexts independently of other agents, except that an executing thread may be used as the executing thread by multiple agents, provided none of the agents sharing the thread have an Agent Record whose [[CanBlock]] field is *true*.</p> |
or maybe "executes algorithms".
The intention is to make this section more generic than about jobs.
I'm okay with the idea that an agent can execute a slightly broader class of thing than just jobs, but I'd like it to be clear that a job is one of the things it can execute. Currently, the only explicit connection between 'agent' and 'job' is in this para and the next, and this PR is dissolving that connection. Maybe somewhere near the definition of job, it could say that a job is executed by an agent. (See also issue #2642.)
The history wrt jobs IIRC was that we had a very generic job queue scheduler system that no embedder actually used. This was dead code that no host used, so it was very misleading, and we removed it.
Yup, that was PR #1597.
So when agents talk about "jobs" I'm pretty sure they're talking about this old notion of a job that has been ripped out.
Well, except #1597 also changed these paragraphs, so presumably they were modified to talk about the new notion of a job.
Since then we've redefined a "Job" to be something more specific: an Abstract Closure that's given to the host to schedule and run.
That was also done in #1597.
So it seems odd to me to say that "surrounding agent" is only well-defined when an agent is executing a Job. The initial script evaluation, for instance, is not part of a Job in the Abstract Closure sense.
I agree it's odd. (#1597 dropped ScriptEvaluationJob without saying that an agent could execute non-jobs like ScriptEvaluation.)
But if an agent can execute non-jobs, are there any restrictions on when/how it executes them? (E.g., can an agent start executing a ScriptEvaluation while another is in progress?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a bit to the Job section about being executed by agents.
Also, side note: I like how |
Good idea, we should do something similar for FinalizationRegistryCleanupJob. |
… a Job ... by hoisting the job-internal steps out to the caller. This brings it in line with the statement (in "9.5 Jobs and Host Operations to Enqueue Jobs") that job-scheduling host hooks "accept a Job Abstract Closure as the parameter". See tc39#3049 (comment) and following.
On second thought, I don't think HTML's HostEnqueueFinalizationRegistryCleanupJob would like that change: it appears to need access to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than some pretty minor comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments. Otherwise LGTM.
6da1bbe
to
3330e23
Compare