/*
 * 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/kernel/thread/mock_scheduler.h"

#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/thread/scheduler.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"

namespace net_instaweb {

MockScheduler::MockScheduler(
    ThreadSystem* thread_system, MockTimer* timer)
    : Scheduler(thread_system, timer),
      timer_(timer) {
}

MockScheduler::~MockScheduler() {
}

void MockScheduler::AwaitWakeupUntilUs(int64 wakeup_time_us) {
  // AwaitWakeupUntilUs is used to effectively move simulated time forward
  // during unit tests.  Various callbacks in the test infrastructure
  // can be called as a result of Alarms firing, enabling the simulation of
  // cache/http fetches with non-zero delay, compute-bound rewrites, or
  // threaded rewrites.
  //
  // To make things simple and deterministic, we simply advance the
  // time when the work threads quiesce.
  if (QueuedWorkerPool::AreBusy(workers_) || running_waiting_alarms()) {
    Scheduler::AwaitWakeupUntilUs(timer_->NowUs() + 10 * Timer::kMsUs);
  } else {
    // Can fire off alarms, so we have to be careful to have the lock
    // relinquished.

    mutex()->Unlock();
    if (wakeup_time_us >= timer_->NowUs()) {
      timer_->SetTimeUs(wakeup_time_us);
    }
    mutex()->Lock();
  }
}

void MockScheduler::AwaitQuiescence() {
  ScopedMutex lock(mutex());
  while (QueuedWorkerPool::AreBusy(workers_) || running_waiting_alarms()) {
    Scheduler::AwaitWakeupUntilUs(timer_->NowUs() + 10 * Timer::kMsUs);
  }
}

void MockScheduler::RegisterWorker(QueuedWorkerPool::Sequence* w) {
  ScopedMutex lock(mutex());
  workers_.insert(w);
}

void MockScheduler::UnregisterWorker(QueuedWorkerPool::Sequence* w) {
  ScopedMutex lock(mutex());
  workers_.erase(w);
}

void MockScheduler::AdvanceTimeUs(int64 timeout_us) {
  ScopedMutex lock(mutex());
  SetTimeUsMutexHeld(timer_->NowUs() + timeout_us);
}

void MockScheduler::SetTimeUsMutexHeld(int64 time_us) {
  // BlockingTimedWaitUs does not guarantee that it will wait all
  // the way until a timeout occurs, so we loop.
  while (timer_->NowUs() < time_us) {
    BlockingTimedWaitUs(time_us - timer_->NowUs());
  }
}

void MockScheduler::SetTimeUs(int64 time_us) {
  CHECK_LE(timer_->NowUs(), time_us)
      << "Time cannot go backward in the scheduler: "
      << "delta=" << timer_->NowUs() - time_us;
  {
    ScopedMutex lock(mutex());
    SetTimeUsMutexHeld(time_us);
  }
}

}  // namespace net_instaweb
