New issue
Advanced search Search tips

Issue 739750 link

Starred by 3 users

Issue metadata

Status: Fixed
Owner:
Closed: Jul 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Windows , Mac
Pri: 2
Type: Bug



Sign in to add a comment

requestIdleCallback() is unthrottled in background tab

Reported by bke...@mozilla.com, Jul 6 2017

Issue description

UserAgent: 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:
 
Cc: igrigo...@chromium.org rmcilroy@chromium.org
Labels: OS-Mac
Status: Untriaged (was: Unconfirmed)
https://www.w3.org/TR/requestidlecallback/#start-an-event-loop-s-idle-period suggests one idle period every 10 seconds while page visibility is hidden.

Comment 2 by bke...@mozilla.com, Jul 6 2017

CPU monitor also indicates the chrome renderer process is using a full CPU core.
Cc: altimin@chromium.org skyos...@chromium.org
+ Sami and Alex for throttling.
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.

Comment 5 by bke...@mozilla.com, 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.
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.
> 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.
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?
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.
> 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
> 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().
>> 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?
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.
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.
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
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).
> 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.
Owner: altimin@chromium.org
Status: Assigned (was: Untriaged)
Yeah, sounds like I have to fix it. 
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.

Comment 20 by bke...@mozilla.com, 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.
Project Member

Comment 21 by bugdroid1@chromium.org, 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

Project Member

Comment 22 by bugdroid1@chromium.org, 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

Comment 23 by ojan@chromium.org, 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.

Comment 24 by ojan@chromium.org, Jul 17 2017

Cc: ojan@chromium.org
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.
Status: Fixed (was: Assigned)
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