/* -*- 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 "mozilla/DelayedRunnable.h" #include "mozilla/Atomics.h" #include "mozilla/RefPtr.h" #include "mozilla/TaskQueue.h" #include "gtest/gtest.h" #include "mozilla/gtest/MozAssertions.h" #include "MediaTimer.h" #include "mozilla/media/MediaUtils.h" #include "VideoUtils.h" using mozilla::Atomic; using mozilla::MakeRefPtr; using mozilla::Monitor; using mozilla::MonitorAutoLock; using mozilla::TaskQueue; namespace { struct ReleaseDetector { explicit ReleaseDetector(Atomic* aActive) : mActive(aActive) { *mActive = true; } ReleaseDetector(ReleaseDetector&& aOther) noexcept : mActive(aOther.mActive) { aOther.mActive = nullptr; } ReleaseDetector(const ReleaseDetector&) = delete; ~ReleaseDetector() { if (mActive) { *mActive = false; } } Atomic* mActive; }; } // namespace TEST(DelayedRunnable, TaskQueueShutdownLeak) { Atomic active{false}; auto taskQueue = TaskQueue::Create( GetMediaThreadPool(mozilla::MediaThreadType::SUPERVISOR), "TestDelayedRunnable TaskQueueShutdownLeak"); taskQueue->DelayedDispatch( NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}), 60e3 /* 1 minute */); EXPECT_TRUE(active); taskQueue->BeginShutdown(); taskQueue->AwaitIdle(); // Leaks are often detected after process shutdown. This doesn't wait that // long, but leaking past thread shutdown would be equally bad since the // runnable can no longer be released on the target thread. This is also the // reason why timers assert that they don't release the last reference to // their callbacks when dispatch fails (like when the target has been // shutdown). EXPECT_FALSE(active); } TEST(DelayedRunnable, nsThreadShutdownLeak) { Atomic active{false}; nsCOMPtr thread; ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK); thread->DelayedDispatch( NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}), 60e3 /* 1 minute */); EXPECT_TRUE(active); ASSERT_EQ(thread->Shutdown(), NS_OK); // Leaks are often detected after process shutdown. This doesn't wait that // long, but leaking past thread shutdown would be equally bad since the // runnable can no longer be released on the target thread. This is also the // reason why timers assert that they don't release the last reference to // their callbacks when dispatch fails (like when the target has been // shutdown). EXPECT_FALSE(active); } /* * This tests a case where we create a background TaskQueue that lives until * xpcom shutdown. This test will fail (by assertion failure) if the TaskQueue * shutdown task is dispatched too late in the shutdown sequence, or: * If the background thread pool is then empty, the TaskQueue shutdown task will * when dispatched require creating a new nsThread, which is forbidden too late * in the shutdown sequence. */ TEST(DelayedRunnable, BackgroundTaskQueueShutdownTask) { nsCOMPtr taskQueue; nsresult rv = NS_CreateBackgroundTaskQueue("TestDelayedRunnable", getter_AddRefs(taskQueue)); ASSERT_NS_SUCCEEDED(rv); // Leak the queue, so it gets cleaned up by xpcom-shutdown. nsISerialEventTarget* tq = taskQueue.forget().take(); mozilla::Unused << tq; } /* * Like BackgroundTaskQueueShutdownTask but for nsThread, since both background * TaskQueues and nsThreads are managed by nsThreadManager. For nsThread things * are different and the shutdown task doesn't use Dispatch, but timings are * similar. */ TEST(DelayedRunnable, nsThreadShutdownTask) { nsCOMPtr thread; ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK); // Leak the thread, so it gets cleaned up by xpcom-shutdown. nsIThread* t = thread.forget().take(); mozilla::Unused << t; } TEST(DelayedRunnable, TimerFiresBeforeRunnableRuns) { RefPtr pool = mozilla::SharedThreadPool::Get("Test Pool"_ns); auto tailTaskQueue1 = TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue1", /* aSupportsTailDispatch = */ true); auto tailTaskQueue2 = TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue2", /* aSupportsTailDispatch = */ true); auto noTailTaskQueue = TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable noTailTaskQueue", /* aSupportsTailDispatch = */ false); enum class State : uint8_t { Start, TimerRan, TasksFinished, } state = State::Start; Monitor monitor MOZ_UNANNOTATED(__func__); MonitorAutoLock lock(monitor); MOZ_ALWAYS_SUCCEEDS( tailTaskQueue1->Dispatch(NS_NewRunnableFunction(__func__, [&] { // This will tail dispatch the delayed runnable, making it prone to // lose a race against the directly-initiated timer firing (and // dispatching another non-tail-dispatched runnable). EXPECT_TRUE(tailTaskQueue1->RequiresTailDispatch(tailTaskQueue2)); tailTaskQueue2->DelayedDispatch( NS_NewRunnableFunction(__func__, [&] {}), 1); MonitorAutoLock lock(monitor); auto timer = MakeRefPtr(); timer->WaitFor(mozilla::TimeDuration::FromMilliseconds(1), __func__) ->Then(noTailTaskQueue, __func__, [&] { MonitorAutoLock lock(monitor); state = State::TimerRan; monitor.NotifyAll(); }); // Wait until the timer has run. It should have dispatched the // TimerEvent to tailTaskQueue2 by then. The tail dispatch happens when // we leave scope. while (state != State::TimerRan) { monitor.Wait(); } // Notify main thread that we've finished the async steps. state = State::TasksFinished; monitor.Notify(); }))); // Wait for async steps before wrapping up the test case. while (state != State::TasksFinished) { monitor.Wait(); } }