1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/IdlePeriodState.h"
#include "mozilla/StaticPrefs_idle_period.h"
#include "mozilla/ipc/IdleSchedulerChild.h"
#include "nsIIdlePeriod.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsXULAppAPI.h"
static uint64_t sIdleRequestCounter = 0;
namespace mozilla {
IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
: mIdlePeriod(aIdlePeriod) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
}
IdlePeriodState::~IdlePeriodState() {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (mIdleScheduler) {
mIdleScheduler->Disconnect();
}
}
size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = 0;
if (mIdlePeriod) {
n += aMallocSizeOf(mIdlePeriod);
}
return n;
}
void IdlePeriodState::FlagNotIdle() {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
EnsureIsActive();
if (mIdleToken && mIdleToken < TimeStamp::Now()) {
ClearIdleToken();
}
}
void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
EnsureIsPaused(aProofOfUnlock);
ClearIdleToken();
}
TimeStamp IdlePeriodState::GetIdleDeadlineInternal(
bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
bool shuttingDown;
TimeStamp localIdleDeadline =
GetLocalIdleDeadline(shuttingDown, aProofOfUnlock);
if (!localIdleDeadline) {
if (!aIsPeek) {
EnsureIsPaused(aProofOfUnlock);
ClearIdleToken();
}
return TimeStamp();
}
TimeStamp idleDeadline =
mHasPendingEventsPromisedIdleEvent || shuttingDown
? localIdleDeadline
: GetIdleToken(localIdleDeadline, aProofOfUnlock);
if (!idleDeadline) {
if (!aIsPeek) {
EnsureIsPaused(aProofOfUnlock);
// Don't call ClearIdleToken() here, since we may have a pending
// request already.
//
// RequestIdleToken can do all sorts of IPC stuff that might
// take mutexes. This is one reason why we need the
// MutexAutoUnlock reference!
RequestIdleToken(localIdleDeadline);
}
return TimeStamp();
}
if (!aIsPeek) {
EnsureIsActive();
}
return idleDeadline;
}
TimeStamp IdlePeriodState::GetLocalIdleDeadline(
bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
// If we are shutting down, we won't honor the idle period, and we will
// always process idle runnables. This will ensure that the idle queue
// gets exhausted at shutdown time to prevent intermittently leaking
// some runnables inside that queue and even worse potentially leaving
// some important cleanup work unfinished.
if (gXPCOMThreadsShutDown ||
nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
aShuttingDown = true;
return TimeStamp::Now();
}
aShuttingDown = false;
TimeStamp idleDeadline;
// This GetIdlePeriodHint() call is the reason we need a MutexAutoUnlock here.
mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
// If HasPendingEvents() has been called and it has returned true because of
// pending idle events, there is a risk that we may decide here that we aren't
// idle and return null, in which case HasPendingEvents() has effectively
// lied. Since we can't go back and fix the past, we have to adjust what we
// do here and forcefully pick the idle queue task here. Note that this means
// that we are choosing to run a task from the idle queue when we would
// normally decide that we aren't in an idle period, but this can only happen
// if we fall out of the idle period in between the call to HasPendingEvents()
// and here, which should hopefully be quite rare. We are effectively
// choosing to prioritize the sanity of our API semantics over the optimal
// scheduling.
if (!mHasPendingEventsPromisedIdleEvent &&
(!idleDeadline || idleDeadline < TimeStamp::Now())) {
return TimeStamp();
}
if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
// If HasPendingEvents() has been called and it has returned true, but we're
// no longer in the idle period, we must return a valid timestamp to pretend
// that we are still in the idle period.
return TimeStamp::Now();
}
return idleDeadline;
}
TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint,
const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (!ShouldGetIdleToken()) {
return aLocalIdlePeriodHint;
}
if (mIdleToken) {
TimeStamp now = TimeStamp::Now();
if (mIdleToken < now) {
ClearIdleToken();
return mIdleToken;
}
return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
: aLocalIdlePeriodHint;
}
return TimeStamp();
}
void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(!mActive);
if (!mIdleSchedulerInitialized) {
mIdleSchedulerInitialized = true;
if (ShouldGetIdleToken()) {
// For now cross-process idle scheduler is supported only on the main
// threads of the child processes.
mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
if (mIdleScheduler) {
mIdleScheduler->Init(this);
}
}
}
if (mIdleScheduler && !mIdleRequestId) {
TimeStamp now = TimeStamp::Now();
if (aLocalIdlePeriodHint <= now) {
return;
}
mIdleRequestId = ++sIdleRequestCounter;
mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
aLocalIdlePeriodHint - now);
}
}
void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
// We check the request ID. It's possible that the server may be granting a
// an ealier request that the client has since cancelled and re-requested.
if (mIdleRequestId == aId) {
mIdleToken = TimeStamp::Now() + aDuration;
}
}
void IdlePeriodState::SetActive() {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(!mActive);
if (mIdleScheduler) {
mIdleScheduler->SetActive();
}
mActive = true;
}
void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(mActive);
if (mIdleScheduler && mIdleScheduler->SetPaused()) {
// We may have gotten a free cpu core for running idle tasks.
// We don't try to catch the case when there are prioritized processes
// running.
// This SendSchedule call is why we need the MutexAutoUnlock here, because
// IPC can do weird things with mutexes.
mIdleScheduler->SendSchedule();
}
mActive = false;
}
void IdlePeriodState::ClearIdleToken() {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (mIdleRequestId) {
if (mIdleScheduler) {
// This SendIdleTimeUsed call is why we need to not be holding
// any locks here, because IPC can do weird things with mutexes.
// Ideally we'd have a MutexAutoUnlock& reference here, but some
// callers end up here while just not holding any locks at all.
mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
}
mIdleRequestId = 0;
mIdleToken = TimeStamp();
}
}
bool IdlePeriodState::ShouldGetIdleToken() {
return StaticPrefs::idle_period_cross_process_scheduling() &&
XRE_IsContentProcess();
}
} // namespace mozilla
|