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