1 /* 2 * libjingle 3 * Copyright 2004--2006, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <algorithm> 29 30 #include "talk/base/taskrunner.h" 31 32 #include "talk/base/common.h" 33 #include "talk/base/scoped_ptr.h" 34 #include "talk/base/task.h" 35 #include "talk/base/logging.h" 36 37 namespace talk_base { 38 39 TaskRunner::TaskRunner() 40 : TaskParent(this), 41 next_timeout_task_(NULL), 42 tasks_running_(false) 43 #ifdef _DEBUG 44 , abort_count_(0), 45 deleting_task_(NULL) 46 #endif 47 { 48 } 49 50 TaskRunner::~TaskRunner() { 51 // this kills and deletes children silently! 52 AbortAllChildren(); 53 InternalRunTasks(true); 54 } 55 56 void TaskRunner::StartTask(Task * task) { 57 tasks_.push_back(task); 58 59 // the task we just started could be about to timeout -- 60 // make sure our "next timeout task" is correct 61 UpdateTaskTimeout(task, 0); 62 63 WakeTasks(); 64 } 65 66 void TaskRunner::RunTasks() { 67 InternalRunTasks(false); 68 } 69 70 void TaskRunner::InternalRunTasks(bool in_destructor) { 71 // This shouldn't run while an abort is happening. 72 // If that occurs, then tasks may be deleted in this method, 73 // but pointers to them will still be in the 74 // "ChildSet copy" in TaskParent::AbortAllChildren. 75 // Subsequent use of those task may cause data corruption or crashes. 76 ASSERT(!abort_count_); 77 // Running continues until all tasks are Blocked (ok for a small # of tasks) 78 if (tasks_running_) { 79 return; // don't reenter 80 } 81 82 tasks_running_ = true; 83 84 int64 previous_timeout_time = next_task_timeout(); 85 86 int did_run = true; 87 while (did_run) { 88 did_run = false; 89 // use indexing instead of iterators because tasks_ may grow 90 for (size_t i = 0; i < tasks_.size(); ++i) { 91 while (!tasks_[i]->Blocked()) { 92 tasks_[i]->Step(); 93 did_run = true; 94 } 95 } 96 } 97 // Tasks are deleted when running has paused 98 bool need_timeout_recalc = false; 99 for (size_t i = 0; i < tasks_.size(); ++i) { 100 if (tasks_[i]->IsDone()) { 101 Task* task = tasks_[i]; 102 if (next_timeout_task_ && 103 task->unique_id() == next_timeout_task_->unique_id()) { 104 next_timeout_task_ = NULL; 105 need_timeout_recalc = true; 106 } 107 108 #ifdef _DEBUG 109 deleting_task_ = task; 110 #endif 111 delete task; 112 #ifdef _DEBUG 113 deleting_task_ = NULL; 114 #endif 115 tasks_[i] = NULL; 116 } 117 } 118 // Finally, remove nulls 119 std::vector<Task *>::iterator it; 120 it = std::remove(tasks_.begin(), 121 tasks_.end(), 122 reinterpret_cast<Task *>(NULL)); 123 124 tasks_.erase(it, tasks_.end()); 125 126 if (need_timeout_recalc) 127 RecalcNextTimeout(NULL); 128 129 // Make sure that adjustments are done to account 130 // for any timeout changes (but don't call this 131 // while being destroyed since it calls a pure virtual function). 132 if (!in_destructor) 133 CheckForTimeoutChange(previous_timeout_time); 134 135 tasks_running_ = false; 136 } 137 138 void TaskRunner::PollTasks() { 139 // see if our "next potentially timed-out task" has indeed timed out. 140 // If it has, wake it up, then queue up the next task in line 141 // Repeat while we have new timed-out tasks. 142 // TODO: We need to guard against WakeTasks not updating 143 // next_timeout_task_. Maybe also add documentation in the header file once 144 // we understand this code better. 145 Task* old_timeout_task = NULL; 146 while (next_timeout_task_ && 147 old_timeout_task != next_timeout_task_ && 148 next_timeout_task_->TimedOut()) { 149 old_timeout_task = next_timeout_task_; 150 next_timeout_task_->Wake(); 151 WakeTasks(); 152 } 153 } 154 155 int64 TaskRunner::next_task_timeout() const { 156 if (next_timeout_task_) { 157 return next_timeout_task_->timeout_time(); 158 } 159 return 0; 160 } 161 162 // this function gets called frequently -- when each task changes 163 // state to something other than DONE, ERROR or BLOCKED, it calls 164 // ResetTimeout(), which will call this function to make sure that 165 // the next timeout-able task hasn't changed. The logic in this function 166 // prevents RecalcNextTimeout() from getting called in most cases, 167 // effectively making the task scheduler O-1 instead of O-N 168 169 void TaskRunner::UpdateTaskTimeout(Task* task, 170 int64 previous_task_timeout_time) { 171 ASSERT(task != NULL); 172 int64 previous_timeout_time = next_task_timeout(); 173 bool task_is_timeout_task = next_timeout_task_ != NULL && 174 task->unique_id() == next_timeout_task_->unique_id(); 175 if (task_is_timeout_task) { 176 previous_timeout_time = previous_task_timeout_time; 177 } 178 179 // if the relevant task has a timeout, then 180 // check to see if it's closer than the current 181 // "about to timeout" task 182 if (task->timeout_time()) { 183 if (next_timeout_task_ == NULL || 184 (task->timeout_time() <= next_timeout_task_->timeout_time())) { 185 next_timeout_task_ = task; 186 } 187 } else if (task_is_timeout_task) { 188 // otherwise, if the task doesn't have a timeout, 189 // and it used to be our "about to timeout" task, 190 // walk through all the tasks looking for the real 191 // "about to timeout" task 192 RecalcNextTimeout(task); 193 } 194 195 // Note when task_running_, then the running routine 196 // (TaskRunner::InternalRunTasks) is responsible for calling 197 // CheckForTimeoutChange. 198 if (!tasks_running_) { 199 CheckForTimeoutChange(previous_timeout_time); 200 } 201 } 202 203 void TaskRunner::RecalcNextTimeout(Task *exclude_task) { 204 // walk through all the tasks looking for the one 205 // which satisfies the following: 206 // it's not finished already 207 // we're not excluding it 208 // it has the closest timeout time 209 210 int64 next_timeout_time = 0; 211 next_timeout_task_ = NULL; 212 213 for (size_t i = 0; i < tasks_.size(); ++i) { 214 Task *task = tasks_[i]; 215 // if the task isn't complete, and it actually has a timeout time 216 if (!task->IsDone() && (task->timeout_time() > 0)) 217 // if it doesn't match our "exclude" task 218 if (exclude_task == NULL || 219 exclude_task->unique_id() != task->unique_id()) 220 // if its timeout time is sooner than our current timeout time 221 if (next_timeout_time == 0 || 222 task->timeout_time() <= next_timeout_time) { 223 // set this task as our next-to-timeout 224 next_timeout_time = task->timeout_time(); 225 next_timeout_task_ = task; 226 } 227 } 228 } 229 230 void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) { 231 int64 next_timeout = next_task_timeout(); 232 bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) || 233 next_timeout < previous_timeout_time || 234 (previous_timeout_time <= CurrentTime() && 235 previous_timeout_time != next_timeout); 236 if (timeout_change) { 237 OnTimeoutChange(); 238 } 239 } 240 241 } // namespace talk_base 242