1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "platform/scheduler/Scheduler.h"
7
8#include "platform/PlatformThreadData.h"
9#include "platform/Task.h"
10#include "platform/ThreadTimers.h"
11#include "platform/TraceEvent.h"
12#include "public/platform/Platform.h"
13#include "wtf/MainThread.h"
14
15namespace blink {
16
17namespace {
18
19// The time we should stay in CompositorPriority mode for, after a touch event.
20double kLowSchedulerPolicyAfterTouchTimeSeconds = 0.1;
21
22// Can be created from any thread.
23// Note if the scheduler gets shutdown, this may be run after.
24class MainThreadIdleTaskAdapter : public WebThread::Task {
25public:
26    MainThreadIdleTaskAdapter(const Scheduler::IdleTask& idleTask, double allottedTimeMs, const TraceLocation& location)
27        : m_idleTask(idleTask)
28        , m_allottedTimeMs(allottedTimeMs)
29        , m_location(location)
30    {
31    }
32
33    // WebThread::Task implementation.
34    virtual void run() OVERRIDE
35    {
36        TRACE_EVENT2("blink", "MainThreadIdleTaskAdapter::run",
37            "src_file", m_location.fileName(),
38            "src_func", m_location.functionName());
39        m_idleTask(m_allottedTimeMs);
40    }
41
42private:
43    Scheduler::IdleTask m_idleTask;
44    double m_allottedTimeMs;
45    TraceLocation m_location;
46};
47
48} // namespace
49
50// Typically only created from compositor or render threads.
51// Note if the scheduler gets shutdown, this may be run after.
52class Scheduler::MainThreadPendingHighPriorityTaskRunner : public WebThread::Task {
53public:
54    MainThreadPendingHighPriorityTaskRunner()
55    {
56        ASSERT(Scheduler::shared());
57    }
58
59    // WebThread::Task implementation.
60    virtual void run() OVERRIDE
61    {
62        Scheduler* scheduler = Scheduler::shared();
63        // FIXME: This check should't be necessary, tasks should not outlive blink.
64        ASSERT(scheduler);
65        if (!scheduler)
66            return;
67        // NOTE we must unconditionally execute high priority tasks here, since if we're not in CompositorPriority
68        // mode, then this is the only place where high priority tasks will be executed.
69        scheduler->swapQueuesRunPendingTasksAndAllowHighPriorityTaskRunnerPosting();
70    }
71};
72
73
74// Can be created from any thread.
75// Note if the scheduler gets shutdown, this may be run after.
76class Scheduler::MainThreadPendingTaskRunner : public WebThread::Task {
77public:
78    MainThreadPendingTaskRunner(
79        const Scheduler::Task& task, const TraceLocation& location, const char* traceName)
80        : m_task(task, location, traceName)
81    {
82        ASSERT(Scheduler::shared());
83    }
84
85    // WebThread::Task implementation.
86    virtual void run() OVERRIDE
87    {
88        Scheduler* scheduler = Scheduler::shared();
89        // FIXME: This check should't be necessary, tasks should not outlive blink.
90        ASSERT(scheduler);
91        if (scheduler)
92            Scheduler::shared()->runPendingHighPriorityTasksIfInCompositorPriority();
93        m_task.run();
94    }
95
96    TracedTask m_task;
97};
98
99Scheduler* Scheduler::s_sharedScheduler = nullptr;
100
101void Scheduler::initializeOnMainThread()
102{
103    s_sharedScheduler = new Scheduler();
104}
105
106void Scheduler::shutdown()
107{
108    delete s_sharedScheduler;
109    s_sharedScheduler = nullptr;
110}
111
112Scheduler* Scheduler::shared()
113{
114    return s_sharedScheduler;
115}
116
117Scheduler::Scheduler()
118    : m_sharedTimerFunction(nullptr)
119    , m_mainThread(blink::Platform::current()->currentThread())
120    , m_compositorPriorityPolicyEndTimeSeconds(0)
121    , m_highPriorityTaskCount(0)
122    , m_highPriorityTaskRunnerPosted(false)
123    , m_schedulerPolicy(Normal)
124{
125}
126
127Scheduler::~Scheduler()
128{
129    while (hasPendingHighPriorityWork()) {
130        swapQueuesAndRunPendingTasks();
131    }
132}
133
134void Scheduler::willBeginFrame(const WebBeginFrameArgs& args)
135{
136    // TODO: Use frame deadline and interval to schedule idle tasks.
137}
138
139void Scheduler::didCommitFrameToCompositor()
140{
141    // TODO: Trigger the frame deadline immediately.
142}
143
144void Scheduler::scheduleIdleTask(const TraceLocation& location, const IdleTask& idleTask)
145{
146    // TODO: send a real allottedTime here.
147    m_mainThread->postTask(new MainThreadIdleTaskAdapter(idleTask, 0, location));
148}
149
150void Scheduler::postHighPriorityTaskInternal(const TraceLocation& location, const Task& task, const char* traceName)
151{
152    Locker<Mutex> lock(m_pendingTasksMutex);
153
154    m_pendingHighPriorityTasks.append(TracedTask(task, location, traceName));
155    atomicIncrement(&m_highPriorityTaskCount);
156    maybePostMainThreadPendingHighPriorityTaskRunner();
157    TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink.scheduler"), "PendingHighPriorityTasks", m_highPriorityTaskCount);
158}
159
160void Scheduler::postTask(const TraceLocation& location, const Task& task)
161{
162    m_mainThread->postTask(new MainThreadPendingTaskRunner(task, location, "Scheduler::MainThreadTask"));
163}
164
165void Scheduler::postInputTask(const TraceLocation& location, const Task& task)
166{
167    postHighPriorityTaskInternal(location, task, "Scheduler::InputTask");
168}
169
170void Scheduler::didReceiveInputEvent()
171{
172    enterSchedulerPolicy(CompositorPriority);
173}
174
175void Scheduler::postCompositorTask(const TraceLocation& location, const Task& task)
176{
177    postHighPriorityTaskInternal(location, task, "Scheduler::CompositorTask");
178}
179
180void Scheduler::postIpcTask(const TraceLocation& location, const Task& task)
181{
182    // FIXME: we want IPCs to be high priority, but we can't currently do that because some of them can take a very long
183    // time to process. These need refactoring but we need to add some infrastructure to identify them.
184    m_mainThread->postTask(new MainThreadPendingTaskRunner(task, location, "Scheduler::IpcTask"));
185}
186
187void Scheduler::maybePostMainThreadPendingHighPriorityTaskRunner()
188{
189    ASSERT(m_pendingTasksMutex.locked());
190    if (m_highPriorityTaskRunnerPosted)
191        return;
192    m_mainThread->postTask(new MainThreadPendingHighPriorityTaskRunner());
193    m_highPriorityTaskRunnerPosted = true;
194}
195
196void Scheduler::postIdleTask(const TraceLocation& location, const IdleTask& idleTask)
197{
198    scheduleIdleTask(location, idleTask);
199}
200
201void Scheduler::tickSharedTimer()
202{
203    TRACE_EVENT0("blink", "Scheduler::tickSharedTimer");
204
205    // Run any high priority tasks that are queued up, otherwise the blink timers will yield immediately.
206    bool workDone = runPendingHighPriorityTasksIfInCompositorPriority();
207    m_sharedTimerFunction();
208
209    // The blink timers may have just yielded, so run any high priority tasks that where queued up
210    // while the blink timers were executing.
211    if (!workDone)
212        runPendingHighPriorityTasksIfInCompositorPriority();
213}
214
215bool Scheduler::runPendingHighPriorityTasksIfInCompositorPriority()
216{
217    ASSERT(isMainThread());
218    if (schedulerPolicy() != CompositorPriority)
219        return false;
220
221    return swapQueuesAndRunPendingTasks();
222}
223
224bool Scheduler::swapQueuesAndRunPendingTasks()
225{
226    ASSERT(isMainThread());
227
228    // These locks guard against another thread posting input or compositor tasks while we swap the buffers.
229    // One the buffers have been swapped we can safely access the returned deque without having to lock.
230    m_pendingTasksMutex.lock();
231    Deque<TracedTask>& highPriorityTasks = m_pendingHighPriorityTasks.swapBuffers();
232    maybeEnterNormalSchedulerPolicy();
233    m_pendingTasksMutex.unlock();
234    return executeHighPriorityTasks(highPriorityTasks);
235}
236
237void Scheduler::swapQueuesRunPendingTasksAndAllowHighPriorityTaskRunnerPosting()
238{
239    ASSERT(isMainThread());
240
241    // These locks guard against another thread posting input or compositor tasks while we swap the buffers.
242    // One the buffers have been swapped we can safely access the returned deque without having to lock.
243    m_pendingTasksMutex.lock();
244    Deque<TracedTask>& highPriorityTasks = m_pendingHighPriorityTasks.swapBuffers();
245    m_highPriorityTaskRunnerPosted = false;
246    maybeEnterNormalSchedulerPolicy();
247    m_pendingTasksMutex.unlock();
248    executeHighPriorityTasks(highPriorityTasks);
249}
250
251void Scheduler::maybeEnterNormalSchedulerPolicy()
252{
253    ASSERT(isMainThread());
254    ASSERT(m_pendingTasksMutex.locked());
255
256    // Go back to the normal scheduler policy if enough time has elapsed.
257    if (schedulerPolicy() == CompositorPriority && Platform::current()->monotonicallyIncreasingTime() > m_compositorPriorityPolicyEndTimeSeconds)
258        enterSchedulerPolicyLocked(Normal);
259}
260
261bool Scheduler::executeHighPriorityTasks(Deque<TracedTask>& highPriorityTasks)
262{
263    TRACE_EVENT0("blink", "Scheduler::executeHighPriorityTasks");
264    int highPriorityTasksExecuted = 0;
265    while (!highPriorityTasks.isEmpty()) {
266        highPriorityTasks.takeFirst().run();
267        highPriorityTasksExecuted++;
268    }
269
270    int highPriorityTaskCount = atomicSubtract(&m_highPriorityTaskCount, highPriorityTasksExecuted);
271    ASSERT_UNUSED(highPriorityTaskCount, highPriorityTaskCount >= 0);
272    TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink.scheduler"), "PendingHighPriorityTasks", m_highPriorityTaskCount);
273    return highPriorityTasksExecuted > 0;
274}
275
276void Scheduler::sharedTimerAdapter()
277{
278    shared()->tickSharedTimer();
279}
280
281void Scheduler::setSharedTimerFiredFunction(void (*function)())
282{
283    m_sharedTimerFunction = function;
284    blink::Platform::current()->setSharedTimerFiredFunction(function ? &Scheduler::sharedTimerAdapter : nullptr);
285}
286
287void Scheduler::setSharedTimerFireInterval(double interval)
288{
289    blink::Platform::current()->setSharedTimerFireInterval(interval);
290}
291
292void Scheduler::stopSharedTimer()
293{
294    blink::Platform::current()->stopSharedTimer();
295}
296
297bool Scheduler::shouldYieldForHighPriorityWork() const
298{
299    // It's only worthwhile yielding in CompositorPriority mode.
300    if (schedulerPolicy() != CompositorPriority)
301        return false;
302
303    return hasPendingHighPriorityWork();
304}
305
306bool Scheduler::hasPendingHighPriorityWork() const
307{
308    // This method is expected to be run on the main thread, but the high priority tasks will be posted by
309    // other threads. We could use locks here, but this function is (sometimes) called a lot by
310    // ThreadTimers::sharedTimerFiredInternal so we decided to use atomics + barrier loads here which
311    // should be cheaper.
312    // NOTE it's possible the barrier read is overkill here, since delayed yielding isn't a big deal.
313    return acquireLoad(&m_highPriorityTaskCount) != 0;
314}
315
316Scheduler::SchedulerPolicy Scheduler::schedulerPolicy() const
317{
318    ASSERT(isMainThread());
319    // It's important not to miss the transition from normal to low latency mode, otherwise we're likely to
320    // delay the processing of input tasks. Since that transition is triggered by a different thread, we
321    // need either a lock or a memory barrier, and the memory barrier is probably cheaper.
322    return static_cast<SchedulerPolicy>(acquireLoad(&m_schedulerPolicy));
323}
324
325void Scheduler::enterSchedulerPolicy(SchedulerPolicy schedulerPolicy)
326{
327    Locker<Mutex> lock(m_pendingTasksMutex);
328    enterSchedulerPolicyLocked(schedulerPolicy);
329}
330
331void Scheduler::enterSchedulerPolicyLocked(SchedulerPolicy schedulerPolicy)
332{
333    ASSERT(m_pendingTasksMutex.locked());
334    if (schedulerPolicy == CompositorPriority)
335        m_compositorPriorityPolicyEndTimeSeconds = Platform::current()->monotonicallyIncreasingTime() + kLowSchedulerPolicyAfterTouchTimeSeconds;
336
337    releaseStore(&m_schedulerPolicy, schedulerPolicy);
338    TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink.scheduler"), "SchedulerPolicy", schedulerPolicy);
339}
340
341} // namespace blink
342