blob: f2e593afcc38ea5d80ab601cdeb85c32041e9aac [file] [log] [blame]
// Copyright 2015 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: cheesy@google.com (Steve Hill)
#include "pagespeed/controller/queued_expensive_operation_controller.h"
#include "base/logging.h"
namespace net_instaweb {
const char QueuedExpensiveOperationController::kActiveExpensiveOperations[] =
"active-expensive-operations";
const char QueuedExpensiveOperationController::kQueuedExpensiveOperations[] =
"queued-expensive-operations";
const char QueuedExpensiveOperationController::kPermittedExpensiveOperations[] =
"permitted-expensive-operations";
QueuedExpensiveOperationController::QueuedExpensiveOperationController(
int max_expensive_operations, ThreadSystem* thread_system,
Statistics* stats)
: max_in_progress_(max_expensive_operations),
num_in_progress_(0),
mutex_(thread_system->NewMutex()),
active_operations_counter_(
stats->GetUpDownCounter(kActiveExpensiveOperations)),
queued_operations_counter_(
stats->GetUpDownCounter(kQueuedExpensiveOperations)),
permitted_operations_counter_(
stats->GetTimedVariable(kPermittedExpensiveOperations)) {
}
QueuedExpensiveOperationController::~QueuedExpensiveOperationController() {
// queue_ is *supposed* to be empty at this point. In case it's not, we
// should make sure it gets cleaned up to avoid a leak. Given that we expect
// the controller will be deleted after everything else has shutdown, running
// Cancel is a bit scary. Instead we just delete the contents of the queue.
// Note that Function DCHECKS that it was run upon deletion, so the DCHECK
// below would have fired in the loop, regardless.
DCHECK(queue_.empty());
while (!queue_.empty()) {
delete queue_.front();
queue_.pop();
}
}
void QueuedExpensiveOperationController::InitStats(Statistics* statistics) {
statistics->AddGlobalUpDownCounter(kActiveExpensiveOperations);
statistics->AddGlobalUpDownCounter(kQueuedExpensiveOperations);
statistics->AddTimedVariable(kPermittedExpensiveOperations,
Statistics::kDefaultGroup);
}
void QueuedExpensiveOperationController::ScheduleExpensiveOperation(
Function* callback) {
ScopedMutex lock(mutex_.get());
CHECK(callback != NULL);
// If we are configured to disallow all expensive operations, immediately deny
// the request and don't queue it.
if (max_in_progress_ == 0) {
lock.Release();
callback->CallCancel();
return;
}
// If we have a spare slot, run the callback immediately.
if (max_in_progress_ < 0 || num_in_progress_ < max_in_progress_) {
IncrementInProgress();
lock.Release();
callback->CallRun();
return;
} else {
// No slot, so enqueue the callback for later.
Enqueue(callback);
}
}
void QueuedExpensiveOperationController::NotifyExpensiveOperationComplete() {
ScopedMutex lock(mutex_.get());
DecrementInProgress();
// We should now have a slot available. If there's something on the queue,
// run it.
if (max_in_progress_ > 0) {
CHECK_LT(num_in_progress_, max_in_progress_);
}
Function* callback = Dequeue();
if (callback != NULL) {
IncrementInProgress();
lock.Release();
callback->CallRun();
return;
}
}
void QueuedExpensiveOperationController::Enqueue(Function* callback) {
queue_.push(callback);
queued_operations_counter_->Set(queue_.size());
}
Function* QueuedExpensiveOperationController::Dequeue() {
Function* result = NULL;
if (!queue_.empty()) {
result = queue_.front();
queue_.pop();
queued_operations_counter_->Set(queue_.size());
}
return result;
}
void QueuedExpensiveOperationController::IncrementInProgress() {
++num_in_progress_;
active_operations_counter_->Set(num_in_progress_);
permitted_operations_counter_->IncBy(1);
}
void QueuedExpensiveOperationController::DecrementInProgress() {
DCHECK_GT(num_in_progress_, 0);
if (num_in_progress_ > 0) {
--num_in_progress_;
active_operations_counter_->Set(num_in_progress_);
}
}
} // namespace net_instaweb