blob: 0ee5d023b5b415f821912cf3222588b232541302 [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/task.h"
17#include "webrtc/base/logging.h"
18
19namespace rtc {
20
21TaskRunner::TaskRunner()
nisseede5da42017-01-12 05:15:36 -080022 : TaskParent(this) {}
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000023
24TaskRunner::~TaskRunner() {
25 // this kills and deletes children silently!
26 AbortAllChildren();
27 InternalRunTasks(true);
28}
29
30void 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
40void TaskRunner::RunTasks() {
41 InternalRunTasks(false);
42}
43
44void 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.
nisseede5da42017-01-12 05:15:36 -080049 // Subsequent use of those task may cause data corruption or crashes.
50#if RTC_DCHECK_IS_ON
51 RTC_DCHECK(!abort_count_);
52#endif
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000053 // 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
Peter Boström0c4e06b2015-10-07 12:23:21 +020060 int64_t previous_timeout_time = next_task_timeout();
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000061
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()) {
deadbeef37f5ecf2017-02-27 14:06:41 -080080 next_timeout_task_ = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000081 need_timeout_recalc = true;
82 }
83
nisseede5da42017-01-12 05:15:36 -080084#if RTC_DCHECK_IS_ON
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000085 deleting_task_ = task;
86#endif
87 delete task;
nisseede5da42017-01-12 05:15:36 -080088#if RTC_DCHECK_IS_ON
deadbeef37f5ecf2017-02-27 14:06:41 -080089 deleting_task_ = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000090#endif
deadbeef37f5ecf2017-02-27 14:06:41 -080091 tasks_[i] = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000092 }
93 }
deadbeef37f5ecf2017-02-27 14:06:41 -080094 // Finally, remove nulls.
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000095 std::vector<Task *>::iterator it;
deadbeef37f5ecf2017-02-27 14:06:41 -080096 it = std::remove(tasks_.begin(), tasks_.end(), nullptr);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000097
98 tasks_.erase(it, tasks_.end());
99
100 if (need_timeout_recalc)
deadbeef37f5ecf2017-02-27 14:06:41 -0800101 RecalcNextTimeout(nullptr);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000102
103 // Make sure that adjustments are done to account
104 // for any timeout changes (but don't call this
105 // while being destroyed since it calls a pure virtual function).
106 if (!in_destructor)
107 CheckForTimeoutChange(previous_timeout_time);
108
109 tasks_running_ = false;
110}
111
112void TaskRunner::PollTasks() {
113 // see if our "next potentially timed-out task" has indeed timed out.
114 // If it has, wake it up, then queue up the next task in line
115 // Repeat while we have new timed-out tasks.
116 // TODO: We need to guard against WakeTasks not updating
117 // next_timeout_task_. Maybe also add documentation in the header file once
118 // we understand this code better.
deadbeef37f5ecf2017-02-27 14:06:41 -0800119 Task* old_timeout_task = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000120 while (next_timeout_task_ &&
121 old_timeout_task != next_timeout_task_ &&
122 next_timeout_task_->TimedOut()) {
123 old_timeout_task = next_timeout_task_;
124 next_timeout_task_->Wake();
125 WakeTasks();
126 }
127}
128
Peter Boström0c4e06b2015-10-07 12:23:21 +0200129int64_t TaskRunner::next_task_timeout() const {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000130 if (next_timeout_task_) {
131 return next_timeout_task_->timeout_time();
132 }
133 return 0;
134}
135
136// this function gets called frequently -- when each task changes
137// state to something other than DONE, ERROR or BLOCKED, it calls
138// ResetTimeout(), which will call this function to make sure that
139// the next timeout-able task hasn't changed. The logic in this function
140// prevents RecalcNextTimeout() from getting called in most cases,
141// effectively making the task scheduler O-1 instead of O-N
142
143void TaskRunner::UpdateTaskTimeout(Task* task,
Peter Boström0c4e06b2015-10-07 12:23:21 +0200144 int64_t previous_task_timeout_time) {
deadbeef37f5ecf2017-02-27 14:06:41 -0800145 RTC_DCHECK(task != nullptr);
Peter Boström0c4e06b2015-10-07 12:23:21 +0200146 int64_t previous_timeout_time = next_task_timeout();
deadbeef37f5ecf2017-02-27 14:06:41 -0800147 bool task_is_timeout_task =
148 next_timeout_task_ != nullptr &&
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000149 task->unique_id() == next_timeout_task_->unique_id();
150 if (task_is_timeout_task) {
151 previous_timeout_time = previous_task_timeout_time;
152 }
153
154 // if the relevant task has a timeout, then
155 // check to see if it's closer than the current
156 // "about to timeout" task
157 if (task->timeout_time()) {
deadbeef37f5ecf2017-02-27 14:06:41 -0800158 if (next_timeout_task_ == nullptr ||
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000159 (task->timeout_time() <= next_timeout_task_->timeout_time())) {
160 next_timeout_task_ = task;
161 }
162 } else if (task_is_timeout_task) {
163 // otherwise, if the task doesn't have a timeout,
164 // and it used to be our "about to timeout" task,
165 // walk through all the tasks looking for the real
166 // "about to timeout" task
167 RecalcNextTimeout(task);
168 }
169
170 // Note when task_running_, then the running routine
171 // (TaskRunner::InternalRunTasks) is responsible for calling
172 // CheckForTimeoutChange.
173 if (!tasks_running_) {
174 CheckForTimeoutChange(previous_timeout_time);
175 }
176}
177
178void TaskRunner::RecalcNextTimeout(Task *exclude_task) {
179 // walk through all the tasks looking for the one
180 // which satisfies the following:
181 // it's not finished already
182 // we're not excluding it
183 // it has the closest timeout time
184
Peter Boström0c4e06b2015-10-07 12:23:21 +0200185 int64_t next_timeout_time = 0;
deadbeef37f5ecf2017-02-27 14:06:41 -0800186 next_timeout_task_ = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000187
188 for (size_t i = 0; i < tasks_.size(); ++i) {
189 Task *task = tasks_[i];
190 // if the task isn't complete, and it actually has a timeout time
191 if (!task->IsDone() && (task->timeout_time() > 0))
192 // if it doesn't match our "exclude" task
deadbeef37f5ecf2017-02-27 14:06:41 -0800193 if (exclude_task == nullptr ||
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000194 exclude_task->unique_id() != task->unique_id())
195 // if its timeout time is sooner than our current timeout time
196 if (next_timeout_time == 0 ||
197 task->timeout_time() <= next_timeout_time) {
198 // set this task as our next-to-timeout
199 next_timeout_time = task->timeout_time();
200 next_timeout_task_ = task;
201 }
202 }
203}
204
Peter Boström0c4e06b2015-10-07 12:23:21 +0200205void TaskRunner::CheckForTimeoutChange(int64_t previous_timeout_time) {
206 int64_t next_timeout = next_task_timeout();
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000207 bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) ||
208 next_timeout < previous_timeout_time ||
209 (previous_timeout_time <= CurrentTime() &&
210 previous_timeout_time != next_timeout);
211 if (timeout_change) {
212 OnTimeoutChange();
213 }
214}
215
216} // namespace rtc