diff options
Diffstat (limited to 'xpcom/tests/gtest/TestDelayedRunnable.cpp')
-rw-r--r-- | xpcom/tests/gtest/TestDelayedRunnable.cpp | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestDelayedRunnable.cpp b/xpcom/tests/gtest/TestDelayedRunnable.cpp new file mode 100644 index 0000000000..b612522b55 --- /dev/null +++ b/xpcom/tests/gtest/TestDelayedRunnable.cpp @@ -0,0 +1,168 @@ +/* -*- 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<bool>* 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<bool>* mActive; +}; +} // namespace + +TEST(DelayedRunnable, TaskQueueShutdownLeak) +{ + Atomic<bool> 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<bool> active{false}; + nsCOMPtr<nsIThread> 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<nsISerialEventTarget> 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<nsIThread> 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<mozilla::SharedThreadPool> 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<mozilla::MediaTimer>(); + 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(); + } +} |