Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  TestUnboundedMPSCQueue.cpp

  Sprache: C
 

/* 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 <algorithm>
#include <cstdint>
#include <string>
#include <vector>

#include "UnboundedMPSCQueue.h"
#include "mozilla/Atomics.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/UniquePtr.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"

namespace mozilla::test_mpsc_queue {

struct MoveOnlyValue {
  explicit MoveOnlyValue(int32_t aValue = 0) : mValue(aValue) {}

  MoveOnlyValue(const MoveOnlyValue&) = delete;

  MoveOnlyValue& operator=(const MoveOnlyValue&) = delete;

  MoveOnlyValue(MoveOnlyValue&& aOther) : mValue(aOther.mValue) {
    aOther.mValue = -1;
  }

  MoveOnlyValue& operator=(MoveOnlyValue&& aOther) {
    mValue = aOther.mValue;
    aOther.mValue = -1;

    return *this;
  }

  int32_t mValue;
};

struct CountedType {
  explicit CountedType(int32_t aValue = 0) : mValue(aValue) { ++sLiveCount; }

  CountedType(const CountedType& aOther) : mValue(aOther.mValue) {
    ++sLiveCount;
  }

  CountedType& operator=(const CountedType& aOther) = default;

  CountedType(CountedType&& aOther) : mValue(aOther.mValue) {
    aOther.mValue = -1;
    ++sLiveCount;
  }

  CountedType& operator=(CountedType&& aOther) {
    mValue = aOther.mValue;
    aOther.mValue = -1;
    return *this;
  }

  ~CountedType() { --sLiveCount; }

  static Atomic<int32_t> sLiveCount;
  int32_t mValue;
};

Atomic<int32_t> CountedType::sLiveCount(0);

template <typename T>
void PushValue(UnboundedMPSCQueue<T>& aQueue, T aValue) {
  auto* msg = new typename UnboundedMPSCQueue<T>::Message();
  msg->data = std::move(aValue);
  aQueue.Push(msg);
}

// --- Single-threaded basics ---

TEST(UnboundedMPSCQueue, PopEmptyReturnsFalse)
{
  UnboundedMPSCQueue<int32_t> queue;
  int32_t output = 42;
  EXPECT_FALSE(queue.Pop(&output));
  EXPECT_EQ(output, 42);
}

TEST(UnboundedMPSCQueue, SinglePushPop)
{
  UnboundedMPSCQueue<int32_t> queue;
  PushValue(queue, 42);

  int32_t output = 0;
  EXPECT_TRUE(queue.Pop(&output));
  EXPECT_EQ(output, 42);
  EXPECT_FALSE(queue.Pop(&output));
}

TEST(UnboundedMPSCQueue, FIFOOrdering)
{
  UnboundedMPSCQueue<int32_t> queue;
  for (int32_t i = 0; i < 100; i++) {
    PushValue(queue, i);
  }

  for (int32_t i = 0; i < 100; i++) {
    int32_t output = -1;
    EXPECT_TRUE(queue.Pop(&output));
    EXPECT_EQ(output, i);
  }

  int32_t output = -1;
  EXPECT_FALSE(queue.Pop(&output));
}

TEST(UnboundedMPSCQueue, ManyElements)
{
  UnboundedMPSCQueue<int32_t> queue;
  const int32_t count = 10000;
  for (int32_t i = 0; i < count; i++) {
    PushValue(queue, i);
  }

  for (int32_t i = 0; i < count; i++) {
    int32_t output = -1;
    EXPECT_TRUE(queue.Pop(&output));
    EXPECT_EQ(output, i);
  }

  int32_t output = -1;
  EXPECT_FALSE(queue.Pop(&output));
}

// Push/pop alternate one-at-a-time (not batched), catching bugs in node
// recycling or state transitions between empty and non-empty.
TEST(UnboundedMPSCQueue, InterleavedPushPop)
{
  UnboundedMPSCQueue<int32_t> queue;
  // Push one, pop one, 50 times.
  for (int32_t i = 0; i < 50; i++) {
    PushValue(queue, i);
    int32_t output = -1;
    EXPECT_TRUE(queue.Pop(&output));
    EXPECT_EQ(output, i);
  }

  // Queue must be empty.
  int32_t output = -1;
  EXPECT_FALSE(queue.Pop(&output));
}

// New pushes after a partial drain are appended correctly and don't corrupt
// remaining unconsumed items -- catches stale-tail or linkage bugs.
TEST(UnboundedMPSCQueue, PopAfterPartialDrain)
{
  UnboundedMPSCQueue<int32_t> queue;
  // Enqueue 5 items.
  for (int32_t i = 0; i < 5; i++) {
    PushValue(queue, i);
  }

  // Drain only the first 3.
  for (int32_t i = 0; i < 3; i++) {
    int32_t output = -1;
    EXPECT_TRUE(queue.Pop(&output));
    EXPECT_EQ(output, i);
  }

  // Enqueue 3 more while 2 remain.
  for (int32_t i = 5; i < 8; i++) {
    PushValue(queue, i);
  }

  // Remaining + new items must appear in FIFO order.
  for (const auto& expected : {34567}) {
    int32_t output = -1;
    EXPECT_TRUE(queue.Pop(&output));
    EXPECT_EQ(output, expected);
  }

  int32_t output = -1;
  EXPECT_FALSE(queue.Pop(&output));
}

// --- Move-only types ---

TEST(UnboundedMPSCQueue, MoveOnlyUniquePtr)
{
  UnboundedMPSCQueue<UniquePtr<int32_t>> queue;
  auto* msg = new UnboundedMPSCQueue<UniquePtr<int32_t>>::Message();
  msg->data = MakeUnique<int32_t>(99);
  queue.Push(msg);

  UniquePtr<int32_t> output;
  EXPECT_TRUE(queue.Pop(&output));
  ASSERT_NE(output, nullptr);
  EXPECT_EQ(*output, 99);
  EXPECT_FALSE(queue.Pop(&output));
}

TEST(UnboundedMPSCQueue, MoveOnlyStruct)
{
  UnboundedMPSCQueue<MoveOnlyValue> queue;
  for (int32_t i = 0; i < 10; i++) {
    PushValue<MoveOnlyValue>(queue, MoveOnlyValue(i));
  }

  for (int32_t i = 0; i < 10; i++) {
    MoveOnlyValue output;
    EXPECT_TRUE(queue.Pop(&output));
    EXPECT_EQ(output.mValue, i);
  }

  MoveOnlyValue output;
  EXPECT_FALSE(queue.Pop(&output));
}

TEST(UnboundedMPSCQueue, MoveOnlyFIFO)
{
  UnboundedMPSCQueue<UniquePtr<int32_t>> queue;
  for (int32_t i = 0; i < 100; i++) {
    auto* msg = new UnboundedMPSCQueue<UniquePtr<int32_t>>::Message();
    msg->data = MakeUnique<int32_t>(i);
    queue.Push(msg);
  }

  for (int32_t i = 0; i < 100; i++) {
    UniquePtr<int32_t> output;
    EXPECT_TRUE(queue.Pop(&output));
    ASSERT_NE(output, nullptr);
    EXPECT_EQ(*output, i);
  }
}

// --- Complex types ---

TEST(UnboundedMPSCQueue, StdString)
{
  UnboundedMPSCQueue<std::string> queue;
  PushValue<std::string>(queue, "hello");
  PushValue<std::string>(queue, "world");
  PushValue<std::string>(queue, "foo");

  std::string output;
  EXPECT_TRUE(queue.Pop(&output));
  EXPECT_EQ(output, "hello");
  EXPECT_TRUE(queue.Pop(&output));
  EXPECT_EQ(output, "world");
  EXPECT_TRUE(queue.Pop(&output));
  EXPECT_EQ(output, "foo");
  EXPECT_FALSE(queue.Pop(&output));
}

// --- Destructor / edge cases ---

TEST(UnboundedMPSCQueue, DestructorDrainsElements)
{
  int32_t before = CountedType::sLiveCount;
  {
    UnboundedMPSCQueue<CountedType> queue;
    for (int32_t i = 0; i < 50; i++) {
      PushValue<CountedType>(queue, CountedType(i));
    }
  }

  EXPECT_EQ(static_cast<int32_t>(CountedType::sLiveCount), before);
}

TEST(UnboundedMPSCQueue, DestructorDrainsMoveOnlyElements)
{
  int32_t before = CountedType::sLiveCount;
  {
    UnboundedMPSCQueue<UniquePtr<CountedType>> queue;
    for (int32_t i = 0; i < 50; i++) {
      auto* msg = new UnboundedMPSCQueue<UniquePtr<CountedType>>::Message();
      msg->data = MakeUnique<CountedType>(i);
      queue.Push(msg);
    }
  }
  EXPECT_EQ(static_cast<int32_t>(CountedType::sLiveCount), before);
}

TEST(UnboundedMPSCQueue, DestroyEmptyQueue)
{
  {
    UnboundedMPSCQueue<int32_t> queue;
  }
  SUCCEED();
}

// --- Multi-producer tests ---

class UnboundedMPSCQueueMTTest : public ::testing::TestWithParam<int32_t> {};

INSTANTIATE_TEST_SUITE_P(ThreadCounts, UnboundedMPSCQueueMTTest,
                         ::testing::Values(124816));

// No items lost under concurrent multi-producer pushes (lost-update / ABA on
// the head pointer).
TEST_P(UnboundedMPSCQueueMTTest, AllItemsReceived) {
  const int32_t numThreads = GetParam();
  UnboundedMPSCQueue<int32_t> queue;

  // Spawn N producer task queues.
  nsTArray<RefPtr<TaskQueue>> threads(numThreads);
  for (int32_t t = 0; t < numThreads; t++) {
    RefPtr<TaskQueue> tq =
        nsThreadManager::get().CreateBackgroundTaskQueue("MPSCTest");
    ASSERT_NE(tq, nullptr);
    threads.AppendElement(std::move(tq));
  }

  // Each producer pushes 10k items.
  for (int32_t t = 0; t < numThreads; t++) {
    MOZ_ALWAYS_SUCCEEDS(
        threads[t]->Dispatch(NS_NewRunnableFunction("MPSCPush", [&queue]() {
          for (int32_t i = 0; i < 10000; i++) {
            PushValue(queue, i);
          }
        })));
  }

  // Wait for all producers to finish.
  for (int32_t t = numThreads - 1; t >= 0; t--) {
    threads[t]->BeginShutdown();
    threads[t]->AwaitShutdownAndIdle();
  }

  // Drain and count -- must equal N * 10k.
  int32_t total = 0;
  int32_t value;
  while (queue.Pop(&value)) {
    total++;
  }
  EXPECT_EQ(total, numThreads * 10000);
}

// Checks for data tearing -- each producer writes values in a unique range
// (threadIdx * 1M + i), then we verify every expected value is present.
TEST_P(UnboundedMPSCQueueMTTest, NoDataCorruption) {
  const int32_t numThreads = GetParam();
  const int32_t itemsPerThread = 10000;
  UnboundedMPSCQueue<int32_t> queue;

  // Spawn N producer task queues.
  nsTArray<RefPtr<TaskQueue>> threads(numThreads);
  for (int32_t t = 0; t < numThreads; t++) {
    RefPtr<TaskQueue> tq =
        nsThreadManager::get().CreateBackgroundTaskQueue("MPSCTest");
    ASSERT_NE(tq, nullptr);
    threads.AppendElement(std::move(tq));
  }

  // Each producer pushes 10k items tagged with its thread index.
  for (int32_t t = 0; t < numThreads; t++) {
    int32_t threadIdx = t;
    MOZ_ALWAYS_SUCCEEDS(threads[t]->Dispatch(
        NS_NewRunnableFunction("MPSCPush", [&queue, threadIdx]() {
          for (int32_t i = 0; i < 10000; i++) {
            PushValue(queue, threadIdx * 1000000 + i);
          }
        })));
  }

  // Wait for all producers.
  for (int32_t t = numThreads - 1; t >= 0; t--) {
    threads[t]->BeginShutdown();
    threads[t]->AwaitShutdownAndIdle();
  }

  // Collect all values.
  std::vector<int32_t> values;
  values.reserve(numThreads * itemsPerThread);
  int32_t value;
  while (queue.Pop(&value)) {
    values.push_back(value);
  }

  EXPECT_EQ(static_cast<int32_t>(values.size()), numThreads * itemsPerThread);

  // Sort and verify every expected value is present exactly once.
  std::sort(values.begin(), values.end());
  for (int32_t t = 0; t < numThreads; t++) {
    for (int32_t i = 0; i < itemsPerThread; i++) {
      int32_t expected = t * 1000000 + i;
      auto it = std::lower_bound(values.begin(), values.end(), expected);
      ASSERT_NE(it, values.end()) << "Missing value " << expected;
      EXPECT_EQ(*it, expected);
    }
  }
}

// Same as AllItemsReceived but with UniquePtr to verify move semantics under
// contention -- catches double-free or use-after-move.
TEST_P(UnboundedMPSCQueueMTTest, MoveOnlyMultiProducer) {
  const int32_t numThreads = GetParam();
  UnboundedMPSCQueue<UniquePtr<int32_t>> queue;

  // Spawn N producer task queues.
  nsTArray<RefPtr<TaskQueue>> threads(numThreads);
  for (int32_t t = 0; t < numThreads; t++) {
    RefPtr<TaskQueue> tq =
        nsThreadManager::get().CreateBackgroundTaskQueue("MPSCTest");
    ASSERT_NE(tq, nullptr);
    threads.AppendElement(std::move(tq));
  }

  // Each producer pushes 10k heap-allocated items.
  for (int32_t t = 0; t < numThreads; t++) {
    MOZ_ALWAYS_SUCCEEDS(
        threads[t]->Dispatch(NS_NewRunnableFunction("MPSCPush", [&queue]() {
          for (int32_t i = 0; i < 10000; i++) {
            auto* msg = new UnboundedMPSCQueue<UniquePtr<int32_t>>::Message();
            msg->data = MakeUnique<int32_t>(i);
            queue.Push(msg);
          }
        })));
  }

  // Wait for all producers.
  for (int32_t t = numThreads - 1; t >= 0; t--) {
    threads[t]->BeginShutdown();
    threads[t]->AwaitShutdownAndIdle();
  }

  // Drain, verify non-null, count.
  int32_t total = 0;
  UniquePtr<int32_t> value;
  while (queue.Pop(&value)) {
    ASSERT_NE(value, nullptr);
    total++;
  }
  EXPECT_EQ(total, numThreads * 10000);
}

// --- Stress tests ---

// Brute-force stress test: 8 producers x 100k items to surface races that only
// manifest under heavy contention.
TEST(UnboundedMPSCQueue, StressHighVolume)
{
  const int32_t numThreads = 8;
  UnboundedMPSCQueue<int32_t> queue;

  // Spawn 8 producer task queues.
  nsTArray<RefPtr<TaskQueue>> threads(numThreads);
  for (int32_t t = 0; t < numThreads; t++) {
    RefPtr<TaskQueue> tq =
        nsThreadManager::get().CreateBackgroundTaskQueue("MPSCStress");
    ASSERT_NE(tq, nullptr);
    threads.AppendElement(std::move(tq));
  }

  // Each producer pushes 100k items.
  for (int32_t t = 0; t < numThreads; t++) {
    MOZ_ALWAYS_SUCCEEDS(
        threads[t]->Dispatch(NS_NewRunnableFunction("MPSCPush", [&queue]() {
          for (int32_t i = 0; i < 100000; i++) {
            PushValue(queue, i);
          }
        })));
  }

  // Wait, drain, verify total count.
  for (int32_t t = numThreads - 1; t >= 0; t--) {
    threads[t]->BeginShutdown();
    threads[t]->AwaitShutdownAndIdle();
  }

  int32_t total = 0;
  int32_t value;
  while (queue.Pop(&value)) {
    total++;
  }
  EXPECT_EQ(total, numThreads * 100000);
}

// True MPSC pattern -- multiple producers push while a single consumer pops
// concurrently, catching races between Push and Pop on the shared head/tail
// pointers.
TEST(UnboundedMPSCQueue, StressConcurrentPushPop)
{
  const int32_t numProducers = 4;
  UnboundedMPSCQueue<int32_t> queue;
  Atomic<bool> producersDone(false);
  Atomic<int32_t> totalPopped(0);

  // Spawn 4 producer task queues.
  nsTArray<RefPtr<TaskQueue>> producers(numProducers);
  for (int32_t t = 0; t < numProducers; t++) {
    RefPtr<TaskQueue> tq =
        nsThreadManager::get().CreateBackgroundTaskQueue("MPSCProd");
    ASSERT_NE(tq, nullptr);
    producers.AppendElement(std::move(tq));
  }

  // Start a consumer that spins draining the queue.
  RefPtr<TaskQueue> consumer =
      nsThreadManager::get().CreateBackgroundTaskQueue("MPSCCons");
  MOZ_ALWAYS_SUCCEEDS(consumer->Dispatch(NS_NewRunnableFunction(
      "MPSCConsumer", [&queue, &producersDone, &totalPopped]() {
        int32_t value;
        for (;;) {
          while (queue.Pop(&value)) {
            totalPopped++;
          }
          if (producersDone) {
            // Final drain after all producers are done.
            while (queue.Pop(&value)) {
              totalPopped++;
            }
            break;
          }
        }
      })));

  // Start all producers, each pushing 100k items.
  for (int32_t t = 0; t < numProducers; t++) {
    MOZ_ALWAYS_SUCCEEDS(
        producers[t]->Dispatch(NS_NewRunnableFunction("MPSCPush", [&queue]() {
          for (int32_t i = 0; i < 100000; i++) {
            PushValue(queue, i);
          }
        })));
  }

  // Wait for producers, signal consumer, wait for consumer.
  for (int32_t t = numProducers - 1; t >= 0; t--) {
    producers[t]->BeginShutdown();
    producers[t]->AwaitShutdownAndIdle();
  }

  producersDone = true;
  consumer->BeginShutdown();
  consumer->AwaitShutdownAndIdle();

  // Consumer must have popped all items.
  EXPECT_EQ(static_cast<int32_t>(totalPopped), numProducers * 100000);
}

}  // namespace mozilla::test_mpsc_queue

Messung V0.5 in Prozent
C=91 H=94 G=92

¤ Dauer der Verarbeitung: 0.6 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002