Home | History | Annotate | Download | only in base
      1 /*
      2  *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include <algorithm>
     12 
     13 #include "webrtc/base/taskrunner.h"
     14 
     15 #include "webrtc/base/common.h"
     16 #include "webrtc/base/scoped_ptr.h"
     17 #include "webrtc/base/task.h"
     18 #include "webrtc/base/logging.h"
     19 
     20 namespace rtc {
     21 
     22 TaskRunner::TaskRunner()
     23   : TaskParent(this),
     24     next_timeout_task_(NULL),
     25     tasks_running_(false)
     26 #if !defined(NDEBUG)
     27     , abort_count_(0),
     28     deleting_task_(NULL)
     29 #endif
     30 {
     31 }
     32 
     33 TaskRunner::~TaskRunner() {
     34   // this kills and deletes children silently!
     35   AbortAllChildren();
     36   InternalRunTasks(true);
     37 }
     38 
     39 void TaskRunner::StartTask(Task * task) {
     40   tasks_.push_back(task);
     41 
     42   // the task we just started could be about to timeout --
     43   // make sure our "next timeout task" is correct
     44   UpdateTaskTimeout(task, 0);
     45 
     46   WakeTasks();
     47 }
     48 
     49 void TaskRunner::RunTasks() {
     50   InternalRunTasks(false);
     51 }
     52 
     53 void TaskRunner::InternalRunTasks(bool in_destructor) {
     54   // This shouldn't run while an abort is happening.
     55   // If that occurs, then tasks may be deleted in this method,
     56   // but pointers to them will still be in the
     57   // "ChildSet copy" in TaskParent::AbortAllChildren.
     58   // Subsequent use of those task may cause data corruption or crashes.
     59   ASSERT(!abort_count_);
     60   // Running continues until all tasks are Blocked (ok for a small # of tasks)
     61   if (tasks_running_) {
     62     return;  // don't reenter
     63   }
     64 
     65   tasks_running_ = true;
     66 
     67   int64_t previous_timeout_time = next_task_timeout();
     68 
     69   int did_run = true;
     70   while (did_run) {
     71     did_run = false;
     72     // use indexing instead of iterators because tasks_ may grow
     73     for (size_t i = 0; i < tasks_.size(); ++i) {
     74       while (!tasks_[i]->Blocked()) {
     75         tasks_[i]->Step();
     76         did_run = true;
     77       }
     78     }
     79   }
     80   // Tasks are deleted when running has paused
     81   bool need_timeout_recalc = false;
     82   for (size_t i = 0; i < tasks_.size(); ++i) {
     83     if (tasks_[i]->IsDone()) {
     84       Task* task = tasks_[i];
     85       if (next_timeout_task_ &&
     86           task->unique_id() == next_timeout_task_->unique_id()) {
     87         next_timeout_task_ = NULL;
     88         need_timeout_recalc = true;
     89       }
     90 
     91 #if !defined(NDEBUG)
     92       deleting_task_ = task;
     93 #endif
     94       delete task;
     95 #if !defined(NDEBUG)
     96       deleting_task_ = NULL;
     97 #endif
     98       tasks_[i] = NULL;
     99     }
    100   }
    101   // Finally, remove nulls
    102   std::vector<Task *>::iterator it;
    103   it = std::remove(tasks_.begin(),
    104                    tasks_.end(),
    105                    reinterpret_cast<Task *>(NULL));
    106 
    107   tasks_.erase(it, tasks_.end());
    108 
    109   if (need_timeout_recalc)
    110     RecalcNextTimeout(NULL);
    111 
    112   // Make sure that adjustments are done to account
    113   // for any timeout changes (but don't call this
    114   // while being destroyed since it calls a pure virtual function).
    115   if (!in_destructor)
    116     CheckForTimeoutChange(previous_timeout_time);
    117 
    118   tasks_running_ = false;
    119 }
    120 
    121 void TaskRunner::PollTasks() {
    122   // see if our "next potentially timed-out task" has indeed timed out.
    123   // If it has, wake it up, then queue up the next task in line
    124   // Repeat while we have new timed-out tasks.
    125   // TODO: We need to guard against WakeTasks not updating
    126   // next_timeout_task_. Maybe also add documentation in the header file once
    127   // we understand this code better.
    128   Task* old_timeout_task = NULL;
    129   while (next_timeout_task_ &&
    130       old_timeout_task != next_timeout_task_ &&
    131       next_timeout_task_->TimedOut()) {
    132     old_timeout_task = next_timeout_task_;
    133     next_timeout_task_->Wake();
    134     WakeTasks();
    135   }
    136 }
    137 
    138 int64_t TaskRunner::next_task_timeout() const {
    139   if (next_timeout_task_) {
    140     return next_timeout_task_->timeout_time();
    141   }
    142   return 0;
    143 }
    144 
    145 // this function gets called frequently -- when each task changes
    146 // state to something other than DONE, ERROR or BLOCKED, it calls
    147 // ResetTimeout(), which will call this function to make sure that
    148 // the next timeout-able task hasn't changed.  The logic in this function
    149 // prevents RecalcNextTimeout() from getting called in most cases,
    150 // effectively making the task scheduler O-1 instead of O-N
    151 
    152 void TaskRunner::UpdateTaskTimeout(Task* task,
    153                                    int64_t previous_task_timeout_time) {
    154   ASSERT(task != NULL);
    155   int64_t previous_timeout_time = next_task_timeout();
    156   bool task_is_timeout_task = next_timeout_task_ != NULL &&
    157       task->unique_id() == next_timeout_task_->unique_id();
    158   if (task_is_timeout_task) {
    159     previous_timeout_time = previous_task_timeout_time;
    160   }
    161 
    162   // if the relevant task has a timeout, then
    163   // check to see if it's closer than the current
    164   // "about to timeout" task
    165   if (task->timeout_time()) {
    166     if (next_timeout_task_ == NULL ||
    167         (task->timeout_time() <= next_timeout_task_->timeout_time())) {
    168       next_timeout_task_ = task;
    169     }
    170   } else if (task_is_timeout_task) {
    171     // otherwise, if the task doesn't have a timeout,
    172     // and it used to be our "about to timeout" task,
    173     // walk through all the tasks looking for the real
    174     // "about to timeout" task
    175     RecalcNextTimeout(task);
    176   }
    177 
    178   // Note when task_running_, then the running routine
    179   // (TaskRunner::InternalRunTasks) is responsible for calling
    180   // CheckForTimeoutChange.
    181   if (!tasks_running_) {
    182     CheckForTimeoutChange(previous_timeout_time);
    183   }
    184 }
    185 
    186 void TaskRunner::RecalcNextTimeout(Task *exclude_task) {
    187   // walk through all the tasks looking for the one
    188   // which satisfies the following:
    189   //   it's not finished already
    190   //   we're not excluding it
    191   //   it has the closest timeout time
    192 
    193   int64_t next_timeout_time = 0;
    194   next_timeout_task_ = NULL;
    195 
    196   for (size_t i = 0; i < tasks_.size(); ++i) {
    197     Task *task = tasks_[i];
    198     // if the task isn't complete, and it actually has a timeout time
    199     if (!task->IsDone() && (task->timeout_time() > 0))
    200       // if it doesn't match our "exclude" task
    201       if (exclude_task == NULL ||
    202           exclude_task->unique_id() != task->unique_id())
    203         // if its timeout time is sooner than our current timeout time
    204         if (next_timeout_time == 0 ||
    205             task->timeout_time() <= next_timeout_time) {
    206           // set this task as our next-to-timeout
    207           next_timeout_time = task->timeout_time();
    208           next_timeout_task_ = task;
    209         }
    210   }
    211 }
    212 
    213 void TaskRunner::CheckForTimeoutChange(int64_t previous_timeout_time) {
    214   int64_t next_timeout = next_task_timeout();
    215   bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) ||
    216       next_timeout < previous_timeout_time ||
    217       (previous_timeout_time <= CurrentTime() &&
    218        previous_timeout_time != next_timeout);
    219   if (timeout_change) {
    220     OnTimeoutChange();
    221   }
    222 }
    223 
    224 } // namespace rtc
    225