• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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