text: // Copyright 2011 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: morlovich@google.com (Maksim Orlovich)
  
  #include "pagespeed/kernel/thread/queued_alarm.h"
  
  #include "pagespeed/kernel/base/abstract_mutex.h"
  #include "pagespeed/kernel/base/basictypes.h"
  #include "pagespeed/kernel/base/function.h"
  #include "pagespeed/kernel/base/scoped_ptr.h"
  #include "pagespeed/kernel/base/thread_system.h"
  #include "pagespeed/kernel/thread/sequence.h"
  
  #include "base/logging.h"
  
  namespace net_instaweb {
  
  QueuedAlarm::QueuedAlarm(Scheduler* scheduler,
                           Sequence* sequence,
                           int64 wakeup_time_us,
                           Function* callback)
      : mutex_(scheduler->thread_system()->NewMutex()),
        scheduler_(scheduler),
        sequence_(sequence),
        callback_(callback),
        canceled_(false),
        queued_sequence_portion_(false) {
    set_delete_after_callback(false);
    alarm_ = scheduler_->AddAlarmAtUs(wakeup_time_us, this);
  }
  
  QueuedAlarm::~QueuedAlarm() {
    if (callback_ != NULL) {
      callback_->CallCancel();
    }
  }
  
  void QueuedAlarm::CancelAlarm() {
    // Note that this has to be serialized with respect to SequencePortionOfRun
    // and the user callback due to use of sequences, but it may overlap Run().
  
    ScopedMutex hold_our_mutex(mutex_.get());
    if (queued_sequence_portion_) {
      // The actual underlying alarm has run, and we have queued invocation of
      // SequencePortionOfRun(); so we just need to tell it to quash itself.
      // Note that it's unsafe to call CancelAlarm at this point ---
      // the underlying Scheduler::Alarm object is dead.
      canceled_ = true;
      return;
    }
  
    ScopedMutex hold_scheduler_mutex(scheduler_->mutex());
    if (scheduler_->CancelAlarm(alarm_)) {
      // Everything canceled nice and clean, so we can go home.
      hold_our_mutex.Release();  // before deleting self
      hold_scheduler_mutex.Release();  // so we don't hold it during CallCancel
      delete this;
    } else {
      // We're in process of invoking Run(), but didn't actually run its
      // body (serialized on our mutex). In this case, signal to it via
      // canceled_ to wrap things up.
      canceled_ = true;
    }
  }
  
  // Runs in arbitrary thread.
  void QueuedAlarm::Run() {
    ScopedMutex lock(mutex_.get());
    if (canceled_) {
      lock.Release();  // before deleting self
      delete this;
    } else {
      queued_sequence_portion_ = true;
      sequence_->Add(MakeFunction(this,
                                  &QueuedAlarm::SequencePortionOfRun,
                                  &QueuedAlarm::SequencePortionOfRunCancelled));
    }
  }
  
  // Runs in the sequence context
  void QueuedAlarm::SequencePortionOfRun() {
    bool canceled;
    {
      ScopedMutex lock(mutex_.get());
      canceled = canceled_;
    }
  
    if (!canceled) {
      callback_->CallRun();
      callback_ = NULL;  // so we don't do ->CallCancel in ~QueuedAlarm
    }
  
    delete this;
  }
  
  void QueuedAlarm::SequencePortionOfRunCancelled() {
    {
      ScopedMutex lock(mutex_.get());
      DCHECK(canceled_);
    }
    delete this;
  }
  
  }  // namespace net_instaweb
  