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
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/ios/scoped_critical_action.h"
#import <UIKit/UIKit.h>
#include <float.h>
#include "base/ios/ios_util.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/sys_string_conversions.h"
#include "base/synchronization/lock.h"
#import "build/branding_buildflags.h"
#if BUILDFLAG(CHROMIUM_BRANDING)
#include <dlfcn.h>
#endif // BUILDFLAG(CHROMIUM_BRANDING)
namespace {
// |backgroundTimeRemaining| is thread-safe, but as of Xcode 11.4 this method
// is still incorrectly marked as not thread-safe, so checking the value of
// |backgroundTimeRemaining| will trigger libMainThreadChecker (if enabled).
// Instead, for Chromium builds only, disable libMainThreadChecker for just
// this call. This logic is only useful for developer builds, and should not
// be included in official builds. These blocks should be removed if future
// versions of the library whitelists |backgroundTimeRemaining|.
NSTimeInterval GetBackgroundTimeRemaining(UIApplication* application) {
#if BUILDFLAG(CHROMIUM_BRANDING)
if (!base::ios::IsRunningOnIOS13OrLater()) {
// On developer iOS12 builds there's no way to suppress the main thread
// checker assert. Since it's a developer build, simply return 0.
return 0;
}
static char const* const lib_main_thread_checker_bundle_path =
#if TARGET_IPHONE_SIMULATOR
"/usr/lib/libMainThreadChecker.dylib";
#else
"/Developer/usr/lib/libMainThreadChecker.dylib";
#endif
static void* handle =
dlopen(lib_main_thread_checker_bundle_path, RTLD_NOLOAD | RTLD_LAZY);
static void (*main_thread_checker_suppression_begin)() =
(void (*)())dlsym(handle, "__main_thread_checker_suppression_begin");
if (main_thread_checker_suppression_begin)
main_thread_checker_suppression_begin();
#endif // BUILDFLAG(CHROMIUM_BRANDING)
NSTimeInterval time = application.backgroundTimeRemaining;
#if BUILDFLAG(CHROMIUM_BRANDING)
static void (*main_thread_checker_suppression_end)() =
(void (*)())dlsym(handle, "__main_thread_checker_suppression_end");
if (main_thread_checker_suppression_end)
main_thread_checker_suppression_end();
dlclose(handle);
#endif // BUILDFLAG(CHROMIUM_BRANDING)
return time;
}
} // namespace
namespace base {
namespace ios {
ScopedCriticalAction::ScopedCriticalAction(StringPiece task_name)
: core_(MakeRefCounted<ScopedCriticalAction::Core>()) {
ScopedCriticalAction::Core::StartBackgroundTask(core_, task_name);
}
ScopedCriticalAction::~ScopedCriticalAction() {
ScopedCriticalAction::Core::EndBackgroundTask(core_);
}
ScopedCriticalAction::Core::Core()
: background_task_id_(UIBackgroundTaskInvalid) {}
ScopedCriticalAction::Core::~Core() {
DCHECK_EQ(background_task_id_, UIBackgroundTaskInvalid);
}
// This implementation calls |beginBackgroundTaskWithName:expirationHandler:|
// when instantiated and |endBackgroundTask:| when destroyed, creating a scope
// whose execution will continue (temporarily) even after the app is
// backgrounded.
// static
void ScopedCriticalAction::Core::StartBackgroundTask(scoped_refptr<Core> core,
StringPiece task_name) {
UIApplication* application = [UIApplication sharedApplication];
if (!application) {
return;
}
NSTimeInterval time = GetBackgroundTimeRemaining(application);
if (time != DBL_MAX && time > 0) {
UMA_HISTOGRAM_MEDIUM_TIMES("IOS.CriticalActionBackgroundTimeRemaining",
base::TimeDelta::FromSeconds(time));
}
NSString* task_string =
!task_name.empty() ? base::SysUTF8ToNSString(task_name) : nil;
core->background_task_id_ = [application
beginBackgroundTaskWithName:task_string
expirationHandler:^{
DLOG(WARNING)
<< "Background task with name <"
<< base::SysNSStringToUTF8(task_string) << "> and with "
<< "id " << core->background_task_id_ << " expired.";
// Note if |endBackgroundTask:| is not called for each task
// before time expires, the system kills the application.
EndBackgroundTask(core);
}];
if (core->background_task_id_ == UIBackgroundTaskInvalid) {
DLOG(WARNING) << "beginBackgroundTaskWithName:<" << task_name << "> "
<< "expirationHandler: returned an invalid ID";
} else {
VLOG(3) << "Beginning background task <" << task_name << "> with id "
<< core->background_task_id_;
}
}
// static
void ScopedCriticalAction::Core::EndBackgroundTask(scoped_refptr<Core> core) {
UIBackgroundTaskIdentifier task_id;
{
AutoLock lock_scope(core->background_task_id_lock_);
if (core->background_task_id_ == UIBackgroundTaskInvalid) {
return;
}
task_id = core->background_task_id_;
core->background_task_id_ = UIBackgroundTaskInvalid;
}
VLOG(3) << "Ending background task with id " << task_id;
[[UIApplication sharedApplication] endBackgroundTask:task_id];
}
} // namespace ios
} // namespace base
|