WICG/scheduling-apis

Proposal should address priority inversion

Opened this issue · 2 comments

I have mentioned this numerous times during TPAC, etc... but if we add a priority to each task, people will encounter priority inversion. It's not a question of if, it's a question of when. So this proposal really needs to address priority inversion or else it's not really viable in my view.

At minimum, there needs to be a way to boost the priority of a task. A better approach will be automatically boosting the priority of a low priority task as needed.

I have mentioned this numerous times during TPAC, etc... but if we add a priority to each task, people will encounter priority inversion. It's not a question of if, it's a question of when. So this proposal really needs to address priority inversion or else it's not really viable in my view.

In my view, the (main thread) web scheduling model (no preemption, single thread) is significantly different than scenarios where priority inversion typically arises (threading, synchronization, preemption). I wrote in detail about this in https://github.com/WICG/scheduling-apis/blob/main/misc/priority-inversion.md, which I had linked in the original request for position. It would be extremely helpful if you could provide examples of situations you're concerned about, which I've asked for before, as well as understanding if/how your understanding differs from what I've written there.

Also, just to clarify "each task", the proposal only adds a priority to tasks scheduled with this API. The intent is for this priority to influence the event loop priority, but UAs have flexibility to choose between scheduler tasks and other task sources.

At minimum, there needs to be a way to boost the priority of a task. A better approach will be automatically boosting the priority of a low priority task as needed.

Boost the priority of which tasks? The priority of tasks scheduled with these APIs can be changed in JavaScript with TaskController.setPriority().

I enjoyed reading that document, Scott!

It's been a long time since I studied the cs theory here so I may be rusty :). Some questions below:


Synchronous userspace tasks that use scheduler.postTask() are not at risk of priority inversion in the same way because of the lack of preemption

Where there is a greater risk of priority inversions of this form is with yieldy asyncrounous tasks, which resemble threads

I think this distinction makes sense, but it feels somewhat equivalent to developers, in practice.


Other Forms of Priority Inversion

I think I have a simple example which is similar but not exactly like your Example 1?

Example 3: A higher priority task await's a promise which is created (and later fulfilled) by a lower priority task. The lower priority task might need to wait for all higher tasks to finish before being scheduled (and resolving the promise) meaning the higher priority task awaiting effectively becomes lower priority.

Example:

const {promise: userDataPromise, resolve, reject} = Promise.withResolvers();

// Will get scheduled at low priority
// Even when the fetch completes, it might take time to schedule continuation and resolve
// (Assumes priority inheritance... but would apply also to default priority)
async function loadLazyUserData() {
  const userData = await fetch(...);
  resolve(userData);
}

function onLoad() {
  scheduler.postTask(setupUI, { priority: 'user-blocking' });
  scheduler.postTask(loadLazyUserData, { priority: 'background' });
}

// This task is high priority.
// This task awaits a promise that is fulfilled by a lower priority task.
// When the fetch response becomes available, promise will still remain unfulfilled until that lower priority task gets scheduled.
// This high priority event handler effectively becomes lower priority.
userButton.onclick = async () => {
  const userData = await userDataPromise;
}

There may be an opportunity for a UA to detect and mitigate priority inversions

Some ideas:

  • requestIdleCallback supports a timeout value which is a deadline. postTask supports a delay, but not a deadline. I wonder if that could be valuable especially for background tasks?
    • And, maybe UA's could apply a default deadline to prevent complete starvation. (Has tradeoffs / risks for other reasons). Although UAs could already have flexibility to this now, it might be useful to allow the developer to add hints?
  • Perhaps it could be possible to detect specifically the case above: where a high priority task is awaiting a promise which was created by a lower priority task, and where that lower priority task is ready to be scheduled again (and thus might be ready to resolve the promise)
    • i.e., the higher priority task boosts the priority of any task "attached" to the resource (perhaps specific to promises + a few more platform primitives)