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 "talk/base/task.h"
29#include "talk/base/common.h"
30#include "talk/base/taskrunner.h"
31
32namespace talk_base {
33
34int32 Task::unique_id_seed_ = 0;
35
36Task::Task(TaskParent *parent)
37    : TaskParent(this, parent),
38      state_(STATE_INIT),
39      blocked_(false),
40      done_(false),
41      aborted_(false),
42      busy_(false),
43      error_(false),
44      start_time_(0),
45      timeout_time_(0),
46      timeout_seconds_(0),
47      timeout_suspended_(false)  {
48  unique_id_ = unique_id_seed_++;
49
50  // sanity check that we didn't roll-over our id seed
51  ASSERT(unique_id_ < unique_id_seed_);
52}
53
54Task::~Task() {
55  // Is this task being deleted in the correct manner?
56  ASSERT(!done_ || GetRunner()->is_ok_to_delete(this));
57  ASSERT(state_ == STATE_INIT || done_);
58  ASSERT(state_ == STATE_INIT || blocked_);
59
60  // If the task is being deleted without being done, it
61  // means that it hasn't been removed from its parent.
62  // This happens if a task is deleted outside of TaskRunner.
63  if (!done_) {
64    Stop();
65  }
66}
67
68int64 Task::CurrentTime() {
69  return GetRunner()->CurrentTime();
70}
71
72int64 Task::ElapsedTime() {
73  return CurrentTime() - start_time_;
74}
75
76void Task::Start() {
77  if (state_ != STATE_INIT)
78    return;
79  // Set the start time before starting the task.  Otherwise if the task
80  // finishes quickly and deletes the Task object, setting start_time_
81  // will crash.
82  start_time_ = CurrentTime();
83  GetRunner()->StartTask(this);
84}
85
86void Task::Step() {
87  if (done_) {
88#ifdef _DEBUG
89    // we do not know how !blocked_ happens when done_ - should be impossible.
90    // But it causes problems, so in retail build, we force blocked_, and
91    // under debug we assert.
92    ASSERT(blocked_);
93#else
94    blocked_ = true;
95#endif
96    return;
97  }
98
99  // Async Error() was called
100  if (error_) {
101    done_ = true;
102    state_ = STATE_ERROR;
103    blocked_ = true;
104//   obsolete - an errored task is not considered done now
105//   SignalDone();
106
107    Stop();
108#ifdef _DEBUG
109    // verify that stop removed this from its parent
110    ASSERT(!parent()->IsChildTask(this));
111#endif
112    return;
113  }
114
115  busy_ = true;
116  int new_state = Process(state_);
117  busy_ = false;
118
119  if (aborted_) {
120    Abort(true);  // no need to wake because we're awake
121    return;
122  }
123
124  if (new_state == STATE_BLOCKED) {
125    blocked_ = true;
126    // Let the timeout continue
127  } else {
128    state_ = new_state;
129    blocked_ = false;
130    ResetTimeout();
131  }
132
133  if (new_state == STATE_DONE) {
134    done_ = true;
135  } else if (new_state == STATE_ERROR) {
136    done_ = true;
137    error_ = true;
138  }
139
140  if (done_) {
141//  obsolete - call this yourself
142//    SignalDone();
143
144    Stop();
145#if _DEBUG
146    // verify that stop removed this from its parent
147    ASSERT(!parent()->IsChildTask(this));
148#endif
149    blocked_ = true;
150  }
151}
152
153void Task::Abort(bool nowake) {
154  // Why only check for done_ (instead of "aborted_ || done_")?
155  //
156  // If aborted_ && !done_, it means the logic for aborting still
157  // needs to be executed (because busy_ must have been true when
158  // Abort() was previously called).
159  if (done_)
160    return;
161  aborted_ = true;
162  if (!busy_) {
163    done_ = true;
164    blocked_ = true;
165    error_ = true;
166
167    // "done_" is set before calling "Stop()" to ensure that this code
168    // doesn't execute more than once (recursively) for the same task.
169    Stop();
170#ifdef _DEBUG
171    // verify that stop removed this from its parent
172    ASSERT(!parent()->IsChildTask(this));
173#endif
174    if (!nowake) {
175      // WakeTasks to self-delete.
176      // Don't call Wake() because it is a no-op after "done_" is set.
177      // Even if Wake() did run, it clears "blocked_" which isn't desireable.
178      GetRunner()->WakeTasks();
179    }
180  }
181}
182
183void Task::Wake() {
184  if (done_)
185    return;
186  if (blocked_) {
187    blocked_ = false;
188    GetRunner()->WakeTasks();
189  }
190}
191
192void Task::Error() {
193  if (error_ || done_)
194    return;
195  error_ = true;
196  Wake();
197}
198
199std::string Task::GetStateName(int state) const {
200  switch (state) {
201    case STATE_BLOCKED: return "BLOCKED";
202    case STATE_INIT: return "INIT";
203    case STATE_START: return "START";
204    case STATE_DONE: return "DONE";
205    case STATE_ERROR: return "ERROR";
206    case STATE_RESPONSE: return "RESPONSE";
207  }
208  return "??";
209}
210
211int Task::Process(int state) {
212  int newstate = STATE_ERROR;
213
214  if (TimedOut()) {
215    ClearTimeout();
216    newstate = OnTimeout();
217    SignalTimeout();
218  } else {
219    switch (state) {
220      case STATE_INIT:
221        newstate = STATE_START;
222        break;
223      case STATE_START:
224        newstate = ProcessStart();
225        break;
226      case STATE_RESPONSE:
227        newstate = ProcessResponse();
228        break;
229      case STATE_DONE:
230      case STATE_ERROR:
231        newstate = STATE_BLOCKED;
232        break;
233    }
234  }
235
236  return newstate;
237}
238
239void Task::Stop() {
240  // No need to wake because we're either awake or in abort
241  TaskParent::OnStopped(this);
242}
243
244void Task::set_timeout_seconds(const int timeout_seconds) {
245  timeout_seconds_ = timeout_seconds;
246  ResetTimeout();
247}
248
249bool Task::TimedOut() {
250  return timeout_seconds_ &&
251    timeout_time_ &&
252    CurrentTime() >= timeout_time_;
253}
254
255void Task::ResetTimeout() {
256  int64 previous_timeout_time = timeout_time_;
257  bool timeout_allowed = (state_ != STATE_INIT)
258                      && (state_ != STATE_DONE)
259                      && (state_ != STATE_ERROR);
260  if (timeout_seconds_ && timeout_allowed && !timeout_suspended_)
261    timeout_time_ = CurrentTime() +
262                    (timeout_seconds_ * kSecToMsec * kMsecTo100ns);
263  else
264    timeout_time_ = 0;
265
266  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
267}
268
269void Task::ClearTimeout() {
270  int64 previous_timeout_time = timeout_time_;
271  timeout_time_ = 0;
272  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
273}
274
275void Task::SuspendTimeout() {
276  if (!timeout_suspended_) {
277    timeout_suspended_ = true;
278    ResetTimeout();
279  }
280}
281
282void Task::ResumeTimeout() {
283  if (timeout_suspended_) {
284    timeout_suspended_ = false;
285    ResetTimeout();
286  }
287}
288
289} // namespace talk_base
290