summaryrefslogtreecommitdiffstats
path: root/base/worker_pool_mac.mm
blob: 956cfb4984ea2c63b2211157f8763f6513bfced6 (plain)
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
// Copyright (c) 2010 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/worker_pool_mac.h"

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/metrics/histogram.h"
#include "base/scoped_nsobject.h"
#include "base/scoped_ptr.h"
#include "base/task.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/worker_pool_linux.h"

// When C++ exceptions are disabled, the C++ library defines |try| and
// |catch| so as to allow exception-expecting C++ code to build properly when
// language support for exceptions is not present.  These macros interfere
// with the use of |@try| and |@catch| in Objective-C files such as this one.
// Undefine these macros here, after everything has been #included, since
// there will be no C++ uses and only Objective-C uses from this point on.
#undef try
#undef catch

namespace {

// |true| to use the Linux WorkerPool implementation for
// |WorkerPool::PostTask()|.
bool use_linux_workerpool_ = true;

Lock lock_;
base::Time last_check_;            // Last hung-test check.
std::vector<id> outstanding_ops_;  // Outstanding operations at last check.
size_t running_ = 0;               // Operations in |Run()|.
size_t outstanding_ = 0;           // Operations posted but not completed.

// We use a wrapper struct here for the NSOperationQueue so that the object
// can be released when LazyInstance calls our destructor.
struct NSOperationQueueWrapper {
  NSOperationQueueWrapper() {
    operation_queue.reset([[NSOperationQueue alloc] init]);
  }
  scoped_nsobject<NSOperationQueue> operation_queue;
};

static base::LazyInstance<NSOperationQueueWrapper> g_nsoperation_queue(
    base::LINKER_INITIALIZED);

}  // namespace

namespace worker_pool_mac {

void SetUseLinuxWorkerPool(bool flag) {
  use_linux_workerpool_ = flag;
}

}  // namespace worker_pool_mac

@implementation WorkerPoolObjC

+ (NSOperationQueue*)sharedOperationQueue {
  return g_nsoperation_queue.Get().operation_queue.get();
}

@end  // @implementation WorkerPoolObjC

// TaskOperation adapts Task->Run() for use in an NSOperationQueue.
@interface TaskOperation : NSOperation {
 @private
  scoped_ptr<Task> task_;
}

// Returns an autoreleased instance of TaskOperation.  See -initWithTask: for
// details.
+ (id)taskOperationWithTask:(Task*)task;

// Designated initializer.  |task| is adopted as the Task* whose Run method
// this operation will call when executed.
- (id)initWithTask:(Task*)task;

@end  // @interface TaskOperation

@implementation TaskOperation

+ (id)taskOperationWithTask:(Task*)task {
  return [[[TaskOperation alloc] initWithTask:task] autorelease];
}

- (id)init {
  return [self initWithTask:NULL];
}

- (id)initWithTask:(Task*)task {
  if ((self = [super init])) {
    task_.reset(task);
  }
  return self;
}

- (void)main {
  DCHECK(task_.get()) << "-[TaskOperation main] called with no task";
  if (!task_.get()) {
    return;
  }

  {
    AutoLock locked(lock_);
    ++running_;
  }

  base::mac::ScopedNSAutoreleasePool autoreleasePool;

  @try {
    task_->Run();
  } @catch(NSException* exception) {
    LOG(ERROR) << "-[TaskOperation main] caught an NSException: "
               << [[exception description] UTF8String];
  } @catch(id exception) {
    LOG(ERROR) << "-[TaskOperation main] caught an unknown exception";
  }

  task_.reset(NULL);

  {
    AutoLock locked(lock_);
    --running_;
    --outstanding_;
  }
}

- (void)dealloc {
  // Getting the task_ contents without a lock can lead to a benign data race.
  // We annotate it to stay silent under ThreadSanitizer.
  ANNOTATE_IGNORE_READS_BEGIN();
  DCHECK(!task_.get())
      << "-[TaskOperation dealloc] called without running task";
  ANNOTATE_IGNORE_READS_END();
  [super dealloc];
}

@end  // @implementation TaskOperation

bool WorkerPool::PostTask(const tracked_objects::Location& from_here,
                          Task* task, bool task_is_slow) {
  if (use_linux_workerpool_) {
    return worker_pool_mac::MacPostTaskHelper(from_here, task, task_is_slow);
  }

  base::mac::ScopedNSAutoreleasePool autorelease_pool;

  // Ignore |task_is_slow|, it doesn't map directly to any tunable aspect of
  // an NSOperation.

  DCHECK(task) << "WorkerPool::PostTask called with no task";
  if (!task) {
    return false;
  }

  task->SetBirthPlace(from_here);

  NSOperationQueue* operation_queue = [WorkerPoolObjC sharedOperationQueue];
  [operation_queue addOperation:[TaskOperation taskOperationWithTask:task]];

  if ([operation_queue isSuspended]) {
    LOG(WARNING) << "WorkerPool::PostTask freeing stuck NSOperationQueue";

    // Nothing should ever be suspending this queue, but in case it winds up
    // happening, free things up.  This is a purely speculative shot in the
    // dark for http://crbug.com/20471.
    [operation_queue setSuspended:NO];
  }

  // Periodically calculate the set of operations which have not made
  // progress and report how many there are.  This should provide a
  // sense of how many clients are seeing hung operations of any sort,
  // and a sense of how many clients are seeing "too many" hung
  // operations.
  std::vector<id> hung_ops;
  size_t outstanding_delta = 0;
  size_t running_ops = 0;
  {
    const base::TimeDelta kCheckPeriod(base::TimeDelta::FromMinutes(10));
    base::Time now = base::Time::Now();

    AutoLock locked(lock_);
    ++outstanding_;
    running_ops = running_;
    if (last_check_.is_null() || now - last_check_ > kCheckPeriod) {
      base::mac::ScopedNSAutoreleasePool autoreleasePool;
      std::vector<id> ops;
      for (id op in [operation_queue operations]) {
        // DO NOT RETAIN.
        ops.push_back(op);
      }
      std::sort(ops.begin(), ops.end());

      outstanding_delta = outstanding_ - ops.size();

      std::set_intersection(outstanding_ops_.begin(), outstanding_ops_.end(),
                            ops.begin(), ops.end(),
                            std::back_inserter(hung_ops));

      outstanding_ops_.swap(ops);
      last_check_ = now;
    }
  }

  // Don't report "nothing to report".
  const size_t kUnaccountedOpsDelta = 10;
  if (hung_ops.size() > 0 || outstanding_delta > kUnaccountedOpsDelta) {
    UMA_HISTOGRAM_COUNTS_100("OSX.HungWorkers", hung_ops.size());
    UMA_HISTOGRAM_COUNTS_100("OSX.OutstandingDelta", outstanding_delta);
    UMA_HISTOGRAM_COUNTS_100("OSX.RunningOps", running_ops);
  }

  return true;
}