Home | History | Annotate | Download | only in base
      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 
     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:
     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 
     56   virtual int ProcessStart() {
     57     return STATE_RESPONSE;
     58   }
     59 
     60   void OnLocalTimeout() {
     61     SignalTimeoutId(unique_id());
     62   }
     63 
     64  protected:
     65   virtual void Stop() {
     66     SignalDoneId(unique_id());
     67     Task::Stop();
     68   }
     69 };
     70 
     71 class StuckTask : public IdTimeoutTask {
     72  public:
     73   explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
     74   virtual int ProcessStart() {
     75     return STATE_BLOCKED;
     76   }
     77 };
     78 
     79 class HappyTask : public IdTimeoutTask {
     80  public:
     81   explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
     82     time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
     83   }
     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:
    100   virtual void WakeTasks() { RunTasks(); }
    101   virtual int64_t CurrentTime() { return GetCurrentTime(); }
    102 
    103   bool timeout_change() const {
    104     return timeout_change_;
    105   }
    106 
    107   void clear_timeout_change() {
    108     timeout_change_ = false;
    109   }
    110  protected:
    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:
    130   TaskTest() {}
    131 
    132   // no need to delete any tasks; the task runner owns them
    133   ~TaskTest() {}
    134 
    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 
    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 
    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 
    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 
    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 
    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:
    300   explicit AbortTask(TaskParent *parent) : Task(parent) {
    301     set_timeout_seconds(1);
    302   }
    303 
    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:
    314   TaskAbortTest() {}
    315 
    316   // no need to delete any tasks; the task runner owns them
    317   ~TaskAbortTest() {}
    318 
    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:
    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 
    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:
    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 
    354   virtual ~SetBoolOnDeleteTask() {
    355     *set_when_deleted_ = true;
    356   }
    357 
    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:
    369   AbortShouldWakeTest() {}
    370 
    371   // no need to delete any tasks; the task runner owns them
    372   ~AbortShouldWakeTest() {}
    373 
    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:
    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 
    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:
    410   TimeoutChangeTest()
    411     : task_count_(arraysize(stuck_tasks_)) {}
    412 
    413   // no need to delete any tasks; the task runner owns them
    414   ~TimeoutChangeTest() {}
    415 
    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:
    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 
    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:
    488   DeleteTestTaskRunner() {
    489   }
    490   virtual void WakeTasks() { }
    491   virtual int64_t CurrentTime() { return GetCurrentTime(); }
    492  private:
    493   RTC_DISALLOW_COPY_AND_ASSIGN(DeleteTestTaskRunner);
    494 };
    495 
    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 
    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 
    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