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