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