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_t 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_t Task::CurrentTime() {
52  return GetRunner()->CurrentTime();
53}
54
55int64_t 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#if !defined(NDEBUG)
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#if !defined(NDEBUG)
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 !defined(NDEBUG)
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#if !defined(NDEBUG)
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
227int Task::ProcessResponse() {
228  return STATE_DONE;
229}
230
231void Task::set_timeout_seconds(const int timeout_seconds) {
232  timeout_seconds_ = timeout_seconds;
233  ResetTimeout();
234}
235
236bool Task::TimedOut() {
237  return timeout_seconds_ &&
238    timeout_time_ &&
239    CurrentTime() >= timeout_time_;
240}
241
242void Task::ResetTimeout() {
243  int64_t previous_timeout_time = timeout_time_;
244  bool timeout_allowed = (state_ != STATE_INIT)
245                      && (state_ != STATE_DONE)
246                      && (state_ != STATE_ERROR);
247  if (timeout_seconds_ && timeout_allowed && !timeout_suspended_)
248    timeout_time_ = CurrentTime() +
249                    (timeout_seconds_ * kSecToMsec * kMsecTo100ns);
250  else
251    timeout_time_ = 0;
252
253  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
254}
255
256void Task::ClearTimeout() {
257  int64_t previous_timeout_time = timeout_time_;
258  timeout_time_ = 0;
259  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
260}
261
262void Task::SuspendTimeout() {
263  if (!timeout_suspended_) {
264    timeout_suspended_ = true;
265    ResetTimeout();
266  }
267}
268
269void Task::ResumeTimeout() {
270  if (timeout_suspended_) {
271    timeout_suspended_ = false;
272    ResetTimeout();
273  }
274}
275
276int Task::OnTimeout() {
277  // by default, we are finished after timing out
278  return STATE_DONE;
279}
280
281} // namespace rtc
282