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