| // 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/queued_worker_pool.h" |
| |
| #include "base/logging.h" |
| |
| namespace net_instaweb { |
| |
| QueuedAlarm::QueuedAlarm(Scheduler* scheduler, |
| QueuedWorkerPool::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 |