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