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
TaskRunner()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
~TaskRunner()33 TaskRunner::~TaskRunner() {
34 // this kills and deletes children silently!
35 AbortAllChildren();
36 InternalRunTasks(true);
37 }
38
StartTask(Task * task)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
RunTasks()49 void TaskRunner::RunTasks() {
50 InternalRunTasks(false);
51 }
52
InternalRunTasks(bool in_destructor)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
PollTasks()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
next_task_timeout() const138 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
UpdateTaskTimeout(Task * task,int64_t previous_task_timeout_time)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
RecalcNextTimeout(Task * exclude_task)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
CheckForTimeoutChange(int64_t previous_timeout_time)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