blob: 0cf8a7dead246c45cdeb9edcf36c680346e4e057 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#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 != nullptr);
// 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 != nullptr) {
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 = nullptr;
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