// Copyright (c) 2008 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/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/histogram.h" #include "base/logging.h" #import "base/scoped_nsautorelease_pool.h" #include "base/scoped_ptr.h" #import "base/singleton_objc.h" #include "base/task.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 { Lock lock_; base::Time last_check_; // Last hung-test check. std::vector outstanding_ops_; // Outstanding operations at last check. size_t running_ = 0; // Operations in |Run()|. size_t outstanding_ = 0; // Operations posted but not completed. } // namespace @implementation WorkerPoolObjC + (NSOperationQueue*)sharedOperationQueue { return SingletonObjC::get(); } @end // @implementation WorkerPoolObjC // TaskOperation adapts Task->Run() for use in an NSOperationQueue. @interface TaskOperation : NSOperation { @private scoped_ptr 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::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) { base::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 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::ScopedNSAutoreleasePool autoreleasePool; std::vector 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; }