/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gtest/gtest.h" #include // uint32_t #include "nsString.h" // nsACString #include "nsThreadUtils.h" // NS_ProcessNextEvent #include "mozilla/Atomics.h" // Atomic #include "mozilla/EventQueue.h" // EventQueuePriority #include "mozilla/Mutex.h" // Mutex, MutexAutoLock #include "mozilla/RefPtr.h" // RefPtr, do_AddRef #include "mozilla/TaskController.h" // TaskController, Task #include "prthread.h" // PR_Sleep using namespace mozilla; namespace TestTaskController { class Logger { public: Logger() : mMutex("Logger") {} void Add(const char* aText) { MutexAutoLock lock(mMutex); mLog += aText; } const nsAutoCString& GetLog() const { return mLog; } private: nsAutoCString mLog; Mutex mMutex; }; class ReschedulingTask : public Task { static constexpr uint32_t LoopCount = 3; public: explicit ReschedulingTask(Kind aKind, Logger* aLogger, const char* aName) : Task(aKind, EventQueuePriority::Normal), mCount(0), mIsDone(false), mLogger(aLogger), mName(aName) {} TaskResult Run() override { mLogger->Add(mName); mCount++; if (mCount < LoopCount) { return TaskResult::Incomplete; } mIsDone = true; return TaskResult::Complete; } #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY bool GetName(nsACString& aName) override { aName.AssignLiteral("AsyncScriptCompileTask"); return true; } #endif bool IsDone() const { return mIsDone; } private: Atomic mCount; Atomic mIsDone; Logger* mLogger; const char* mName; }; using namespace mozilla; TEST(TaskController, RescheduleOnMainThread) { Logger logger; RefPtr mainThreadTask = new ReschedulingTask(Task::Kind::MainThreadOnly, &logger, "1"); TaskController::Get()->AddTask(do_AddRef(mainThreadTask)); while (NS_ProcessNextEvent(nullptr, false)) { } ASSERT_TRUE(mainThreadTask->IsDone()); ASSERT_TRUE(logger.GetLog() == "111"); } TEST(TaskController, RescheduleOffMainThread) { Logger logger; RefPtr offThreadTask = new ReschedulingTask(Task::Kind::OffMainThreadOnly, &logger, "1"); TaskController::Get()->AddTask(do_AddRef(offThreadTask)); uint32_t count = 0; while (!offThreadTask->IsDone() && count < 100) { PR_Sleep(PR_MillisecondsToInterval(100)); count++; } ASSERT_TRUE(offThreadTask->IsDone()); ASSERT_TRUE(logger.GetLog() == "111"); } TEST(TaskController, RescheduleMainAndOffMainThreads) { Logger logger; RefPtr offThreadTask = new ReschedulingTask(Task::Kind::OffMainThreadOnly, &logger, "1"); RefPtr mainThreadTask = new ReschedulingTask(Task::Kind::MainThreadOnly, &logger, "2"); mainThreadTask->AddDependency(offThreadTask.get()); TaskController::Get()->AddTask(do_AddRef(offThreadTask)); TaskController::Get()->AddTask(do_AddRef(mainThreadTask)); uint32_t count = 0; while (!offThreadTask->IsDone() && count < 100) { PR_Sleep(PR_MillisecondsToInterval(100)); count++; } ASSERT_TRUE(offThreadTask->IsDone()); // At this point, the main thread task shouldn't have run. ASSERT_TRUE(logger.GetLog() == "111"); while (NS_ProcessNextEvent(nullptr, false)) { } ASSERT_TRUE(mainThreadTask->IsDone()); ASSERT_TRUE(logger.GetLog() == "111222"); } TEST(TaskController, RescheduleOrder) { Logger logger; RefPtr mainThreadTask1 = new ReschedulingTask(Task::Kind::MainThreadOnly, &logger, "1"); RefPtr mainThreadTask2 = new ReschedulingTask(Task::Kind::MainThreadOnly, &logger, "2"); RefPtr mainThreadTask3 = new ReschedulingTask(Task::Kind::MainThreadOnly, &logger, "3"); TaskController::Get()->AddTask(do_AddRef(mainThreadTask1)); TaskController::Get()->AddTask(do_AddRef(mainThreadTask2)); TaskController::Get()->AddTask(do_AddRef(mainThreadTask3)); while (NS_ProcessNextEvent(nullptr, false)) { } ASSERT_TRUE(mainThreadTask1->IsDone()); ASSERT_TRUE(mainThreadTask2->IsDone()); ASSERT_TRUE(mainThreadTask3->IsDone()); // Rescheduled tasks should be added to the beginning of the queue. ASSERT_TRUE(logger.GetLog() == "111222333"); } TEST(TaskController, RescheduleOrderOffMainThread) { Logger logger1; Logger logger2; Logger logger3; RefPtr offThreadTask1 = new ReschedulingTask(Task::Kind::OffMainThreadOnly, &logger1, "1"); RefPtr offThreadTask2 = new ReschedulingTask(Task::Kind::OffMainThreadOnly, &logger2, "2"); RefPtr offThreadTask3 = new ReschedulingTask(Task::Kind::OffMainThreadOnly, &logger3, "3"); TaskController::Get()->AddTask(do_AddRef(offThreadTask1)); TaskController::Get()->AddTask(do_AddRef(offThreadTask2)); TaskController::Get()->AddTask(do_AddRef(offThreadTask3)); uint32_t count = 0; while (!(offThreadTask1->IsDone() && offThreadTask2->IsDone() && offThreadTask3->IsDone()) && count < 100) { PR_Sleep(PR_MillisecondsToInterval(100)); count++; } ASSERT_TRUE(offThreadTask1->IsDone()); ASSERT_TRUE(offThreadTask2->IsDone()); ASSERT_TRUE(offThreadTask3->IsDone()); // Rescheduled tasks should be enqueued. // The order between off-thread tasks are not deterministic. ASSERT_TRUE(logger1.GetLog() == "111"); ASSERT_TRUE(logger2.GetLog() == "222"); ASSERT_TRUE(logger3.GetLog() == "333"); } } // namespace TestTaskController