Skip to content

Commit c887dc3

Browse files
committed
Clean up yield inheritance
1. Key the scheduling state based on the {{Scheduler}} to prevent leaking it across (potentially cross-origin) windows. This changes the event loop's continuation state to be a small wrapper around a map. The continuation state is propagated in the same way, but the scheduler state is unique to the scheduler and not shared. In practice there will only be one entry in this map (a task or microtask can only have originated from one task), but the mechanism is generic enough to support other use cases, implementations can optimize this, and the key/value mapping hopefully makes the isolation clear. Alternatively, we could propagate only the state for the current scheduler, but we don't always know the current scheduler, e.g. in "queue a microtask", and this model is different enough from AsyncContext and Chrome's "Task Attribution" that we'd need a separate mechanism, which is a performance concern. The main behavioral difference is how propagating is handled in the case of A --> (B microtask) --> A. With this approach, the context is preserved in the second call to A, which matches the synchronous behavior of A --> calls B --> calls A. 2. Propagate the current scheduling state in "queue a microtask", unless coming from JavaScript, in which case the propagation is handled by the abstract job closure. Previously, the state would be inherited only if it wasn't reset by another microtask or after the postTask callback ran. This fixes the inconsistency, making directly scheduled microtasks match microtasks originating from JavaScript.
1 parent 83873f3 commit c887dc3

File tree

2 files changed

+88
-16
lines changed

2 files changed

+88
-16
lines changed

spec/patches.md

+54-10
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,30 @@ determine task execution order across [=scheduler task queues=] of the same {{Ta
3232
all {{Scheduler}}s associated with the same [=event loop=]. A timestamp would also suffice as long
3333
as it is guaranteed to be strictly increasing and unique.
3434

35-
Add: An [=event loop=] has a <dfn for="event loop">current scheduling state</dfn> (a [=scheduling
36-
state=] or null), which is initialized to null.
35+
Add: An [=event loop=] has a <dfn for="event loop">current continuation state</dfn> (a
36+
[=continuation state=] or null), which is initially null.
37+
38+
Add the following algorithms:
39+
40+
<div algorithm>
41+
To <dfn>set the continuation state value</dfn> for |key| to |value| given an |eventLoop| (an
42+
[=event loop=]):
43+
44+
1. If |eventLoop|'s [=event loop/current continuation state=] is null, then set |eventLoop|'s
45+
[=event loop/current continuation state=] to a new [=continuation state=].
46+
1. Let |continuationState| be |eventLoop|'s [=event loop/current continuation state=].
47+
1. Assert: |continuationState|'s [=continuation state/state map=][|key|] does not [=map/exist=].
48+
1. Set |continuationState|'s [=continuation state/state map=][|key|] to |value|.
49+
</div>
50+
51+
<div algorithm>
52+
To <dfn>get the continuation state value</dfn> for |key| given an |eventLoop| (an [=event loop=]):
53+
54+
1. Let |continuationState| be |eventLoop|'s [=event loop/current continuation state=].
55+
1. If |continuationState| is not null and |continuationState|'s
56+
[=continuation state/state map=][|key|] [=map/exists=], then return |continuationState|'s
57+
[=continuation state/state map=][|key|], otherwise return null.
58+
</div>
3759

3860
### <a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model">Event loop: processing model</a> ### {#sec-patches-html-event-loop-processing}
3961

@@ -80,33 +102,55 @@ Issue: The |taskQueue| in this step will either be a [=set=] of [=tasks=] or a [
80102
*roughly* compatible. Ideally, there would be a common task queue interface that supports a `pop()`
81103
method that would return a plain [=task=], but that would involve a fair amount of refactoring.
82104

105+
### <a href="https://html.spec.whatwg.org/#queuing-tasks">Event Loop: Queuing Tasks</a> ### {#sec-patches-html-queuing-tasks}
106+
107+
Change the <a href="">To queue a microtask</a> algorithm to accept an optional boolean
108+
|ignoreContinuationState| (default false).
109+
110+
Change Step 5 to the following:
111+
112+
1. Let |continuationState| be |eventLoop|'s [=event loop/current continuation state=] if
113+
|ignoreContinuationState| is false, otherwise null.
114+
1. Set <var ignore=''>microtask</var>'s <a attribute for="task">steps</a> to the following:
115+
1. If |ignoreContinuationState| is false, then set |eventLoop|'s
116+
[=event loop/current continuation state=] to |continuationState|.
117+
1. Run <var ignore=''>steps</var>.
118+
1. If |ignoreContinuationState| is false, then set |eventLoop|'s
119+
[=event loop/current continuation state=] to null.
120+
83121
### <a href="https://html.spec.whatwg.org/multipage/webappapis.html#hostmakejobcallback">HostMakeJobCallback(callable)</a> ### {#sec-patches-html-hostmakejobcallback}
84122

85123
Add the following before step 5:
86124

87125
1. Let |event loop| be <var ignore=''>incumbent settings<var>'s
88126
[=environment settings object/realm=]'s [=realm/agent=]'s [=agent/event loop=].
89-
1. Let |state| be |event loop|'s [=event loop/current scheduling state=].
127+
1. Let |state| be |event loop|'s [=event loop/current continuation state=].
90128

91129
Modify step 5 to read:
92130

93131
1. Return the <span>JobCallback Record</span> { \[[Callback]]: <var ignore=''>callable</var>,
94132
\[[HostDefined]]: { \[[IncumbentSettings]]: <var ignore=''>incumbent settings</var>,
95133
\[[ActiveScriptContext]]: <var ignore=''>script execution context</var>,
96-
\[[SchedulingState]]: |state| } }.
134+
\[[ContinuationState]]: |state| } }.
97135

98136
### <a href="https://html.spec.whatwg.org/multipage/webappapis.html#hostcalljobcallback">HostCallJobCallback(callback, V, argumentsList)</a> ### {#sec-patches-html-hostcalljobcallback}
99137

100138
Add the following steps before step 5:
101139

102140
1. Let |event loop| be <var ignore=''>incumbent settings<var>'s
103141
[=environment settings object/realm=]'s [=realm/agent=]'s [=agent/event loop=].
104-
1. Set |event loop|'s [=event loop/current scheduling state=] to
105-
<var ignore=''>callback</var>.\[[HostDefined]].\[[SchedulingState]].
142+
1. Set |event loop|'s [=event loop/current continuation state=] to
143+
<var ignore=''>callback</var>.\[[HostDefined]].\[[ContinuationState]].
106144

107145
Add the following after step 7:
108146

109-
1. Set |event loop|'s [=event loop/current scheduling state=] to null.
147+
1. Set |event loop|'s [=event loop/current continuation state=] to null.
148+
149+
### <a href="https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuepromisejob">HostEnqueuePromiseJob(job, realm)</a> ### {#sec-patches-html-hostenqueuepromisejob}
150+
151+
Change step 2 to:
152+
153+
1. Queue a microtask to perform the following steps with |ignoreContinuationState| set to true:
110154

111155
## <a href="https://w3c.github.io/requestidlecallback/">`requestIdleCallback()`</a> ## {#sec-patches-requestidlecallback}
112156

@@ -118,9 +162,9 @@ Add the following step before step 3.3:
118162
1. Let |state| be a new [=scheduling state=].
119163
1. Set |state|'s [=scheduling state/priority source=] to the result of [=creating a fixed priority
120164
unabortable task signal=] given "{{TaskPriority/background}}" and |realm|.
121-
1. Let |event loop| be |realm|'s [=realm/agent=]'s [=agent/event loop=].
122-
1. Set |event loop|'s [=event loop/current scheduling state=] to |state|.
165+
1. Let |scheduler| be the {{Scheduler}} whose [=relevant realm=] is |realm|.
166+
1. [=Set the current scheduling state=] for |scheduler| to |state|.
123167

124168
Add the following after step 3.3:
125169

126-
1. Set |event loop|'s [=event loop/current scheduling state=] to null.
170+
1. Set |event loop|'s [=event loop/current continuation state=] to null.

spec/scheduling-tasks.md

+34-6
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,22 @@ A <dfn>scheduler task queue</dfn> is a [=struct=] with the following [=struct/it
175175
A <dfn>scheduling state</dfn> is a [=struct=] with the following [=struct/items=]:
176176

177177
: <dfn for="scheduling state">abort source</dfn>
178-
:: An {{AbortSignal}} object or, initially null.
178+
:: An {{AbortSignal}} object or null, initially null.
179179
: <dfn for="scheduling state">priority source</dfn>
180180
:: A {{TaskSignal}} object or null, initially null.
181181

182182
<br/>
183183

184+
A <dfn>continuation state</dfn> is a [=struct=] with the following [=struct/items=]:
185+
186+
: <dfn for="continuation state">state map</dfn>
187+
:: An initially empty [=map=].
188+
189+
Note: The [=continuation state/state map=] can be implemented as weak map if its keys are
190+
implemented as garbage collected objects.
191+
192+
<br/>
193+
184194
A <dfn>task handle</dfn> is a [=struct=] with the following [=struct/items=]:
185195

186196
: <dfn for="task handle">task</dfn>
@@ -289,6 +299,25 @@ A <dfn>task handle</dfn> is a [=struct=] with the following [=struct/items=]:
289299

290300
### Scheduling Tasks and Continuations ### {#sec-scheduler-alg-scheduling-tasks-and-continuations}
291301

302+
<div algorithm>
303+
To <dfn>set the current scheduling state</dfn> for |scheduler| (a {{Scheduler}}) to |state| (a
304+
[=scheduling state=]):
305+
306+
1. Let |eventLoop| be |scheduler|'s [=relevant agent=]'s [=agent/event loop=].
307+
1. [=Set the continuation state value=] for |scheduler| to |state| given |eventLoop|.
308+
309+
Note: Any key can be used for the [=continuation state/state map=] as long as it is unique to the
310+
{{Scheduler}}.
311+
</div>
312+
313+
<div algorithm>
314+
To <dfn>get the current scheduling state</dfn> for |scheduler| (a {{Scheduler}}):
315+
316+
1. Let |eventLoop| be |scheduler|'s [=relevant agent=]'s [=agent/event loop=].
317+
1. Return the result of [=getting the continuation state value=] for |scheduler| given
318+
|eventLoop|.
319+
</div>
320+
292321
<div algorithm>
293322
To <dfn>schedule a postTask task</dfn> for {{Scheduler}} |scheduler| given a
294323
{{SchedulerPostTaskCallback}} |callback| and {{SchedulerPostTaskOptions}} |options|:
@@ -316,12 +345,12 @@ A <dfn>task handle</dfn> is a [=struct=] with the following [=struct/items=]:
316345
for |scheduler| given |state|'s [=scheduling state/priority source=] and false.
317346
1. [=Schedule a task to invoke an algorithm=] for |scheduler| given |handle| and the following
318347
steps:
319-
1. Let |event loop| be the |scheduler|'s [=relevant agent=]'s [=agent/event loop=].
320-
1. Set |event loop|'s [=event loop/current scheduling state=] to |state|.
348+
1. Let |eventLoop| be the |scheduler|'s [=relevant agent=]'s [=agent/event loop=].
349+
1. [=Set the current scheduling state=] for |scheduler| to |state|.
321350
1. Let |callbackResult| be the result of [=invoking=] |callback| with « » and "`rethrow`".
322351
If that threw an exception, then [=reject=] |result| with that. Otherwise, [=resolve=]
323352
|result| with |callbackResult|.
324-
1. Set |event loop|'s [=event loop/current scheduling state=] to null.
353+
1. Set |eventLoop|'s [=event loop/current continuation state=] to null.
325354
1. Let |delay| be |options|["{{SchedulerPostTaskOptions/delay}}"].
326355
1. If |delay| is greater than 0, then [=run steps after a timeout=] given |scheduler|'s [=relevant
327356
global object=], "`scheduler-postTask`", |delay|, and the following steps:
@@ -340,8 +369,7 @@ Issue: [=Run steps after a timeout=] doesn't necessarily account for suspension;
340369
To <dfn>schedule a yield continuation</dfn> for {{Scheduler}} |scheduler|:
341370

342371
1. Let |result| be [=a new promise=].
343-
1. Let |inheritedState| be the |scheduler|'s [=relevant agent=]'s [=agent/event loop=]'s
344-
[=event loop/current scheduling state=].
372+
1. Let |inheritedState| be the result of [=getting the current scheduling state=] for |scheduler|.
345373
1. Let |abortSource| be |inheritedState|'s [=scheduling state/abort source=] if |inheritedState|
346374
is not null, or otherwise null.
347375
1. If |abortSource| is not null and |abortSource| is [=AbortSignal/aborted=], then [=reject=]

0 commit comments

Comments
 (0)