blob: bbc789265d7b81c819f63055209978481f9622ac [file] [log] [blame]
// 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/logging.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/metrics/histogram.h"
#include "base/scoped_ptr.h"
#import "base/singleton_objc.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.
} // namespace
namespace worker_pool_mac {
void SetUseLinuxWorkerPool(bool flag) {
use_linux_workerpool_ = flag;
}
} // namespace worker_pool_mac
@implementation WorkerPoolObjC
+ (NSOperationQueue*)sharedOperationQueue {
return SingletonObjC<NSOperationQueue>::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;
}