[iOS] The UIProcess may get killed for trying to stay runnable in the background...
[WebKit-https.git] / Source / WebKit / UIProcess / ios / ProcessAssertionIOS.mm
1 /*
2  * Copyright (C) 2014-2019 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 #import "config.h"
27 #import "ProcessAssertion.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "AssertionServicesSPI.h"
32 #import "Logging.h"
33 #import <UIKit/UIApplication.h>
34 #import <wtf/HashMap.h>
35 #import <wtf/RunLoop.h>
36 #import <wtf/Vector.h>
37
38 using WebKit::ProcessAndUIAssertion;
39
40 @interface WKProcessAssertionBackgroundTaskManager : NSObject
41
42 + (WKProcessAssertionBackgroundTaskManager *)shared;
43
44 - (void)addAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion;
45 - (void)removeAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion;
46
47 @end
48
49 @implementation WKProcessAssertionBackgroundTaskManager
50 {
51     UIBackgroundTaskIdentifier _backgroundTask;
52     HashSet<ProcessAndUIAssertion*> _assertionsNeedingBackgroundTask;
53     BOOL _assertionHasExpiredInTheBackground;
54 }
55
56 + (WKProcessAssertionBackgroundTaskManager *)shared
57 {
58     static WKProcessAssertionBackgroundTaskManager *shared = [WKProcessAssertionBackgroundTaskManager new];
59     return shared;
60 }
61
62 - (instancetype)init
63 {
64     self = [super init];
65     if (!self)
66         return nil;
67
68     _backgroundTask = UIBackgroundTaskInvalid;
69
70     [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
71         _assertionHasExpiredInTheBackground = NO;
72         [self _updateBackgroundTask];
73     }];
74
75     return self;
76 }
77
78 - (void)dealloc
79 {
80     [self _releaseBackgroundTask];
81     [super dealloc];
82 }
83
84 - (void)addAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion
85 {
86     _assertionsNeedingBackgroundTask.add(&assertion);
87     [self _updateBackgroundTask];
88 }
89
90 - (void)removeAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion
91 {
92     _assertionsNeedingBackgroundTask.remove(&assertion);
93     [self _updateBackgroundTask];
94 }
95
96 - (void)_notifyAssertionsOfImminentSuspension
97 {
98     ASSERT(RunLoop::isMain());
99
100     for (auto* assertion : copyToVector(_assertionsNeedingBackgroundTask))
101         assertion->uiAssertionWillExpireImminently();
102 }
103
104 - (void)_updateBackgroundTask
105 {
106     if (!_assertionsNeedingBackgroundTask.isEmpty() && _backgroundTask == UIBackgroundTaskInvalid) {
107         if (_assertionHasExpiredInTheBackground) {
108             RELEASE_LOG_ERROR(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Ignored request to start a background task because we're still in the background and the previous task expired", self);
109             // Our invalidation handler would not get called if we tried to re-take a new background assertion at this point, and the UIProcess would get killed (rdar://problem/50001505).
110             return;
111         }
112         RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - beginBackgroundTaskWithName", self);
113         _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"com.apple.WebKit.ProcessAssertion" expirationHandler:^{
114             RELEASE_LOG_ERROR(ProcessSuspension, "Background task expired while holding WebKit ProcessAssertion (isMainThread? %d).", RunLoop::isMain());
115             // The expiration handler gets called on a non-main thread when the underlying assertion could not be taken (rdar://problem/27278419).
116             if (RunLoop::isMain())
117                 [self _notifyAssertionsOfImminentSuspension];
118             else {
119                 dispatch_sync(dispatch_get_main_queue(), ^{
120                     [self _notifyAssertionsOfImminentSuspension];
121                 });
122             }
123
124             // Remember that the assertion has expired in the background so we do not try to re-take it until the application becomes foreground again.
125             _assertionHasExpiredInTheBackground = YES;
126             [self _releaseBackgroundTask];
127         }];
128     } else if (_assertionsNeedingBackgroundTask.isEmpty())
129         [self _releaseBackgroundTask];
130 }
131
132 - (void)_releaseBackgroundTask
133 {
134     if (_backgroundTask == UIBackgroundTaskInvalid)
135         return;
136
137     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
138     [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
139     _backgroundTask = UIBackgroundTaskInvalid;
140 }
141
142 @end
143
144 namespace WebKit {
145
146 const BKSProcessAssertionFlags suspendedTabFlags = (BKSProcessAssertionAllowIdleSleep);
147 const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionPreventTaskSuspend);
148 const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
149
150 static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
151 {
152     switch (assertionState) {
153     case AssertionState::Suspended:
154         return suspendedTabFlags;
155     case AssertionState::Background:
156     case AssertionState::UnboundedNetworking:
157         return backgroundTabFlags;
158     case AssertionState::Foreground:
159         return foregroundTabFlags;
160     }
161 }
162
163 static AssertionReason reasonForState(AssertionState assertionState)
164 {
165     switch (assertionState) {
166     case AssertionState::UnboundedNetworking:
167         return AssertionReason::FinishTaskUnbounded;
168     case AssertionState::Suspended:
169     case AssertionState::Background:
170     case AssertionState::Foreground:
171         return AssertionReason::Extension;
172     }
173 }
174
175 static BKSProcessAssertionReason toBKSProcessAssertionReason(AssertionReason reason)
176 {
177     switch (reason) {
178     case AssertionReason::Extension:
179         return BKSProcessAssertionReasonExtension;
180     case AssertionReason::FinishTask:
181         return BKSProcessAssertionReasonFinishTask;
182     case AssertionReason::FinishTaskUnbounded:
183         return BKSProcessAssertionReasonFinishTaskUnbounded;
184     }
185 }
186
187 ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState)
188     : ProcessAssertion(pid, name, assertionState, reasonForState(assertionState))
189 {
190 }
191
192 ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState, AssertionReason assertionReason)
193     : m_assertionState(assertionState)
194 {
195     auto weakThis = makeWeakPtr(*this);
196     BKSProcessAssertionAcquisitionHandler handler = ^(BOOL acquired) {
197         if (!acquired) {
198             RELEASE_LOG_ERROR(ProcessSuspension, " %p - ProcessAssertion() PID %d Unable to acquire assertion for process with PID %d", this, getpid(), pid);
199             dispatch_async(dispatch_get_main_queue(), ^{
200                 if (weakThis)
201                     processAssertionWasInvalidated();
202             });
203         }
204     };
205     RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() PID %d acquiring assertion for process with PID %d, name '%s'", this, getpid(), pid, name.utf8().data());
206     
207     m_assertion = adoptNS([[BKSProcessAssertion alloc] initWithPID:pid flags:flagsForState(assertionState) reason:toBKSProcessAssertionReason(assertionReason) name:(NSString *)name withHandler:handler]);
208     m_assertion.get().invalidationHandler = ^() {
209         dispatch_async(dispatch_get_main_queue(), ^{
210             RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() Process assertion for process with PID %d was invalidated", this, pid);
211             if (weakThis)
212                 processAssertionWasInvalidated();
213         });
214     };
215 }
216
217 ProcessAssertion::~ProcessAssertion()
218 {
219     m_assertion.get().invalidationHandler = nil;
220
221     RELEASE_LOG(ProcessSuspension, "%p - ~ProcessAssertion() Releasing process assertion", this);
222     [m_assertion invalidate];
223 }
224
225 void ProcessAssertion::processAssertionWasInvalidated()
226 {
227     ASSERT(RunLoop::isMain());
228     RELEASE_LOG_ERROR(ProcessSuspension, "%p - ProcessAssertion::processAssertionWasInvalidated()", this);
229
230     m_validity = Validity::No;
231 }
232
233 void ProcessAssertion::setState(AssertionState assertionState)
234 {
235     if (m_assertionState == assertionState)
236         return;
237
238     RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u) previousState: %u", this, static_cast<unsigned>(assertionState), static_cast<unsigned>(m_assertionState));
239     m_assertionState = assertionState;
240     [m_assertion setFlags:flagsForState(assertionState)];
241 }
242
243 void ProcessAndUIAssertion::updateRunInBackgroundCount()
244 {
245     bool shouldHoldBackgroundTask = validity() != Validity::No && state() != AssertionState::Suspended;
246     if (m_isHoldingBackgroundTask == shouldHoldBackgroundTask)
247         return;
248
249     if (shouldHoldBackgroundTask)
250         [[WKProcessAssertionBackgroundTaskManager shared] addAssertionNeedingBackgroundTask:*this];
251     else
252         [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
253
254     m_isHoldingBackgroundTask = shouldHoldBackgroundTask;
255 }
256
257 ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, const String& reason, AssertionState assertionState)
258     : ProcessAssertion(pid, reason, assertionState)
259 {
260     updateRunInBackgroundCount();
261 }
262
263 ProcessAndUIAssertion::~ProcessAndUIAssertion()
264 {
265     if (m_isHoldingBackgroundTask)
266         [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
267 }
268
269 void ProcessAndUIAssertion::setState(AssertionState assertionState)
270 {
271     ProcessAssertion::setState(assertionState);
272     updateRunInBackgroundCount();
273 }
274
275 void ProcessAndUIAssertion::uiAssertionWillExpireImminently()
276 {
277     if (auto* client = this->client())
278         client->uiAssertionWillExpireImminently();
279 }
280
281 void ProcessAndUIAssertion::processAssertionWasInvalidated()
282 {
283     ProcessAssertion::processAssertionWasInvalidated();
284     updateRunInBackgroundCount();
285 }
286
287 } // namespace WebKit
288
289 #endif // PLATFORM(IOS_FAMILY)