| OLD | NEW |
| (Empty) |
| 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/constructormagic.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 TimeMillis() * 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 | |
| OLD | NEW |