From 31518784d824b076ab58520dc3dc834813abba64 Mon Sep 17 00:00:00 2001 From: Scott Haseley Date: Mon, 27 Mar 2023 16:52:52 -0700 Subject: [PATCH 1/2] Make prioritization stricter --- spec/index.bs | 1 + spec/patches.md | 52 ++++++++++++++++++++++++++++++---------- spec/scheduling-tasks.md | 42 +++++++++++++------------------- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/spec/index.bs b/spec/index.bs index 78a380d..417ef4c 100644 --- a/spec/index.bs +++ b/spec/index.bs @@ -89,6 +89,7 @@ spec: HTML; urlPrefix: https://html.spec.whatwg.org/multipage/; text: associated Document; for:Window; url: window-object.html#concept-document-window type: dfn text: runnable; for:task; url: webappapis.html#concept-task-runnable + text: posted message task source; url: web-messaging.html#posted-message-task-source spec: dom; urlPrefix: https://dom.spec.whatwg.org/#; type: dfn; text: signal; for: AbortController; url: abortcontroller-signal diff --git a/spec/patches.md b/spec/patches.md index c855ba8..bf1def0 100644 --- a/spec/patches.md +++ b/spec/patches.md @@ -28,28 +28,54 @@ a specific [=task queue=]. With: For each [=event loop=], every [=task source=] that is not a [=scheduler task source=] must be associated with a specific [=task queue=]. +An [=event loop=] object has a numeric next enqueue order, which is +is initialized to 1. + +Note: The [=event loop/next enqueue order=] is a strictly increasing number that is used to +determine task execution order across [=scheduler task queues=] of the same {{TaskPriority}} across +all {{Scheduler}}s associated with the same [=event loop=]. A timestamp would also suffice as long +as it is guaranteed to be strictly increasing and unique. + ### Event loop: processing model ### {#sec-patches-html-event-loop-processing} Add the following steps to the event loop processing steps, before step 1: 1. Let |queues| be the [=set=] of the [=event loop=]'s [=task queues=] that contain at least one runnable task. - 1. Let |schedulers| be the [=set=] of all {{Scheduler}} objects whose - [=relevant agent's=] [=agent/event loop=] is this event loop and that - [=have a runnable task=]. - 1. If |schedulers| and |queues| are both [=list/empty=], skip to the + 1. Let |scheduler queue| be the result of + [=selecting the next scheduler queue from all schedulers=] given the [=event loop=]. + 1. If |scheduler queue| is null and |queues| is [=list/empty=], skip to the microtasks step below. -Modify step 1 to read: +Modify step 1 to be the following steps: + 1. If |scheduler queue| is not null: + 1. If |scheduler queue|'s [=scheduler task queue/priority=] is {{TaskPriority/user-blocking}} + and |queues| is not [=list/empty=], then [=set/remove=] from |queues| any [=task queue=] + whose [=task source=] is in «[=timer task source=], [=posted message task source=]». + 1. If |scheduler queue|'s [=scheduler task queue/priority=] is {{TaskPriority/background}}, set + |scheduler queue| to null. + 1. Let |taskQueue| be one of the following, chosen in an [=implementation-defined=] manner: + * If |queues| is not [=list/empty=], one of the [=task queues=] in |queues|, chosen in an + [=implementation-defined=] manner. + * |scheduler queue|'s [=scheduler task queue/tasks=], if |scheduler queue| is not null. + - 1. Let |taskQueue| be one of the following, chosen in an - [=implementation-defined=] manner: - * If |queues| is not [=list/empty=], one of the [=task queues=] in |queues|, - chosen in an [=implementation-defined=] manner. - * If |schedulers| is not [=list/empty=], the result of - [=selecting the task queue of the next scheduler task=] from one of the - {{Scheduler}}s in |schedulers|, chosen in an [=implementation-defined=] - manner. +Note: This section defines the integration of {{Scheduler}} tasks and the [=event loop=]. +

+{{TaskPriority/background}} tasks will only run if no other tasks are runnable. +

+{{TaskPriority/user-visible}} tasks are meant to be scheduled in a similar way to existing +scheduling mechanisms, specifically {{WindowOrWorkerGlobalScope/setTimeout()|setTimeout(0)}} and +same-window {{Window/postMessage(message, options)|postMessage()}}. While the relative priority of +these is unspecified, {{TaskPriority/user-blocking}} tasks are specified to have a higher priority +in the event loop than these scheduling methods. +

+The intention is for {{TaskPriority/user-blocking}} tasks to be given an increased priority in the +[=event loop=], reflecting the developer's indication of the task importance. UAs have flexibility +to prioritize between {{TaskPriority/user-blocking}} tasks other task sources and, but are +encouraged to give some increased priority to the former. One possible strategy is to give +{{TaskPriority/user-blocking}} tasks priority over everything except user input and rendering (to +ensure the UI remains responsive). Issue: The |taskQueue| in this step will either be a [=set=] of [=tasks=] or a [=set=] of [=scheduler tasks=]. The steps that follow only [=set/remove=] an diff --git a/spec/scheduling-tasks.md b/spec/scheduling-tasks.md index 0241da1..15c5ce8 100644 --- a/spec/scheduling-tasks.md +++ b/spec/scheduling-tasks.md @@ -133,22 +133,9 @@ single per-{{TaskPriority}} [=scheduler task queue=], and move tasks between [=scheduler task queues=] in response to a {{TaskSignal}}'s [=TaskSignal/priority=] changing, inserting based on [=scheduler task/enqueue order=]. This approach would simplify -[=selecting the task queue of the next scheduler task=], but make priority +[=selecting the next scheduler queue from all schedulers=], but make priority changes more complex. - -A {{Scheduler}} object has a numeric next enqueue order -which is initialized to 1. - -Note: The [=Scheduler/next enqueue order=] is a strictly increasing number that -is used to determine task execution order across [=scheduler task queues=] of the -same {{TaskPriority}} within the same {{Scheduler}}. A logically equivalent -alternative would be to place the [=Scheduler/next enqueue order=] on the -[=event loop=], since the only requirements are that the number be strictly -increasing and not be repeated within a {{Scheduler}}. - -Issue: Would it be simpler to just use a timestamp here? - The postTask(|callback|, |options|) method steps are to return the result of [=scheduling a postTask task=] for [=this=] given |callback| and |options|. @@ -294,8 +281,8 @@ see [whatwg/html#5925](https://github.com/whatwg/html/issues/5925). 1. Let |global| be the [=relevant global object=] for |scheduler|. 1. Let |document| be |global|'s associated `Document` if |global| is a {{Window}} object; otherwise null. - 1. Let |enqueue order| be |scheduler|'s [=Scheduler/next enqueue order=]. - 1. Increment |scheduler|'s [=Scheduler/next enqueue order=] by 1. + 1. Let |enqueue order| be |scheduler|'s [=event loop/next enqueue order=]. + 1. Increment |scheduler|'s [=event loop/next enqueue order=] by 1. 1. Let |task| be the result of [=queuing a scheduler task=] on |queue| given |enqueue order|, [=the posted task task source=], and |document|, and that performs the following steps: @@ -309,7 +296,7 @@ see [whatwg/html#5925](https://github.com/whatwg/html/issues/5925). Issue: Because this algorithm can be called from [=in parallel=] steps, parts of this and other algorithms are racy. Specifically, the - [=Scheduler/next enqueue order=] should be updated atomically, and accessing + [=event loop/next enqueue order=] should be updated atomically, and accessing the [=scheduler task queues=] should occur atomically. The latter also affects the event loop task queues (see [this issue](https://github.com/whatwg/html/issues/6475)). @@ -334,24 +321,27 @@ see [whatwg/html#5925](https://github.com/whatwg/html/issues/5925). 1. Return |queues|. -
- The result of selecting the task queue of the next scheduler task - for {{Scheduler}} |scheduler| is a [=set=] of [=scheduler tasks=] as defined - by the following steps: +
+ To select the next scheduler queue from all schedulers given an [=event loop=] + |event loop|, perform the following steps. They return a [=scheduler task queue=] or null if no + {{Scheduler}} associated with the |event loop| [=has a runnable task=]. - 1. Let |queues| be the result of [=getting the runnable task queues=] for |scheduler|. + 1. Let |queues| be an empty [=set=]. + 1. Let |schedulers| be the [=set=] of all {{Scheduler}} objects whose [=relevant agent's=] + [=agent/event loop=] is |event loop| and that [=have a runnable task=]. + 1. For each |scheduler| in |schedulers|, [=list/extend=] |queues| with the result of [=getting the + runnable task queues=] for |scheduler|. 1. If |queues| is [=list/empty=] return null. 1. [=set/Remove=] from |queues| any |queue| such that |queue|'s [=scheduler task queue/priority=] is [=TaskPriority/less than=] any other [=set/item=] of |queues|. 1. Let |queue| be the [=scheduler task queue=] in |queues| whose - [=scheduler task queue/first runnable task=] is the - [=scheduler task/older than|oldest=]. + [=scheduler task queue/first runnable task=] is the [=scheduler task/older than|oldest=].
Two tasks cannot have the same age since [=scheduler task/enqueue order=] is unique. - 1. Return |queue|'s [=scheduler task queue/tasks=]. + 1. Return |queue|. Note: The next task to run is the oldest, highest priority [=task/runnable=] - [=scheduler task=]. + [=scheduler task=] from all {{Scheduler}}s associated with the [=event loop=].
Examples {#sec-scheduling-tasks-examples} From d4e46945858fa39278cac04f40dfb8305089ee3c Mon Sep 17 00:00:00 2001 From: Scott Haseley Date: Tue, 4 Apr 2023 15:36:46 -0700 Subject: [PATCH 2/2] Cleanup --- spec/patches.md | 66 +++++++++++++++++++++------------------- spec/scheduling-tasks.md | 12 ++++---- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/spec/patches.md b/spec/patches.md index bf1def0..bf39cda 100644 --- a/spec/patches.md +++ b/spec/patches.md @@ -38,44 +38,48 @@ as it is guaranteed to be strictly increasing and unique. ### Event loop: processing model ### {#sec-patches-html-event-loop-processing} -Add the following steps to the event loop processing steps, before step 1: - - 1. Let |queues| be the [=set=] of the [=event loop=]'s [=task queues=] that - contain at least one runnable task. - 1. Let |scheduler queue| be the result of - [=selecting the next scheduler queue from all schedulers=] given the [=event loop=]. - 1. If |scheduler queue| is null and |queues| is [=list/empty=], skip to the - microtasks step below. - -Modify step 1 to be the following steps: - 1. If |scheduler queue| is not null: - 1. If |scheduler queue|'s [=scheduler task queue/priority=] is {{TaskPriority/user-blocking}} - and |queues| is not [=list/empty=], then [=set/remove=] from |queues| any [=task queue=] - whose [=task source=] is in «[=timer task source=], [=posted message task source=]». - 1. If |scheduler queue|'s [=scheduler task queue/priority=] is {{TaskPriority/background}}, set - |scheduler queue| to null. - 1. Let |taskQueue| be one of the following, chosen in an [=implementation-defined=] manner: - * If |queues| is not [=list/empty=], one of the [=task queues=] in |queues|, chosen in an - [=implementation-defined=] manner. - * |scheduler queue|'s [=scheduler task queue/tasks=], if |scheduler queue| is not null. +Replace: + 1. If the [=event loop=] has a [=task queue=] with at least one runnable + task, then: + 1. Let |taskQueue| be one such [=task queue=], chosen in an [=implementation-defined=] manner. + +With: + 1. Let |queues| be the [=set=] of the [=event loop=]'s [=task queues=] that contain at least one + runnable task. + 1. Let |scheduler queue| be the result of [=selecting the next scheduler queue from all schedulers=] + given the [=event loop=]. + 1. If |scheduler queue| is not null and |queues| is not [=list/empty=], then: + 1. If |scheduler queue|'s [=scheduler task queue/priority=] is {{TaskPriority/user-blocking}}, + then [=set/remove=] from |queues| any [=task queue=] whose [=task source=] is in + «[=timer task source=], [=posted message task source=]». + 1. Otherwise if |scheduler queue|'s [=scheduler task queue/priority=] is + {{TaskPriority/background}}, then set |scheduler queue| to null. + 1. If |scheduler queue| is not null or |queues| is not [=list/empty=], then: + 1. Let |taskQueue| be one of the following, chosen in an [=implementation-defined=] manner: + * One of the [=task queues=] in |queues|, if |queues| is not [=list/empty=]. + * |scheduler queue|'s [=scheduler task queue/tasks=], if |scheduler queue| is not null. Note: This section defines the integration of {{Scheduler}} tasks and the [=event loop=].

-{{TaskPriority/background}} tasks will only run if no other tasks are runnable. +The intention of {{TaskPriority/user-blocking}} priority is to enable developers to schedule high +priority work that still stays responsive to input and rendering, e.g. critical work spawned by an +input response that shouldn't block the next frame or key press. {{TaskPriority/user-blocking}} +tasks are specified to have higher event loop priority than other scheduling methods, specifically +{{WindowOrWorkerGlobalScope/setTimeout()|setTimeout(0)}} and same-window +{{Window/postMessage(message, options)|postMessage()}}. UAs have flexibility to prioritize between +{{TaskPriority/user-blocking}} tasks other task sources, but are encouraged to give some increased +priority to the former. One possible strategy is to give {{TaskPriority/user-blocking}} +tasks priority over everything except user input and rendering (to ensure the UI remains +responsive), as well as user-driven tasks like navigation.

{{TaskPriority/user-visible}} tasks are meant to be scheduled in a similar way to existing -scheduling mechanisms, specifically {{WindowOrWorkerGlobalScope/setTimeout()|setTimeout(0)}} and -same-window {{Window/postMessage(message, options)|postMessage()}}. While the relative priority of -these is unspecified, {{TaskPriority/user-blocking}} tasks are specified to have a higher priority -in the event loop than these scheduling methods. +scheduling mechanisms like {{WindowOrWorkerGlobalScope/setTimeout()|setTimeout(0)}} and +and same-window {{Window/postMessage(message, options)|postMessage()}}, but the relative priority +of these is unspecified (as is the case for all event loop task queues).

-The intention is for {{TaskPriority/user-blocking}} tasks to be given an increased priority in the -[=event loop=], reflecting the developer's indication of the task importance. UAs have flexibility -to prioritize between {{TaskPriority/user-blocking}} tasks other task sources and, but are -encouraged to give some increased priority to the former. One possible strategy is to give -{{TaskPriority/user-blocking}} tasks priority over everything except user input and rendering (to -ensure the UI remains responsive). +{{TaskPriority/background}} tasks can only run if no other tasks are runnable. +This is similar to idle tasks, but they can run outside of idle periods. Issue: The |taskQueue| in this step will either be a [=set=] of [=tasks=] or a [=set=] of [=scheduler tasks=]. The steps that follow only [=set/remove=] an diff --git a/spec/scheduling-tasks.md b/spec/scheduling-tasks.md index 15c5ce8..533de83 100644 --- a/spec/scheduling-tasks.md +++ b/spec/scheduling-tasks.md @@ -218,7 +218,7 @@ Processing Model {#sec-scheduling-tasks-processing-model}
To schedule a postTask task for {{Scheduler}} |scheduler| given a {{SchedulerPostTaskCallback}} |callback| and - {{SchedulerPostTaskOptions}} |options|, run the following steps: + {{SchedulerPostTaskOptions}} |options|: 1. Let |result| be [=a new promise=]. 1. Let |signal| be |options|["{{SchedulerPostTaskOptions/signal}}"] if @@ -310,12 +310,12 @@ see [whatwg/html#5925](https://github.com/whatwg/html/issues/5925).
To get the runnable task queues - for a {{Scheduler}} |scheduler|, run the following steps: + for a {{Scheduler}} |scheduler|: - 1. Let |queues| be the result of [=map/get the values|getting the values=] of - |scheduler|'s [=Scheduler/static priority task queue map=]. - 1. [=list/Extend=] |queues| with the result of [=map/get the values|getting the values=] - of |scheduler|'s [=Scheduler/dynamic priority task queue map=]. + 1. Let |queues| be the result of [=map/get the values|getting the values=] of |scheduler|'s + [=Scheduler/static priority task queue map=]. + 1. [=list/Extend=] |queues| with the result of [=map/get the values|getting the values=] of + |scheduler|'s [=Scheduler/dynamic priority task queue map=]. 1. [=list/Remove=] from |queues| any |queue| such that |queue|'s [=scheduler task queue/tasks=] do not contain a [=task/runnable=] [=scheduler task=]. 1. Return |queues|.