xref: /aosp_15_r20/external/cronet/base/ios/scoped_critical_action.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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