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  static const std::string STR_BLOCKED("BLOCKED");
201  static const std::string STR_INIT("INIT");
202  static const std::string STR_START("START");
203  static const std::string STR_DONE("DONE");
204  static const std::string STR_ERROR("ERROR");
205  static const std::string STR_RESPONSE("RESPONSE");
206  static const std::string STR_HUH("??");
207  switch (state) {
208    case STATE_BLOCKED: return STR_BLOCKED;
209    case STATE_INIT: return STR_INIT;
210    case STATE_START: return STR_START;
211    case STATE_DONE: return STR_DONE;
212    case STATE_ERROR: return STR_ERROR;
213    case STATE_RESPONSE: return STR_RESPONSE;
214  }
215  return STR_HUH;
216}
217
218int Task::Process(int state) {
219  int newstate = STATE_ERROR;
220
221  if (TimedOut()) {
222    ClearTimeout();
223    newstate = OnTimeout();
224    SignalTimeout();
225  } else {
226    switch (state) {
227      case STATE_INIT:
228        newstate = STATE_START;
229        break;
230      case STATE_START:
231        newstate = ProcessStart();
232        break;
233      case STATE_RESPONSE:
234        newstate = ProcessResponse();
235        break;
236      case STATE_DONE:
237      case STATE_ERROR:
238        newstate = STATE_BLOCKED;
239        break;
240    }
241  }
242
243  return newstate;
244}
245
246void Task::Stop() {
247  // No need to wake because we're either awake or in abort
248  TaskParent::OnStopped(this);
249}
250
251void Task::set_timeout_seconds(const int timeout_seconds) {
252  timeout_seconds_ = timeout_seconds;
253  ResetTimeout();
254}
255
256bool Task::TimedOut() {
257  return timeout_seconds_ &&
258    timeout_time_ &&
259    CurrentTime() >= timeout_time_;
260}
261
262void Task::ResetTimeout() {
263  int64 previous_timeout_time = timeout_time_;
264  bool timeout_allowed = (state_ != STATE_INIT)
265                      && (state_ != STATE_DONE)
266                      && (state_ != STATE_ERROR);
267  if (timeout_seconds_ && timeout_allowed && !timeout_suspended_)
268    timeout_time_ = CurrentTime() +
269                    (timeout_seconds_ * kSecToMsec * kMsecTo100ns);
270  else
271    timeout_time_ = 0;
272
273  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
274}
275
276void Task::ClearTimeout() {
277  int64 previous_timeout_time = timeout_time_;
278  timeout_time_ = 0;
279  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
280}
281
282void Task::SuspendTimeout() {
283  if (!timeout_suspended_) {
284    timeout_suspended_ = true;
285    ResetTimeout();
286  }
287}
288
289void Task::ResumeTimeout() {
290  if (timeout_suspended_) {
291    timeout_suspended_ = false;
292    ResetTimeout();
293  }
294}
295
296} // namespace talk_base
297