/*
 * 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 != 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
