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