requestIdleCallback() is unthrottled in background tab
Reported by
bke...@mozilla.com,
Jul 6 2017
|
|||||
Issue descriptionUserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 Steps to reproduce the problem: 1. Open https://timer-flood.glitch.me/ in chrome 2. Observe that requestIdleCallback() runs at ~60fps in foreground. 3. Open example.com in a new tab 4. Switch back to the original tab. 5. Observe that requestIdleCallback() spiked up to > 10k fps while in the background. What is the expected behavior? What went wrong? Given that chrome is trying to limit CPU usage of background tabs I would expect requestIdleCallback would not fire in background tabs at all. Did this work before? N/A Does this work in other browsers? N/A Chrome version: <Copy from: 'about:version'> Channel: n/a OS Version: 10.0 Flash Version:
,
Jul 6 2017
CPU monitor also indicates the chrome renderer process is using a full CPU core.
,
Jul 6 2017
+ Sami and Alex for throttling.
,
Jul 6 2017
We stop requestIdleCallback after 10 seconds in background tabs. Does that match what you're seeing? If that works as expected, I'm not sure we need to do any additional throttling.
,
Jul 6 2017
> We stop requestIdleCallback after 10 seconds in background tabs. Does that match what you're seeing? If that works as expected, I'm not sure we need to do any additional throttling.
Oh, you are right. It does stop after a while.
I just find it surprising that this:
function foo() {
requestIdleCallback(foo);
}
foo();
Will call foo() around 60 times a second while in the foreground, but will call foo() as fast as possible in the background (until the 10 second cutoff is reached).
The 100% CPU usage during this 10 second time window is what was surprising to me.
FWIW, in firefox we limit background idle callbacks to the same frequency as setTimeout(); 1 per second. I'm just mentioning this for comparison's sake.
,
Jul 6 2017
It should only throttle to 60 fps in the foreground if some part of the page is updating (causing rendering). Otherwise you'll be able to run as fast as possible in the foreground too. Since we never render anything in the background, idle callbacks there can always run as fast as possible assuming nothing else wants to run.
,
Jul 7 2017
> We stop requestIdleCallback after 10 seconds in background tabs That doesn't really match what the spec is saying, so either our implementation or the spec should change.
,
Jul 7 2017
There is a note in section 5.1 of the spec that explicitly has this example. Admittedly this is non-native, but I didn't want to constrain browser implementers on the exact details here. I'd welcome ideas for how to fit this into the normative text - does setTimeout have any normative text describing our throttling of these in the background?
,
Jul 7 2017
I believe that we definitely should start throttling rAC to 1Hz in background tabs given that Firefox does. Stopping rAC after some time in background seems fine (but we might want to increase current 10-second limit after enabling rAC throttling). What's the current Firefox behaviour here? Also, idle tasks currently take ~2% of all consumed CPU even after backgrounding the renderer: see RendererScheduler.TaskDurationPerQueueType2.Background.AfterFifthMinute UMA. I'll look into that more closely.
,
Jul 7 2017
> Stopping rAC after some time in background seems fine (but we might want to increase current 10-second limit after enabling rAC throttling). What's the current Firefox behaviour here? We don't have any complete halting of callbacks like this right now. We just throttle to 1hz in the background let setTimeout(). In theory we want to move towards a world where we freeze background tabs, though. So I wouldn't rule it out in the future. Kind of a tangent, but note we also have some requestIdleCallback() weirdness when nothing is rendering: https://bugzilla.mozilla.org/show_bug.cgi?id=1379178
,
Jul 7 2017
> I believe that we definitely should start throttling rAC to 1Hz in background tabs given that Firefox does. Also consider that we have not shipped yet. I think its planned for our next release in August. Still, I would be happy to 1Hz background throttling here since this API is essentially a way for script to hint that its ok to run these callbacks late. It would be unusual to throttle setTimeout(f,0), but not requestIdleCallback().
,
Jul 7 2017
>> Also consider that we have not shipped yet. Could you clarify this (it's my understanding that Firefox has rIC support) and describe current behaviour?
,
Jul 7 2017
Firefox 54 is our current release. The requestIdleCallback() implementation exists, but is still disabled by default in that release. Firefox 55 has requestIdleCallback() enabled. Its expected to ship in early August.
,
Jul 7 2017
So, I've been trying to wrap my head around the 100% cpu thing when not rendering a bit more. AIUI the browser should queue a requestIdleCallback() to run in the next idle period. It should not run in the current idle period. Correct? AFAICT this happens as part of step 7 of: https://w3c.github.io/requestidlecallback/#start-an-event-loop%27s-idle-period It moves all of the idle callbacks into the run list and then executes "invoke idle callbacks algorithm". Any nested requestIdleCallback() calls should populate the idle callbacks list and not go directly into the run list. Then step 2.1 should wait until the end of the current deadline is reached before doing this again. So this code: function tick() { requestIdleCallback(tick); } tick(); Should not run any faster than `1 / idle-deadline` Hz. Is this all correct? From what I can tell chrome is doing all of this when rendering takes place. When rendering is not taking place, though, it fires an IdleDeadline of 50ms but then goes ahead and schedules the nested requestIdleCallback() without waiting for this 50ms to expire.
,
Jul 7 2017
For example, I change https://idle-no-render.glitch.me/ to sum "IdleDeadline.timeRemaining()" and average it over one second. Chrome is advertising that it has a lot of idle time: requestIdleCallback rate (executions / second): 159133 requestIdleCallback deadline (total idle milliseconds / second): 7955820
,
Jul 7 2017
Chrome has two different concepts — short idle period (tied to begin main frame and rendering cycle) and long idle period (e.g. when the tab is backgrounded). Currently in Chrome there is one 10-seconds-long idle period after backgrounding a tab, and in this period a newly-requested rIC runs immediately (and for each run there is a 50ms deadline).
,
Jul 7 2017
> Currently in Chrome there is one 10-seconds-long idle period after backgrounding a tab, and in this period a newly-requested rIC runs immediately (and for each run there is a 50ms deadline). Ok. The "run immediately" seems in conflict with step 2.1 here: https://w3c.github.io/requestidlecallback/#start-an-event-loop%27s-idle-period Also, the "run immediately" seems to happen in foreground if nothing is rendering.
,
Jul 7 2017
Yeah, sounds like I have to fix it.
,
Jul 10 2017
re #14: yeah that sounds like the bug, we should be waiting until the end of the idle period (50ms) before scheduling the next idle period. I thought we used to do that, but maybe we do something wrong when in the background.
,
Jul 10 2017
> I thought we used to do that, but maybe we do something wrong when in the background. FWIW, you can observe the same behavior in the foreground using: https://idle-no-render.glitch.me/ It seems to trigger when there is no requestAnimationFrame or other rendering happening.
,
Jul 13 2017
The following revision refers to this bug: https://chromium.googlesource.com/chromium/src.git/+/67a6ece7daa70d306dce9430e25c2c24b753879f commit 67a6ece7daa70d306dce9430e25c2c24b753879f Author: Alexander Timin <altimin@chromium.org> Date: Thu Jul 13 14:19:05 2017 [scheduler] Make long idle periods conform to the spec. Wait until the old idle period deadline and do not start a new idle period immediately per paragraph 5.2.2.1 of the spec: https://w3c.github.io/requestidlecallback/#start-an-event-loop%27s-idle-period BUG= 739750 Change-Id: I3b96ddf92651b0bc4d227cef1c1ac68be7ea9179 Reviewed-on: https://chromium-review.googlesource.com/568301 Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Commit-Queue: Alexander Timin <altimin@chromium.org> Cr-Commit-Position: refs/heads/master@{#486368} [modify] https://crrev.com/67a6ece7daa70d306dce9430e25c2c24b753879f/third_party/WebKit/Source/platform/scheduler/child/idle_helper.cc [modify] https://crrev.com/67a6ece7daa70d306dce9430e25c2c24b753879f/third_party/WebKit/Source/platform/scheduler/child/idle_helper_unittest.cc [modify] https://crrev.com/67a6ece7daa70d306dce9430e25c2c24b753879f/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
,
Jul 14 2017
The following revision refers to this bug: https://chromium.googlesource.com/chromium/src.git/+/ea5a4492a0301a8ed634d6f2d23dd5654641a288 commit ea5a4492a0301a8ed634d6f2d23dd5654641a288 Author: Alexander Timin <altimin@chromium.org> Date: Fri Jul 14 11:19:56 2017 [scheduler] Add layout test for long idle periods. R=rmcilroy@chromium.org BUG= 739750 Change-Id: Iccb5f8ecaf72f2c73b2d23bd2c29bc2e2cb88cfe Reviewed-on: https://chromium-review.googlesource.com/570420 Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Commit-Queue: Alexander Timin <altimin@chromium.org> Cr-Commit-Position: refs/heads/master@{#486733} [add] https://crrev.com/ea5a4492a0301a8ed634d6f2d23dd5654641a288/third_party/WebKit/LayoutTests/virtual/threaded/fast/idle-callback/long_idle_periods.html
,
Jul 17 2017
Should we throttle these to 1 second the same way we do setTimeout? Alternately, should we just not fire it in background tabs? The point of requestIdleCallback is to do work that doesn't jank input and visual effects. Background tabs don't have visual effects. I suppose it could be for audio effects as well, but if that were the case you'd need much shorter idle times.
,
Jul 17 2017
,
Jul 18 2017
I'm not quite sure I understand #23, the two paragraphs seem to contradict each other. When a tab is in the background it doesn't have visual effects / input which could jank, so it is the perfect time to do some background work without causing input / visual jank. I think we should continue to fire these when in the background, although I'm fine with throttling them to a limited amount of CPU usage like setTimeout to limit abuse.
,
Jul 18 2017
Re #23: At the moment rIC fire up to 20 times per second for 10 seconds and than stop indefinitely. This behaviour matches the spec and mitigates the immediate problem. I've filed crbug.com/745692 (and CC'd you all) to track further work. I'll consider this bug resolved. |
|||||
►
Sign in to add a comment |
|||||
Comment 1 by jakearchibald@chromium.org
, Jul 6 2017Labels: OS-Mac
Status: Untriaged (was: Unconfirmed)