• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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