1// Copyright 2012 The Chromium Authors 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 "base/ios/scoped_critical_action.h" 6 7#import <UIKit/UIKit.h> 8#include <float.h> 9 10#include <atomic> 11 12#include "base/ios/ios_util.h" 13#include "base/logging.h" 14#include "base/memory/ref_counted.h" 15#include "base/memory/singleton.h" 16#include "base/metrics/histogram_macros.h" 17#include "base/metrics/user_metrics.h" 18#include "base/strings/string_piece.h" 19#include "base/strings/sys_string_conversions.h" 20#include "base/synchronization/lock.h" 21 22namespace base::ios { 23namespace { 24 25constexpr base::TimeDelta kMaxTaskReuseDelay = base::Seconds(3); 26 27// Used for unit-testing only. 28std::atomic<int> g_num_active_background_tasks_for_test{0}; 29 30} // namespace 31 32ScopedCriticalAction::ScopedCriticalAction(StringPiece task_name) 33 : task_handle_(ActiveBackgroundTaskCache::GetInstance() 34 ->EnsureBackgroundTaskExistsWithName(task_name)) {} 35 36ScopedCriticalAction::~ScopedCriticalAction() { 37 ActiveBackgroundTaskCache::GetInstance()->ReleaseHandle(task_handle_); 38} 39 40// static 41void ScopedCriticalAction::ClearNumActiveBackgroundTasksForTest() { 42 g_num_active_background_tasks_for_test.store(0); 43} 44 45// static 46int ScopedCriticalAction::GetNumActiveBackgroundTasksForTest() { 47 return g_num_active_background_tasks_for_test.load(); 48} 49 50ScopedCriticalAction::Core::Core() 51 : background_task_id_(UIBackgroundTaskInvalid) {} 52 53ScopedCriticalAction::Core::~Core() { 54 DCHECK_EQ(background_task_id_, UIBackgroundTaskInvalid); 55} 56 57// This implementation calls |beginBackgroundTaskWithName:expirationHandler:| 58// when instantiated and |endBackgroundTask:| when destroyed, creating a scope 59// whose execution will continue (temporarily) even after the app is 60// backgrounded. 61// static 62void ScopedCriticalAction::Core::StartBackgroundTask(scoped_refptr<Core> core, 63 StringPiece task_name) { 64 UIApplication* application = UIApplication.sharedApplication; 65 if (!application) { 66 return; 67 } 68 69 AutoLock lock_scope(core->background_task_id_lock_); 70 if (core->background_task_id_ != UIBackgroundTaskInvalid) { 71 // Already started. 72 return; 73 } 74 75 NSString* task_string = 76 !task_name.empty() ? base::SysUTF8ToNSString(task_name) : nil; 77 core->background_task_id_ = [application 78 beginBackgroundTaskWithName:task_string 79 expirationHandler:^{ 80 DLOG(WARNING) 81 << "Background task with name <" 82 << base::SysNSStringToUTF8(task_string) << "> and with " 83 << "id " << core->background_task_id_ << " expired."; 84 // Note if |endBackgroundTask:| is not called for each task 85 // before time expires, the system kills the application. 86 EndBackgroundTask(core); 87 }]; 88 89 if (core->background_task_id_ == UIBackgroundTaskInvalid) { 90 DLOG(WARNING) << "beginBackgroundTaskWithName:<" << task_name << "> " 91 << "expirationHandler: returned an invalid ID"; 92 } else { 93 VLOG(3) << "Beginning background task <" << task_name << "> with id " 94 << core->background_task_id_; 95 g_num_active_background_tasks_for_test.fetch_add(1, 96 std::memory_order_relaxed); 97 } 98} 99 100// static 101void ScopedCriticalAction::Core::EndBackgroundTask(scoped_refptr<Core> core) { 102 UIBackgroundTaskIdentifier task_id; 103 { 104 AutoLock lock_scope(core->background_task_id_lock_); 105 if (core->background_task_id_ == UIBackgroundTaskInvalid) { 106 // Never started successfully or already ended. 107 return; 108 } 109 task_id = 110 static_cast<UIBackgroundTaskIdentifier>(core->background_task_id_); 111 core->background_task_id_ = UIBackgroundTaskInvalid; 112 } 113 114 VLOG(3) << "Ending background task with id " << task_id; 115 [[UIApplication sharedApplication] endBackgroundTask:task_id]; 116 g_num_active_background_tasks_for_test.fetch_sub(1, 117 std::memory_order_relaxed); 118} 119 120ScopedCriticalAction::ActiveBackgroundTaskCache::InternalEntry:: 121 InternalEntry() = default; 122 123ScopedCriticalAction::ActiveBackgroundTaskCache::InternalEntry:: 124 ~InternalEntry() = default; 125 126ScopedCriticalAction::ActiveBackgroundTaskCache::InternalEntry::InternalEntry( 127 InternalEntry&&) = default; 128 129ScopedCriticalAction::ActiveBackgroundTaskCache::InternalEntry& 130ScopedCriticalAction::ActiveBackgroundTaskCache::InternalEntry::operator=( 131 InternalEntry&&) = default; 132 133// static 134ScopedCriticalAction::ActiveBackgroundTaskCache* 135ScopedCriticalAction::ActiveBackgroundTaskCache::GetInstance() { 136 return base::Singleton< 137 ActiveBackgroundTaskCache, 138 base::LeakySingletonTraits<ActiveBackgroundTaskCache>>::get(); 139} 140 141ScopedCriticalAction::ActiveBackgroundTaskCache::ActiveBackgroundTaskCache() = 142 default; 143 144ScopedCriticalAction::ActiveBackgroundTaskCache::~ActiveBackgroundTaskCache() = 145 default; 146 147ScopedCriticalAction::ActiveBackgroundTaskCache::Handle ScopedCriticalAction:: 148 ActiveBackgroundTaskCache::EnsureBackgroundTaskExistsWithName( 149 StringPiece task_name) { 150 const base::TimeTicks now = base::TimeTicks::Now(); 151 const base::TimeTicks min_reusable_time = now - kMaxTaskReuseDelay; 152 NameAndTime min_reusable_key{task_name, min_reusable_time}; 153 154 Handle handle; 155 { 156 AutoLock lock_scope(entries_map_lock_); 157 auto lower_it = entries_map_.lower_bound(min_reusable_key); 158 159 if (lower_it != entries_map_.end() && lower_it->first.first == task_name) { 160 // A reusable Core instance exists, with the same name and created 161 // recently enough to warrant reuse. 162 DCHECK_GE(lower_it->first.second, min_reusable_time); 163 handle = lower_it; 164 } else { 165 // No reusable entry exists, so a new entry needs to be created. 166 auto it = entries_map_.emplace_hint( 167 lower_it, NameAndTime{std::move(min_reusable_key.first), now}, 168 InternalEntry{}); 169 DCHECK_EQ(it->first.second, now); 170 DCHECK(!it->second.core); 171 handle = it; 172 handle->second.core = MakeRefCounted<Core>(); 173 } 174 175 // This guarantees a non-zero counter and hence the deletion of this map 176 // entry during this function body, even after the lock is released. 177 ++handle->second.num_active_handles; 178 } 179 180 // If this call didn't newly-create a Core instance, the call to 181 // StartBackgroundTask() is almost certainly (barring race conditions) 182 // unnecessary. It is however harmless to invoke it twice. 183 Core::StartBackgroundTask(handle->second.core, task_name); 184 185 return handle; 186} 187 188void ScopedCriticalAction::ActiveBackgroundTaskCache::ReleaseHandle( 189 Handle handle) { 190 scoped_refptr<Core> background_task_to_end; 191 192 { 193 AutoLock lock_scope(entries_map_lock_); 194 --handle->second.num_active_handles; 195 if (handle->second.num_active_handles == 0) { 196 // Move to |background_task_to_end| so the global lock is released before 197 // invoking EndBackgroundTask() which is expensive. 198 background_task_to_end = std::move(handle->second.core); 199 entries_map_.erase(handle); 200 } 201 } 202 203 // Note that at this point another, since the global lock was released, 204 // another task could have started with the same name, but this harmless. 205 if (background_task_to_end != nullptr) { 206 Core::EndBackgroundTask(std::move(background_task_to_end)); 207 } 208} 209 210} // namespace base::ios 211