/*
 * 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/scheduler_based_abstract_lock.h"

#include "base/logging.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/debug.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/thread/scheduler.h"

namespace net_instaweb {

namespace {

// Number of times we busy spin before we start to sleep.
// TODO(jmaessen): Is this the right setting?
const int kBusySpinIterations = 100;
const int64 kMaxSpinSleepMs = Timer::kMinuteMs;  // Never sleep for more than 1m
const int64 kMinTriesPerSteal = 2;  // Try to lock twice / steal interval.

// We back off exponentially, with a constant of 1.5.  We add an extra ms to
// this backoff to avoid problems with wait intervals of 0 or 1.  We bound the
// blocking time at kMaxSpinSleepMs.
int64 Backoff(int64 interval_ms, int64 max_interval_ms) {
  int64 new_interval_ms = 1 + interval_ms + (interval_ms >> 1);
  if (new_interval_ms >= max_interval_ms) {
    new_interval_ms = max_interval_ms;
    // Log the first time we reach or cross the threshold.
    // TODO(jmaessen): LOG(ERROR) is deadlocking.  Why?  We're using cooperative
    // thread cancellation in the tests that hang, and it sometimes succeeds.
    if (false && interval_ms != max_interval_ms) {
      LOG(ERROR) << "Reached maximum sleep time " << StackTraceString();
    }
  }
  return new_interval_ms;
}

// Compute new backoff time interval given current interval_ms, but don't exceed
// max_interval_ms or have the interval continue much past end_time_ms.
int64 IntervalWithEnd(Timer* timer, int64 interval_ms,
                      int64 max_interval_ms, int64 end_time_ms) {
  int64 now_ms = timer->NowMs();
  int64 remaining = end_time_ms - now_ms;
  interval_ms = Backoff(interval_ms, max_interval_ms);
  if (remaining > interval_ms) {
    return interval_ms;
  } else {
    return remaining;
  }
}

// This object actually contains the state needed for periodically polling the
// provided lock using the try_lock method, and for eventually calling or
// canceling the callback. While it may feel attractive to reuse this object,
// it's not actually safe as it runs into races trying to check the
// delete_after_callback_ bit.
class TimedWaitPollState : public Function {
 public:
  typedef bool (SchedulerBasedAbstractLock::*TryLockMethod)(int64 steal_ms);

  TimedWaitPollState(
      Scheduler* scheduler, Function* callback,
      SchedulerBasedAbstractLock* lock, TryLockMethod try_lock,
      int64 steal_ms, int64 end_time_ms,
      int64 max_interval_ms)
      : scheduler_(scheduler),
        callback_(callback),
        lock_(lock),
        try_lock_(try_lock),
        steal_ms_(steal_ms),
        end_time_ms_(end_time_ms),
        max_interval_ms_(max_interval_ms),
        interval_ms_(0) {}
  virtual ~TimedWaitPollState() { }

  // Note: doesn't actually clone interval_ms_.
  TimedWaitPollState* Clone() {
    return new TimedWaitPollState(scheduler_, callback_, lock_,
                                  try_lock_, steal_ms_, end_time_ms_,
                                  max_interval_ms_);
  }

 protected:
  virtual void Run() {
    if ((lock_->*try_lock_)(steal_ms_)) {
      callback_->CallRun();
      return;
    }
    Timer* timer = scheduler_->timer();
    int64 now_ms = timer->NowMs();
    if (now_ms >= end_time_ms_) {
      callback_->CallCancel();
      return;
    }

    TimedWaitPollState* next_try = Clone();
    next_try->interval_ms_ =
        IntervalWithEnd(timer, interval_ms_, max_interval_ms_, end_time_ms_);
    scheduler_->AddAlarmAtUs((now_ms + next_try->interval_ms_) * Timer::kMsUs,
                             next_try);
  }

 private:
  Scheduler* scheduler_;
  Function* callback_;
  SchedulerBasedAbstractLock* lock_;
  TryLockMethod try_lock_;
  const int64 steal_ms_;
  const int64 end_time_ms_;
  const int64 max_interval_ms_;
  int64 interval_ms_;
};

}  // namespace

SchedulerBasedAbstractLock::~SchedulerBasedAbstractLock() { }

void SchedulerBasedAbstractLock::PollAndCallback(
    TryLockMethod try_lock, int64 steal_ms, int64 wait_ms, Function* callback) {
  // Measure ending time from immediately after failure of the fast path.
  int64 end_time_ms = scheduler()->timer()->NowMs() + wait_ms;
  if (BusySpin(try_lock, steal_ms)) {
    callback->CallRun();
    return;
  }
  // Slow path.  Allocate a TimedWaitPollState object and cede control to it.
  int64 max_interval_ms = (steal_ms + 1) / kMinTriesPerSteal;
  TimedWaitPollState* poller =
      new TimedWaitPollState(scheduler(), callback, this,
                             try_lock, steal_ms,
                             end_time_ms, max_interval_ms);
  poller->CallRun();
}

// The basic structure of each locking operation is the same:
// Quick check for a free lock using TryLock().
// If that fails, call PollAndCallBack, which:
//   * First busy spins attempting to obtain the lock
//   * If that fails, schedules an alarm that attempts to take the lock,
//     or failing that backs off and schedules another alarm.
// We run callbacks as soon as possible.  We could instead defer them
// to a scheduler sequence, but in practice we don't have an appropriate
// sequence to hand when we we stand up the lock manager.  So it's up to
// callers to schedule appropriate tasks when locks have been obtained.

bool SchedulerBasedAbstractLock::LockTimedWait(int64 wait_ms) {
  if (TryLock()) {
    // Fast path.
    return true;
  } else {
    SchedulerBlockingFunction block(scheduler());
    PollAndCallback(&SchedulerBasedAbstractLock::TryLockIgnoreSteal,
                    kMinTriesPerSteal * kMaxSpinSleepMs, wait_ms, &block);
    return block.Block();
  }
}

void SchedulerBasedAbstractLock::LockTimedWait(
    int64 wait_ms, Function* callback) {
  if (TryLock()) {
    // Fast path.
    callback->CallRun();
  } else {
    PollAndCallback(&SchedulerBasedAbstractLock::TryLockIgnoreSteal,
                    kMinTriesPerSteal * kMaxSpinSleepMs, wait_ms, callback);
  }
}

bool SchedulerBasedAbstractLock::LockTimedWaitStealOld(
    int64 wait_ms, int64 steal_ms) {
  if (TryLock()) {
    // Fast path.
    return true;
  } else {
    SchedulerBlockingFunction block(scheduler());
    PollAndCallback(&SchedulerBasedAbstractLock::TryLockStealOld,
                    steal_ms, wait_ms, &block);
    return block.Block();
  }
}

void SchedulerBasedAbstractLock::LockTimedWaitStealOld(
    int64 wait_ms, int64 steal_ms, Function* callback) {
  if (TryLock()) {
    // Fast path.
    callback->CallRun();
  } else if (wait_ms == 0) {
    // Short circuit 0ms wait to just poll once, rather than calling
    // the busy-loop, which runs it 100x (kBusySpinIterations).
    if (TryLockStealOld(steal_ms)) {
      callback->CallRun();
    } else {
      callback->CallCancel();
    }
  } else {
    PollAndCallback(&SchedulerBasedAbstractLock::TryLockStealOld,
                    steal_ms, wait_ms, callback);
  }
}

// We implement spinning without regard to whether the underlying lock primitive
// can time out or not.  This wrapper method is just TryLock with a bogus
// timeout parameter, so that we can pass timeout-free TryLock to a spin
// routine.
bool SchedulerBasedAbstractLock::TryLockIgnoreSteal(int64 steal_ignored) {
  return TryLock();
}

// Actively attempt to take lock without pausing.
bool SchedulerBasedAbstractLock::BusySpin(TryLockMethod try_lock,
                                          int64 steal_ms) {
  for (int i = 0; i < kBusySpinIterations; i++) {
    if ((this->*try_lock)(steal_ms)) {
      return true;
    }
  }
  return false;
}

}  // namespace net_instaweb
