- add third_party src.
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / base / taskrunner.cc
1 /*
2  * libjingle
3  * Copyright 2004--2006, Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without 
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice, 
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products 
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <algorithm>
29
30 #include "talk/base/taskrunner.h"
31
32 #include "talk/base/common.h"
33 #include "talk/base/scoped_ptr.h"
34 #include "talk/base/task.h"
35 #include "talk/base/logging.h"
36
37 namespace talk_base {
38
39 TaskRunner::TaskRunner()
40   : TaskParent(this),
41     next_timeout_task_(NULL),
42     tasks_running_(false)
43 #ifdef _DEBUG
44     , abort_count_(0),
45     deleting_task_(NULL)
46 #endif
47 {
48 }
49
50 TaskRunner::~TaskRunner() {
51   // this kills and deletes children silently!
52   AbortAllChildren();
53   InternalRunTasks(true);
54 }
55
56 void TaskRunner::StartTask(Task * task) {
57   tasks_.push_back(task);
58
59   // the task we just started could be about to timeout --
60   // make sure our "next timeout task" is correct
61   UpdateTaskTimeout(task, 0);
62
63   WakeTasks();
64 }
65
66 void TaskRunner::RunTasks() {
67   InternalRunTasks(false);
68 }
69
70 void TaskRunner::InternalRunTasks(bool in_destructor) {
71   // This shouldn't run while an abort is happening.
72   // If that occurs, then tasks may be deleted in this method,
73   // but pointers to them will still be in the
74   // "ChildSet copy" in TaskParent::AbortAllChildren.
75   // Subsequent use of those task may cause data corruption or crashes.  
76   ASSERT(!abort_count_);
77   // Running continues until all tasks are Blocked (ok for a small # of tasks)
78   if (tasks_running_) {
79     return;  // don't reenter
80   }
81
82   tasks_running_ = true;
83
84   int64 previous_timeout_time = next_task_timeout();
85
86   int did_run = true;
87   while (did_run) {
88     did_run = false;
89     // use indexing instead of iterators because tasks_ may grow
90     for (size_t i = 0; i < tasks_.size(); ++i) {
91       while (!tasks_[i]->Blocked()) {
92         tasks_[i]->Step();
93         did_run = true;
94       }
95     }
96   }
97   // Tasks are deleted when running has paused
98   bool need_timeout_recalc = false;
99   for (size_t i = 0; i < tasks_.size(); ++i) {
100     if (tasks_[i]->IsDone()) {
101       Task* task = tasks_[i];
102       if (next_timeout_task_ &&
103           task->unique_id() == next_timeout_task_->unique_id()) {
104         next_timeout_task_ = NULL;
105         need_timeout_recalc = true;
106       }
107
108 #ifdef _DEBUG
109       deleting_task_ = task;
110 #endif
111       delete task;
112 #ifdef _DEBUG
113       deleting_task_ = NULL;
114 #endif
115       tasks_[i] = NULL;
116     }
117   }
118   // Finally, remove nulls
119   std::vector<Task *>::iterator it;
120   it = std::remove(tasks_.begin(),
121                    tasks_.end(),
122                    reinterpret_cast<Task *>(NULL));
123
124   tasks_.erase(it, tasks_.end());
125
126   if (need_timeout_recalc)
127     RecalcNextTimeout(NULL);
128
129   // Make sure that adjustments are done to account
130   // for any timeout changes (but don't call this
131   // while being destroyed since it calls a pure virtual function).
132   if (!in_destructor)
133     CheckForTimeoutChange(previous_timeout_time);
134
135   tasks_running_ = false;
136 }
137
138 void TaskRunner::PollTasks() {
139   // see if our "next potentially timed-out task" has indeed timed out.
140   // If it has, wake it up, then queue up the next task in line
141   // Repeat while we have new timed-out tasks.
142   // TODO: We need to guard against WakeTasks not updating
143   // next_timeout_task_. Maybe also add documentation in the header file once
144   // we understand this code better.
145   Task* old_timeout_task = NULL;
146   while (next_timeout_task_ &&
147       old_timeout_task != next_timeout_task_ &&
148       next_timeout_task_->TimedOut()) {
149     old_timeout_task = next_timeout_task_;
150     next_timeout_task_->Wake();
151     WakeTasks();
152   }
153 }
154
155 int64 TaskRunner::next_task_timeout() const {
156   if (next_timeout_task_) {
157     return next_timeout_task_->timeout_time();
158   }
159   return 0;
160 }
161
162 // this function gets called frequently -- when each task changes
163 // state to something other than DONE, ERROR or BLOCKED, it calls
164 // ResetTimeout(), which will call this function to make sure that
165 // the next timeout-able task hasn't changed.  The logic in this function
166 // prevents RecalcNextTimeout() from getting called in most cases,
167 // effectively making the task scheduler O-1 instead of O-N
168
169 void TaskRunner::UpdateTaskTimeout(Task* task,
170                                    int64 previous_task_timeout_time) {
171   ASSERT(task != NULL);
172   int64 previous_timeout_time = next_task_timeout();
173   bool task_is_timeout_task = next_timeout_task_ != NULL &&
174       task->unique_id() == next_timeout_task_->unique_id();
175   if (task_is_timeout_task) {
176     previous_timeout_time = previous_task_timeout_time;
177   }
178
179   // if the relevant task has a timeout, then
180   // check to see if it's closer than the current
181   // "about to timeout" task
182   if (task->timeout_time()) {
183     if (next_timeout_task_ == NULL ||
184         (task->timeout_time() <= next_timeout_task_->timeout_time())) {
185       next_timeout_task_ = task;
186     }
187   } else if (task_is_timeout_task) {
188     // otherwise, if the task doesn't have a timeout,
189     // and it used to be our "about to timeout" task,
190     // walk through all the tasks looking for the real
191     // "about to timeout" task
192     RecalcNextTimeout(task);
193   }
194
195   // Note when task_running_, then the running routine
196   // (TaskRunner::InternalRunTasks) is responsible for calling
197   // CheckForTimeoutChange.
198   if (!tasks_running_) {
199     CheckForTimeoutChange(previous_timeout_time);
200   }
201 }
202
203 void TaskRunner::RecalcNextTimeout(Task *exclude_task) {
204   // walk through all the tasks looking for the one
205   // which satisfies the following:
206   //   it's not finished already
207   //   we're not excluding it
208   //   it has the closest timeout time
209
210   int64 next_timeout_time = 0;
211   next_timeout_task_ = NULL;
212
213   for (size_t i = 0; i < tasks_.size(); ++i) {
214     Task *task = tasks_[i];
215     // if the task isn't complete, and it actually has a timeout time
216     if (!task->IsDone() && (task->timeout_time() > 0))
217       // if it doesn't match our "exclude" task
218       if (exclude_task == NULL ||
219           exclude_task->unique_id() != task->unique_id())
220         // if its timeout time is sooner than our current timeout time
221         if (next_timeout_time == 0 ||
222             task->timeout_time() <= next_timeout_time) {
223           // set this task as our next-to-timeout
224           next_timeout_time = task->timeout_time();
225           next_timeout_task_ = task;
226         }
227   }
228 }
229
230 void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) {
231   int64 next_timeout = next_task_timeout();
232   bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) ||
233       next_timeout < previous_timeout_time ||
234       (previous_timeout_time <= CurrentTime() &&
235        previous_timeout_time != next_timeout);
236   if (timeout_change) {
237     OnTimeoutChange();
238   }
239 }
240
241 } // namespace talk_base