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