From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../doctor/test/gtest/TestMultiWriterQueue.cpp | 382 +++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp (limited to 'dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp') diff --git a/dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp b/dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp new file mode 100644 index 0000000000..35a89c9267 --- /dev/null +++ b/dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp @@ -0,0 +1,382 @@ +/* -*- 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 "MultiWriterQueue.h" + +#include "DDTimeStamp.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Assertions.h" +#include "nsDeque.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include +#include + +using mozilla::MultiWriterQueue; +using mozilla::MultiWriterQueueDefaultBufferSize; +using mozilla::MultiWriterQueueReaderLocking_Mutex; +using mozilla::MultiWriterQueueReaderLocking_None; + +template +static void TestMultiWriterQueueST(const int loops) { + using Q = MultiWriterQueue; + Q q; + + int pushes = 0; + // Go through 2 cycles of pushes&pops, to exercize reusable buffers. + for (int max = loops; max <= loops * 2; max *= 2) { + // Push all numbers. + for (int i = 1; i <= max; ++i) { + bool newBuffer = q.Push(i); + // A new buffer should be added at the last push of each buffer. + EXPECT_EQ(++pushes % BufferSize == 0, newBuffer); + } + + // Pop numbers, should be FIFO. + int x = 0; + q.PopAll([&](int& i) { EXPECT_EQ(++x, i); }); + + // We should have got all numbers. + EXPECT_EQ(max, x); + + // Nothing left. + q.PopAll([&](int&) { EXPECT_TRUE(false); }); + } +} + +TEST(MultiWriterQueue, SingleThreaded) +{ + TestMultiWriterQueueST<1>(10); + TestMultiWriterQueueST<2>(10); + TestMultiWriterQueueST<4>(10); + + TestMultiWriterQueueST<10>(9); + TestMultiWriterQueueST<10>(10); + TestMultiWriterQueueST<10>(11); + TestMultiWriterQueueST<10>(19); + TestMultiWriterQueueST<10>(20); + TestMultiWriterQueueST<10>(21); + TestMultiWriterQueueST<10>(999); + TestMultiWriterQueueST<10>(1000); + TestMultiWriterQueueST<10>(1001); + + TestMultiWriterQueueST<8192>(8192 * 4 + 1); +} + +template +static void TestMultiWriterQueueMT(int aWriterThreads, int aReaderThreads, + int aTotalLoops, const char* aPrintPrefix) { + Q q; + + const int threads = aWriterThreads + aReaderThreads; + const int loops = aTotalLoops / aWriterThreads; + + nsIThread** array = new nsIThread*[threads]; + + mozilla::Atomic pushThreadsCompleted{0}; + int pops = 0; + + nsCOMPtr popper = NS_NewRunnableFunction("MWQPopper", [&]() { + // int popsBefore = pops; + // int allocsBefore = q.AllocatedBuffersStats().mCount; + q.PopAll([&pops](const int& i) { ++pops; }); + // if (pops != popsBefore || + // q.AllocatedBuffersStats().mCount != allocsBefore) { + // printf("%s threads=1+%d loops/thread=%d pops=%d " + // "buffers: live=%d (w %d) reusable=%d (w %d) " + // "alloc=%d (w %d)\n", + // aPrintPrefix, + // aWriterThreads, + // loops, + // pops, + // q.LiveBuffersStats().mCount, + // q.LiveBuffersStats().mWatermark, + // q.ReusableBuffersStats().mCount, + // q.ReusableBuffersStats().mWatermark, + // q.AllocatedBuffersStats().mCount, + // q.AllocatedBuffersStats().mWatermark); + // } + }); + // Cycle through reader threads. + mozilla::Atomic readerThread{0}; + + double start = mozilla::ToSeconds(mozilla::DDNow()); + + for (int k = 0; k < threads; k++) { + // First `aReaderThreads` threads to pop, all others to push. + if (k < aReaderThreads) { + nsCOMPtr t; + nsresult rv = NS_NewNamedThread("MWQThread", getter_AddRefs(t)); + EXPECT_NS_SUCCEEDED(rv); + NS_ADDREF(array[k] = t); + } else { + nsCOMPtr t; + nsCOMPtr r = NS_NewRunnableFunction("MWQPusher", [&, k]() { + // Give a bit of breathing space to construct other threads. + PR_Sleep(PR_MillisecondsToInterval(100)); + + for (int i = 0; i < loops; ++i) { + if (q.Push(k * threads + i) && aReaderThreads != 0) { + // Run a popper task every time we push the last element of a + // buffer. + array[++readerThread % aReaderThreads]->Dispatch( + popper, nsIThread::DISPATCH_NORMAL); + } + } + ++pushThreadsCompleted; + }); + nsresult rv = NS_NewNamedThread("MWQThread", getter_AddRefs(t), r); + EXPECT_NS_SUCCEEDED(rv); + NS_ADDREF(array[k] = t); + } + } + + for (int k = threads - 1; k >= 0; k--) { + array[k]->Shutdown(); + NS_RELEASE(array[k]); + } + delete[] array; + + // There may be a few more elements that haven't been read yet. + q.PopAll([&pops](const int& i) { ++pops; }); + const int pushes = aWriterThreads * loops; + EXPECT_EQ(pushes, pops); + q.PopAll([](const int& i) { EXPECT_TRUE(false); }); + + double duration = mozilla::ToSeconds(mozilla::DDNow()) - start - 0.1; + printf( + "%s threads=%dw+%dr loops/thread=%d pushes=pops=%d duration=%fs " + "pushes/s=%f buffers: live=%d (w %d) reusable=%d (w %d) " + "alloc=%d (w %d)\n", + aPrintPrefix, aWriterThreads, aReaderThreads, loops, pushes, duration, + pushes / duration, q.LiveBuffersStats().mCount, + q.LiveBuffersStats().mWatermark, q.ReusableBuffersStats().mCount, + q.ReusableBuffersStats().mWatermark, q.AllocatedBuffersStats().mCount, + q.AllocatedBuffersStats().mWatermark); +} + +// skip test on windows10-aarch64 due to unexpected test timeout at +// MultiWriterSingleReader, bug 1526001 +#if !defined(_M_ARM64) +TEST(MultiWriterQueue, MultiWriterSingleReader) +{ + // Small BufferSize, to exercize the buffer management code. + TestMultiWriterQueueMT< + MultiWriterQueue>( + 1, 0, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 1, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 2, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 3, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 4, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 5, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 6, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 7, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 8, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 9, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 10, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 16, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 32, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 64, 1, 2 * 1024 * 1024, "MultiWriterQueue"); + + // A more real-life buffer size. + TestMultiWriterQueueMT< + MultiWriterQueue>( + 64, 1, 2 * 1024 * 1024, + "MultiWriterQueue"); + + // DEBUG-mode thread-safety checks should make the following (multi-reader + // with no locking) crash; uncomment to verify. + // TestMultiWriterQueueMT< + // MultiWriterQueue>(64, 2, 2*1024*1024); +} +#endif + +// skip test on windows10-aarch64 due to unexpected test timeout at +// MultiWriterMultiReade, bug 1526001 +#if !defined(_M_ARM64) +TEST(MultiWriterQueue, MultiWriterMultiReader) +{ + static_assert( + std::is_same_v< + MultiWriterQueue, + MultiWriterQueue>, + "MultiWriterQueue reader locking should use Mutex by default"); + + // Small BufferSize, to exercize the buffer management code. + TestMultiWriterQueueMT< + MultiWriterQueue>( + 1, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 2, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 3, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 4, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 5, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 6, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 7, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 8, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 9, 2, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 10, 4, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 16, 8, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 32, 16, 1024 * 1024, "MultiWriterQueue"); + TestMultiWriterQueueMT< + MultiWriterQueue>( + 64, 32, 1024 * 1024, "MultiWriterQueue"); + + // A more real-life buffer size. + TestMultiWriterQueueMT< + MultiWriterQueue>( + 64, 32, 1024 * 1024, + "MultiWriterQueue"); +} +#endif + +// Single-threaded use only. +struct DequeWrapperST { + nsDeque mDQ; + + bool Push(int i) { + mDQ.PushFront(reinterpret_cast(static_cast(i))); + return true; + } + template + void PopAll(F&& aF) { + while (mDQ.GetSize() != 0) { + int i = static_cast(reinterpret_cast(mDQ.Pop())); + aF(i); + } + } + + struct CountAndWatermark { + int mCount = 0; + int mWatermark = 0; + } mLiveBuffersStats, mReusableBuffersStats, mAllocatedBuffersStats; + + CountAndWatermark LiveBuffersStats() const { return mLiveBuffersStats; } + CountAndWatermark ReusableBuffersStats() const { + return mReusableBuffersStats; + } + CountAndWatermark AllocatedBuffersStats() const { + return mAllocatedBuffersStats; + } +}; + +// Multi-thread (atomic) writes allowed, make sure you don't pop unless writes +// can't happen. +struct DequeWrapperAW : DequeWrapperST { + mozilla::Atomic mWriting{false}; + + bool Push(int i) { + while (!mWriting.compareExchange(false, true)) { + } + mDQ.PushFront(reinterpret_cast(static_cast(i))); + mWriting = false; + return true; + } +}; + +// Multi-thread writes allowed, make sure you don't pop unless writes can't +// happen. +struct DequeWrapperMW : DequeWrapperST { + mozilla::Mutex mMutex MOZ_UNANNOTATED; + + DequeWrapperMW() : mMutex("DequeWrapperMW/MT") {} + + bool Push(int i) { + mozilla::MutexAutoLock lock(mMutex); + mDQ.PushFront(reinterpret_cast(static_cast(i))); + return true; + } +}; + +// Multi-thread read&writes allowed. +struct DequeWrapperMT : DequeWrapperMW { + template + void PopAll(F&& aF) { + while (mDQ.GetSize() != 0) { + int i; + { + mozilla::MutexAutoLock lock(mMutex); + i = static_cast(reinterpret_cast(mDQ.Pop())); + } + aF(i); + } + } +}; + +TEST(MultiWriterQueue, nsDequeBenchmark) +{ + TestMultiWriterQueueMT(1, 0, 2 * 1024 * 1024, + "DequeWrapperST "); + + TestMultiWriterQueueMT(1, 0, 2 * 1024 * 1024, + "DequeWrapperAW "); + TestMultiWriterQueueMT(1, 0, 2 * 1024 * 1024, + "DequeWrapperMW "); + TestMultiWriterQueueMT(1, 0, 2 * 1024 * 1024, + "DequeWrapperMT "); + TestMultiWriterQueueMT(1, 1, 2 * 1024 * 1024, + "DequeWrapperMT "); + + TestMultiWriterQueueMT(8, 0, 2 * 1024 * 1024, + "DequeWrapperAW "); + TestMultiWriterQueueMT(8, 0, 2 * 1024 * 1024, + "DequeWrapperMW "); + TestMultiWriterQueueMT(8, 0, 2 * 1024 * 1024, + "DequeWrapperMT "); + TestMultiWriterQueueMT(8, 1, 2 * 1024 * 1024, + "DequeWrapperMT "); +} -- cgit v1.2.3