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 #if defined(WEBRTC_POSIX)
12 #include <sys/time.h>
13 #endif  // WEBRTC_POSIX
14 
15 // TODO: Remove this once the cause of sporadic failures in these
16 // tests is tracked down.
17 #include <iostream>
18 
19 #if defined(WEBRTC_WIN)
20 #include "webrtc/base/win32.h"
21 #endif  // WEBRTC_WIN
22 
23 #include "webrtc/base/arraysize.h"
24 #include "webrtc/base/common.h"
25 #include "webrtc/base/gunit.h"
26 #include "webrtc/base/logging.h"
27 #include "webrtc/base/task.h"
28 #include "webrtc/base/taskrunner.h"
29 #include "webrtc/base/thread.h"
30 #include "webrtc/base/timeutils.h"
31 
32 namespace rtc {
33 
GetCurrentTime()34 static int64_t GetCurrentTime() {
35   return static_cast<int64_t>(Time()) * 10000;
36 }
37 
38 // feel free to change these numbers.  Note that '0' won't work, though
39 #define STUCK_TASK_COUNT 5
40 #define HAPPY_TASK_COUNT 20
41 
42 // this is a generic timeout task which, when it signals timeout, will
43 // include the unique ID of the task in the signal (we don't use this
44 // in production code because we haven't yet had occasion to generate
45 // an array of the same types of task)
46 
47 class IdTimeoutTask : public Task, public sigslot::has_slots<> {
48  public:
IdTimeoutTask(TaskParent * parent)49   explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
50     SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
51   }
52 
53   sigslot::signal1<const int> SignalTimeoutId;
54   sigslot::signal1<const int> SignalDoneId;
55 
ProcessStart()56   virtual int ProcessStart() {
57     return STATE_RESPONSE;
58   }
59 
OnLocalTimeout()60   void OnLocalTimeout() {
61     SignalTimeoutId(unique_id());
62   }
63 
64  protected:
Stop()65   virtual void Stop() {
66     SignalDoneId(unique_id());
67     Task::Stop();
68   }
69 };
70 
71 class StuckTask : public IdTimeoutTask {
72  public:
StuckTask(TaskParent * parent)73   explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
ProcessStart()74   virtual int ProcessStart() {
75     return STATE_BLOCKED;
76   }
77 };
78 
79 class HappyTask : public IdTimeoutTask {
80  public:
HappyTask(TaskParent * parent)81   explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
82     time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
83   }
ProcessStart()84   virtual int ProcessStart() {
85     if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
86       return STATE_RESPONSE;
87     else
88       return STATE_BLOCKED;
89   }
90 
91  private:
92   int time_to_perform_;
93 };
94 
95 // simple implementation of a task runner which uses Windows'
96 // GetSystemTimeAsFileTime() to get the current clock ticks
97 
98 class MyTaskRunner : public TaskRunner {
99  public:
WakeTasks()100   virtual void WakeTasks() { RunTasks(); }
CurrentTime()101   virtual int64_t CurrentTime() { return GetCurrentTime(); }
102 
timeout_change() const103   bool timeout_change() const {
104     return timeout_change_;
105   }
106 
clear_timeout_change()107   void clear_timeout_change() {
108     timeout_change_ = false;
109   }
110  protected:
OnTimeoutChange()111   virtual void OnTimeoutChange() {
112     timeout_change_ = true;
113   }
114   bool timeout_change_;
115 };
116 
117 //
118 // this unit test is primarily concerned (for now) with the timeout
119 // functionality in tasks.  It works as follows:
120 //
121 //   * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
122 //     and some "happy" (will immediately finish).
123 //   * Set the timeout on the "stuck" tasks to some number of seconds between
124 //     1 and the number of stuck tasks
125 //   * Start all the stuck & happy tasks in random order
126 //   * Wait "number of stuck tasks" seconds and make sure everything timed out
127 
128 class TaskTest : public sigslot::has_slots<> {
129  public:
TaskTest()130   TaskTest() {}
131 
132   // no need to delete any tasks; the task runner owns them
~TaskTest()133   ~TaskTest() {}
134 
Start()135   void Start() {
136     // create and configure tasks
137     for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
138       stuck_[i].task_ = new StuckTask(&task_runner_);
139       stuck_[i].task_->SignalTimeoutId.connect(this,
140                                                &TaskTest::OnTimeoutStuck);
141       stuck_[i].timed_out_ = false;
142       stuck_[i].xlat_ = stuck_[i].task_->unique_id();
143       stuck_[i].task_->set_timeout_seconds(i + 1);
144       LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
145                    << stuck_[i].task_->timeout_seconds();
146     }
147 
148     for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
149       happy_[i].task_ = new HappyTask(&task_runner_);
150       happy_[i].task_->SignalTimeoutId.connect(this,
151                                                &TaskTest::OnTimeoutHappy);
152       happy_[i].task_->SignalDoneId.connect(this,
153                                             &TaskTest::OnDoneHappy);
154       happy_[i].timed_out_ = false;
155       happy_[i].xlat_ = happy_[i].task_->unique_id();
156     }
157 
158     // start all the tasks in random order
159     int stuck_index = 0;
160     int happy_index = 0;
161     for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
162       if ((stuck_index < STUCK_TASK_COUNT) &&
163           (happy_index < HAPPY_TASK_COUNT)) {
164         if (rand() % 2 == 1) {
165           stuck_[stuck_index++].task_->Start();
166         } else {
167           happy_[happy_index++].task_->Start();
168         }
169       } else if (stuck_index < STUCK_TASK_COUNT) {
170         stuck_[stuck_index++].task_->Start();
171       } else {
172         happy_[happy_index++].task_->Start();
173       }
174     }
175 
176     for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
177       std::cout << "Stuck task #" << i << " timeout is " <<
178           stuck_[i].task_->timeout_seconds() << " at " <<
179           stuck_[i].task_->timeout_time() << std::endl;
180     }
181 
182     // just a little self-check to make sure we started all the tasks
183     ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
184     ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
185 
186     // run the unblocked tasks
187     LOG(LS_INFO) << "Running tasks";
188     task_runner_.RunTasks();
189 
190     std::cout << "Start time is " << GetCurrentTime() << std::endl;
191 
192     // give all the stuck tasks time to timeout
193     for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
194          ++i) {
195       Thread::Current()->ProcessMessages(1000);
196       for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
197         if (happy_[j].task_) {
198           happy_[j].task_->Wake();
199         }
200       }
201       LOG(LS_INFO) << "Polling tasks";
202       task_runner_.PollTasks();
203     }
204 
205     // We see occasional test failures here due to the stuck tasks not having
206     // timed-out yet, which seems like it should be impossible. To help track
207     // this down we have added logging of the timing information, which we send
208     // directly to stdout so that we get it in opt builds too.
209     std::cout << "End time is " << GetCurrentTime() << std::endl;
210   }
211 
OnTimeoutStuck(const int id)212   void OnTimeoutStuck(const int id) {
213     LOG(LS_INFO) << "Timed out task " << id;
214 
215     int i;
216     for (i = 0; i < STUCK_TASK_COUNT; ++i) {
217       if (stuck_[i].xlat_ == id) {
218         stuck_[i].timed_out_ = true;
219         stuck_[i].task_ = NULL;
220         break;
221       }
222     }
223 
224     // getting a bad ID here is a failure, but let's continue
225     // running to see what else might go wrong
226     EXPECT_LT(i, STUCK_TASK_COUNT);
227   }
228 
OnTimeoutHappy(const int id)229   void OnTimeoutHappy(const int id) {
230     int i;
231     for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
232       if (happy_[i].xlat_ == id) {
233         happy_[i].timed_out_ = true;
234         happy_[i].task_ = NULL;
235         break;
236       }
237     }
238 
239     // getting a bad ID here is a failure, but let's continue
240     // running to see what else might go wrong
241     EXPECT_LT(i, HAPPY_TASK_COUNT);
242   }
243 
OnDoneHappy(const int id)244   void OnDoneHappy(const int id) {
245     int i;
246     for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
247       if (happy_[i].xlat_ == id) {
248         happy_[i].task_ = NULL;
249         break;
250       }
251     }
252 
253     // getting a bad ID here is a failure, but let's continue
254     // running to see what else might go wrong
255     EXPECT_LT(i, HAPPY_TASK_COUNT);
256   }
257 
check_passed()258   void check_passed() {
259     EXPECT_TRUE(task_runner_.AllChildrenDone());
260 
261     // make sure none of our happy tasks timed out
262     for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
263       EXPECT_FALSE(happy_[i].timed_out_);
264     }
265 
266     // make sure all of our stuck tasks timed out
267     for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
268       EXPECT_TRUE(stuck_[i].timed_out_);
269       if (!stuck_[i].timed_out_) {
270         std::cout << "Stuck task #" << i << " timeout is at "
271                   << stuck_[i].task_->timeout_time() << std::endl;
272       }
273     }
274 
275     std::cout.flush();
276   }
277 
278  private:
279   struct TaskInfo {
280     IdTimeoutTask *task_;
281     bool timed_out_;
282     int xlat_;
283   };
284 
285   MyTaskRunner task_runner_;
286   TaskInfo stuck_[STUCK_TASK_COUNT];
287   TaskInfo happy_[HAPPY_TASK_COUNT];
288 };
289 
TEST(start_task_test,Timeout)290 TEST(start_task_test, Timeout) {
291   TaskTest task_test;
292   task_test.Start();
293   task_test.check_passed();
294 }
295 
296 // Test for aborting the task while it is running
297 
298 class AbortTask : public Task {
299  public:
AbortTask(TaskParent * parent)300   explicit AbortTask(TaskParent *parent) : Task(parent) {
301     set_timeout_seconds(1);
302   }
303 
ProcessStart()304   virtual int ProcessStart() {
305     Abort();
306     return STATE_NEXT;
307   }
308  private:
309   RTC_DISALLOW_COPY_AND_ASSIGN(AbortTask);
310 };
311 
312 class TaskAbortTest : public sigslot::has_slots<> {
313  public:
TaskAbortTest()314   TaskAbortTest() {}
315 
316   // no need to delete any tasks; the task runner owns them
~TaskAbortTest()317   ~TaskAbortTest() {}
318 
Start()319   void Start() {
320     Task *abort_task = new AbortTask(&task_runner_);
321     abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
322     abort_task->Start();
323 
324     // run the task
325     task_runner_.RunTasks();
326   }
327 
328  private:
OnTimeout()329   void OnTimeout() {
330     FAIL() << "Task timed out instead of aborting.";
331   }
332 
333   MyTaskRunner task_runner_;
334   RTC_DISALLOW_COPY_AND_ASSIGN(TaskAbortTest);
335 };
336 
TEST(start_task_test,Abort)337 TEST(start_task_test, Abort) {
338   TaskAbortTest abort_test;
339   abort_test.Start();
340 }
341 
342 // Test for aborting a task to verify that it does the Wake operation
343 // which gets it deleted.
344 
345 class SetBoolOnDeleteTask : public Task {
346  public:
SetBoolOnDeleteTask(TaskParent * parent,bool * set_when_deleted)347   SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
348     : Task(parent),
349       set_when_deleted_(set_when_deleted) {
350     EXPECT_TRUE(NULL != set_when_deleted);
351     EXPECT_FALSE(*set_when_deleted);
352   }
353 
~SetBoolOnDeleteTask()354   virtual ~SetBoolOnDeleteTask() {
355     *set_when_deleted_ = true;
356   }
357 
ProcessStart()358   virtual int ProcessStart() {
359     return STATE_BLOCKED;
360   }
361 
362  private:
363   bool* set_when_deleted_;
364   RTC_DISALLOW_COPY_AND_ASSIGN(SetBoolOnDeleteTask);
365 };
366 
367 class AbortShouldWakeTest : public sigslot::has_slots<> {
368  public:
AbortShouldWakeTest()369   AbortShouldWakeTest() {}
370 
371   // no need to delete any tasks; the task runner owns them
~AbortShouldWakeTest()372   ~AbortShouldWakeTest() {}
373 
Start()374   void Start() {
375     bool task_deleted = false;
376     Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
377     task_to_abort->Start();
378 
379     // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
380     // TaskRunner::RunTasks() immediately which should delete the task.
381     task_to_abort->Abort();
382     EXPECT_TRUE(task_deleted);
383 
384     if (!task_deleted) {
385       // avoid a crash (due to referencing a local variable)
386       // if the test fails.
387       task_runner_.RunTasks();
388     }
389   }
390 
391  private:
OnTimeout()392   void OnTimeout() {
393     FAIL() << "Task timed out instead of aborting.";
394   }
395 
396   MyTaskRunner task_runner_;
397   RTC_DISALLOW_COPY_AND_ASSIGN(AbortShouldWakeTest);
398 };
399 
TEST(start_task_test,AbortShouldWake)400 TEST(start_task_test, AbortShouldWake) {
401   AbortShouldWakeTest abort_should_wake_test;
402   abort_should_wake_test.Start();
403 }
404 
405 // Validate that TaskRunner's OnTimeoutChange gets called appropriately
406 //  * When a task calls UpdateTaskTimeout
407 //  * When the next timeout task time, times out
408 class TimeoutChangeTest : public sigslot::has_slots<> {
409  public:
TimeoutChangeTest()410   TimeoutChangeTest()
411     : task_count_(arraysize(stuck_tasks_)) {}
412 
413   // no need to delete any tasks; the task runner owns them
~TimeoutChangeTest()414   ~TimeoutChangeTest() {}
415 
Start()416   void Start() {
417     for (int i = 0; i < task_count_; ++i) {
418       stuck_tasks_[i] = new StuckTask(&task_runner_);
419       stuck_tasks_[i]->set_timeout_seconds(i + 2);
420       stuck_tasks_[i]->SignalTimeoutId.connect(this,
421                                                &TimeoutChangeTest::OnTimeoutId);
422     }
423 
424     for (int i = task_count_ - 1; i >= 0; --i) {
425       stuck_tasks_[i]->Start();
426     }
427     task_runner_.clear_timeout_change();
428 
429     // At this point, our timeouts are set as follows
430     // task[0] is 2 seconds, task[1] at 3 seconds, etc.
431 
432     stuck_tasks_[0]->set_timeout_seconds(2);
433     // Now, task[0] is 2 seconds, task[1] at 3 seconds...
434     // so timeout change shouldn't be called.
435     EXPECT_FALSE(task_runner_.timeout_change());
436     task_runner_.clear_timeout_change();
437 
438     stuck_tasks_[0]->set_timeout_seconds(1);
439     // task[0] is 1 seconds, task[1] at 3 seconds...
440     // The smallest timeout got smaller so timeout change be called.
441     EXPECT_TRUE(task_runner_.timeout_change());
442     task_runner_.clear_timeout_change();
443 
444     stuck_tasks_[1]->set_timeout_seconds(2);
445     // task[0] is 1 seconds, task[1] at 2 seconds...
446     // The smallest timeout is still 1 second so no timeout change.
447     EXPECT_FALSE(task_runner_.timeout_change());
448     task_runner_.clear_timeout_change();
449 
450     while (task_count_ > 0) {
451       int previous_count = task_count_;
452       task_runner_.PollTasks();
453       if (previous_count != task_count_) {
454         // We only get here when a task times out.  When that
455         // happens, the timeout change should get called because
456         // the smallest timeout is now in the past.
457         EXPECT_TRUE(task_runner_.timeout_change());
458         task_runner_.clear_timeout_change();
459       }
460       Thread::Current()->socketserver()->Wait(500, false);
461     }
462   }
463 
464  private:
OnTimeoutId(const int id)465   void OnTimeoutId(const int id) {
466     for (size_t i = 0; i < arraysize(stuck_tasks_); ++i) {
467       if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
468         task_count_--;
469         stuck_tasks_[i] = NULL;
470         break;
471       }
472     }
473   }
474 
475   MyTaskRunner task_runner_;
476   StuckTask* (stuck_tasks_[3]);
477   int task_count_;
478   RTC_DISALLOW_COPY_AND_ASSIGN(TimeoutChangeTest);
479 };
480 
TEST(start_task_test,TimeoutChange)481 TEST(start_task_test, TimeoutChange) {
482   TimeoutChangeTest timeout_change_test;
483   timeout_change_test.Start();
484 }
485 
486 class DeleteTestTaskRunner : public TaskRunner {
487  public:
DeleteTestTaskRunner()488   DeleteTestTaskRunner() {
489   }
WakeTasks()490   virtual void WakeTasks() { }
CurrentTime()491   virtual int64_t CurrentTime() { return GetCurrentTime(); }
492  private:
493   RTC_DISALLOW_COPY_AND_ASSIGN(DeleteTestTaskRunner);
494 };
495 
TEST(unstarted_task_test,DeleteTask)496 TEST(unstarted_task_test, DeleteTask) {
497   // This test ensures that we don't
498   // crash if a task is deleted without running it.
499   DeleteTestTaskRunner task_runner;
500   HappyTask* happy_task = new HappyTask(&task_runner);
501   happy_task->Start();
502 
503   // try deleting the task directly
504   HappyTask* child_happy_task = new HappyTask(happy_task);
505   delete child_happy_task;
506 
507   // run the unblocked tasks
508   task_runner.RunTasks();
509 }
510 
TEST(unstarted_task_test,DoNotDeleteTask1)511 TEST(unstarted_task_test, DoNotDeleteTask1) {
512   // This test ensures that we don't
513   // crash if a task runner is deleted without
514   // running a certain task.
515   DeleteTestTaskRunner task_runner;
516   HappyTask* happy_task = new HappyTask(&task_runner);
517   happy_task->Start();
518 
519   HappyTask* child_happy_task = new HappyTask(happy_task);
520   child_happy_task->Start();
521 
522   // Never run the tasks
523 }
524 
TEST(unstarted_task_test,DoNotDeleteTask2)525 TEST(unstarted_task_test, DoNotDeleteTask2) {
526   // This test ensures that we don't
527   // crash if a taskrunner is delete with a
528   // task that has never been started.
529   DeleteTestTaskRunner task_runner;
530   HappyTask* happy_task = new HappyTask(&task_runner);
531   happy_task->Start();
532 
533   // Do not start the task.
534   // Note: this leaks memory, so don't do this.
535   // Instead, always run your tasks or delete them.
536   new HappyTask(happy_task);
537 
538   // run the unblocked tasks
539   task_runner.RunTasks();
540 }
541 
542 }  // namespace rtc
543