w3c / requestidlecallback

Cooperative Scheduling of Background Tasks
https://w3c.github.io/requestidlecallback/
Other
50 stars 19 forks source link

Friendly callbacks #34

Closed Krinkle closed 8 years ago

Krinkle commented 8 years ago

There's a notion of "friendly callbacks" I've seen in a few places where people showcase requestIdleCallback. This is a kind of callback that checks timeRemaining() to decide whether to do the work now or not, e.g. when there is not enough time left to synchronously do the work it expects to take a certain amount of time (e.g. if they need 40ms, and there's only 10 of the 50ms left, they'll return early and reschedule for another idle period).

However I'm not sure how well that works in practice. It seems there is potential scenario in which such a friendly callback will always "draw the short straw" if it is not the first callback in an idle period. And since it always has to reschedule (it is not moved along internally, it's a new callback) there can always be something else in front of it.

It might be over-engineered, perhaps it makes sense to either:

It also comes to mind that in general it seems wrong to encourage hardcoded time values in authored code as a way to measure execution time. Device capability and software implementations vary so much that it's hard to make use of in a meaningful way. But there isn't an easy alternative there, either.

igrigorik commented 8 years ago

Well, the spec title is "Cooperative Scheduling of Background Tasks".. with emphasis on cooperative :-)

However I'm not sure how well that works in practice. It seems there is potential scenario in which such a friendly callback will always "draw the short straw" if it is not the first callback in an idle period. And since it always has to reschedule (it is not moved along internally, it's a new callback) there can always be something else in front of it.

If the current idle period has expired the execution of remaining callbacks is deferred until next period, and they're processed in FIFO order, so the ones that have been in the queue longer will be processed first.

Recognise time requirement as a concept (e.g. instead of an if-check inside, it is passed to requestIdleCallback). This is less flexible, less low-level and requires authors to know the required time ahead of time. This also would violate FIFO naturally. Presumably the user agent would internally skip callbacks it knows will take too long, but keeps them in the queue where they were. Thus the next idle period, it will be in front of the remaining queue.

It may make sense to allow smarter scheduling where the application can communicate how much time it needs -- e.g. if I know I need a "long" idle block (50ms) then communicating that to the UA will definitely help. However, I don't think we want to much with the ordering properties here.. otherwise we run into all kinds of starvation issues.

It also comes to mind that in general it seems wrong to encourage hardcoded time values in authored code as a way to measure execution time. Device capability and software implementations vary so much that it's hard to make use of in a meaningful way. But there isn't an easy alternative there, either.

The recommended approach is to execute small tasks and keep checking timeRemaining().. A slower device will take more time and you can adjust accordingly.

Krinkle commented 8 years ago

If the current idle period has expired the execution of remaining callbacks is deferred until next period, and they're processed in FIFO order, so the ones that have been in the queue longer will be processed first.

True, but in the scenario of a "friendly callback" (i.e. one that checks timeRemaining() and requests a new callback for itself and returns early if it expects to need more time), it will essentially be a fresh entry in the queue. Not one that has been there longer.

The recommended approach is to execute small tasks and keep checking timeRemaining().. A slower device will take more time and you can adjust accordingly.

Yeah, agreed completely. One shouldn't need to check timeRemaining() though? Once the budget is exceeded, the user agent will finish the current callback and then wait for the next idle period before processing the next task in the queue.

I'm slightly puzzled as to when timeRemaining() is meant to be used. The more I think about it, the more it seems like one should never use it. The usage example I have in my head is a callback that checks timeRemaining() and compares it against some number and then decides whether to do "the task" now, or request it for the next idle period and return.

I think I get it now. It's not meant for tasks that have one (small) chunk of work to help decide whether there is enough time remaining to do that chunk. Not at all. Rather, it's meant more for tasks that have multiple work chunks (e.g. they have a queue of their own). A kind of "meta" task that would use timeRemaining() to decide when to stop (e.g. right after, or slightly before it exceeds the time window), similar to what the user agent does internally for requestIdleCallback.

igrigorik commented 8 years ago

I think I get it now. It's not meant for tasks that have one (small) chunk of work to help decide whether there is enough time remaining to do that chunk. Not at all. Rather, it's meant more for tasks that have multiple work chunks (e.g. they have a queue of their own). A kind of "meta" task that would use timeRemaining() to decide when to stop (e.g. right after, or slightly before it exceeds the time window), similar to what the user agent does internally for requestIdleCallback.

Yes, exactly. Except, don't stop "right after" as that means you've just exceeded your frame budget and UA will be forced to skip a frame. Instead, check timeRemaining() and exit when it's sufficiently low (i.e. estimated time for your task is greater than remaining time in the idle block).

Krinkle commented 8 years ago

I'll close this for now, though we could perhaps improve the documentation a bit.