summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/gtest/TestDelayedRunnable.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/tests/gtest/TestDelayedRunnable.cpp168
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();
+ }
+}