1/*
2 * Copyright (C) 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "BackgroundProcessResponsivenessTimer.h"
28
29#include "Logging.h"
30#include "WebProcessMessages.h"
31#include "WebProcessProxy.h"
32
33namespace WebKit {
34
35static const Seconds initialCheckingInterval { 20_s };
36static const Seconds maximumCheckingInterval { 8_h };
37static const Seconds responsivenessTimeout { 90_s };
38
39BackgroundProcessResponsivenessTimer::BackgroundProcessResponsivenessTimer(WebProcessProxy& webProcessProxy)
40 : m_webProcessProxy(webProcessProxy)
41 , m_checkingInterval(initialCheckingInterval)
42 , m_responsivenessCheckTimer(RunLoop::main(), this, &BackgroundProcessResponsivenessTimer::responsivenessCheckTimerFired)
43 , m_timeoutTimer(RunLoop::main(), this, &BackgroundProcessResponsivenessTimer::timeoutTimerFired)
44{
45}
46
47BackgroundProcessResponsivenessTimer::~BackgroundProcessResponsivenessTimer()
48{
49}
50
51void BackgroundProcessResponsivenessTimer::updateState()
52{
53 if (!shouldBeActive()) {
54 if (m_responsivenessCheckTimer.isActive()) {
55 m_checkingInterval = initialCheckingInterval;
56 m_responsivenessCheckTimer.stop();
57 }
58 m_timeoutTimer.stop();
59 m_isResponsive = true;
60 return;
61 }
62
63 if (!isActive())
64 m_responsivenessCheckTimer.startOneShot(m_checkingInterval);
65}
66
67void BackgroundProcessResponsivenessTimer::didReceiveBackgroundResponsivenessPong()
68{
69 if (!m_timeoutTimer.isActive())
70 return;
71
72 m_timeoutTimer.stop();
73 scheduleNextResponsivenessCheck();
74
75 setResponsive(true);
76}
77
78void BackgroundProcessResponsivenessTimer::invalidate()
79{
80 m_timeoutTimer.stop();
81 m_responsivenessCheckTimer.stop();
82}
83
84void BackgroundProcessResponsivenessTimer::processTerminated()
85{
86 invalidate();
87 setResponsive(true);
88}
89
90void BackgroundProcessResponsivenessTimer::responsivenessCheckTimerFired()
91{
92 ASSERT(shouldBeActive());
93 ASSERT(!m_timeoutTimer.isActive());
94
95 m_timeoutTimer.startOneShot(responsivenessTimeout);
96 m_webProcessProxy.send(Messages::WebProcess::BackgroundResponsivenessPing(), 0);
97}
98
99void BackgroundProcessResponsivenessTimer::timeoutTimerFired()
100{
101 ASSERT(shouldBeActive());
102
103 scheduleNextResponsivenessCheck();
104
105 if (!m_isResponsive)
106 return;
107
108 if (!client().mayBecomeUnresponsive())
109 return;
110
111 setResponsive(false);
112}
113
114void BackgroundProcessResponsivenessTimer::setResponsive(bool isResponsive)
115{
116 if (m_isResponsive == isResponsive)
117 return;
118
119 client().willChangeIsResponsive();
120 m_isResponsive = isResponsive;
121 client().didChangeIsResponsive();
122
123 if (m_isResponsive) {
124 RELEASE_LOG_ERROR(PerformanceLogging, "Notifying the client that background WebProcess with pid %d has become responsive again", m_webProcessProxy.processIdentifier());
125 client().didBecomeResponsive();
126 } else {
127 RELEASE_LOG_ERROR(PerformanceLogging, "Notifying the client that background WebProcess with pid %d has become unresponsive", m_webProcessProxy.processIdentifier());
128 client().didBecomeUnresponsive();
129 }
130}
131
132bool BackgroundProcessResponsivenessTimer::shouldBeActive() const
133{
134#if !PLATFORM(IOS_FAMILY)
135 // Service worker process are always in the background.
136 if (m_webProcessProxy.isServiceWorkerProcess())
137 return true;
138 return !m_webProcessProxy.visiblePageCount() && m_webProcessProxy.pageCount();
139#else
140 // Disable background process responsiveness checking on iOS since such processes usually get suspended.
141 return false;
142#endif
143}
144
145bool BackgroundProcessResponsivenessTimer::isActive() const
146{
147 return m_responsivenessCheckTimer.isActive() || m_timeoutTimer.isActive();
148}
149
150void BackgroundProcessResponsivenessTimer::scheduleNextResponsivenessCheck()
151{
152 // Exponential backoff to avoid waking up the process too often.
153 ASSERT(!m_responsivenessCheckTimer.isActive());
154 m_checkingInterval = std::min(m_checkingInterval * 2, maximumCheckingInterval);
155 m_responsivenessCheckTimer.startOneShot(m_checkingInterval);
156}
157
158ResponsivenessTimer::Client& BackgroundProcessResponsivenessTimer::client() const
159{
160 return m_webProcessProxy;
161}
162
163} // namespace WebKit
164