trisquel-icecat/icecat/xpcom/tests/gtest/TestAutoRefCnt.cpp

168 lines
5.7 KiB
C++

/* -*- 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 "nsISupportsImpl.h"
#include "mozilla/Atomics.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "gtest/gtest.h"
#include "mozilla/gtest/MozAssertions.h"
using namespace mozilla;
class nsThreadSafeAutoRefCntRunner final : public Runnable {
public:
NS_IMETHOD Run() final {
for (int i = 0; i < 10000; i++) {
if (++sRefCnt == 1) {
sIncToOne++;
}
if (--sRefCnt == 0) {
sDecToZero++;
}
}
return NS_OK;
}
static ThreadSafeAutoRefCnt sRefCnt;
static Atomic<uint32_t, Relaxed> sIncToOne;
static Atomic<uint32_t, Relaxed> sDecToZero;
nsThreadSafeAutoRefCntRunner() : Runnable("nsThreadSafeAutoRefCntRunner") {}
private:
~nsThreadSafeAutoRefCntRunner() = default;
};
ThreadSafeAutoRefCnt nsThreadSafeAutoRefCntRunner::sRefCnt;
Atomic<uint32_t, Relaxed> nsThreadSafeAutoRefCntRunner::sIncToOne(0);
Atomic<uint32_t, Relaxed> nsThreadSafeAutoRefCntRunner::sDecToZero(0);
class nsThreadSafeAutoRefCntDecrementWithLimitRunner final : public Runnable {
public:
static constexpr size_t kDecrementsPerThread = 1000;
NS_IMETHOD Run() final {
for (size_t i = 0; i < kDecrementsPerThread; i++) {
auto [ok, count] = sRefCnt.DecrementWithLimit<1>();
if (!ok) {
sLimitHits++;
break;
}
}
return NS_OK;
}
static ThreadSafeAutoRefCnt sRefCnt;
// Relaxed ordering so sLimitHits doesn't affect the ordering properties of
// DecrementWithLimit being tested.
static Atomic<uint32_t, Relaxed> sLimitHits;
nsThreadSafeAutoRefCntDecrementWithLimitRunner()
: Runnable("nsThreadSafeAutoRefCntDecrementWithLimitRunner") {}
private:
~nsThreadSafeAutoRefCntDecrementWithLimitRunner() = default;
};
ThreadSafeAutoRefCnt nsThreadSafeAutoRefCntDecrementWithLimitRunner::sRefCnt;
Atomic<uint32_t, Relaxed>
nsThreadSafeAutoRefCntDecrementWithLimitRunner::sLimitHits(0);
// When a refcounted object is actually owned by a cache, we may not
// want to release the object after last reference gets released. In
// this pattern, the cache may rely on the balance of increment to one
// and decrement to zero, so that it can maintain a counter for GC.
TEST(AutoRefCnt, ThreadSafeAutoRefCntBalance)
{
static const size_t kThreadCount = 4;
nsCOMPtr<nsIThread> threads[kThreadCount];
for (auto& thread : threads) {
nsresult rv = NS_NewNamedThread("AutoRefCnt Test", getter_AddRefs(thread),
new nsThreadSafeAutoRefCntRunner);
EXPECT_NS_SUCCEEDED(rv);
}
for (const auto& thread : threads) {
thread->Shutdown();
}
EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sRefCnt, nsrefcnt(0));
EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sIncToOne,
nsThreadSafeAutoRefCntRunner::sDecToZero);
}
// Spawns kThreadCount threads each attempting kDecrementsPerThread
// decrements, then verifies the final refcount is 1 and that exactly
// aExpectedLimitHits threads hit the limit.
// kInitial is derived from aExpectedLimitHits so that exactly
// (kThreadCount - aExpectedLimitHits) threads can complete all their
// decrements; the rest hit the limit.
static void RunDecrementWithLimitThreaded(uint32_t aExpectedLimitHits) {
static const size_t kThreadCount = 4;
const nsrefcnt kInitial =
(kThreadCount - aExpectedLimitHits) *
nsThreadSafeAutoRefCntDecrementWithLimitRunner::kDecrementsPerThread +
1;
nsThreadSafeAutoRefCntDecrementWithLimitRunner::sRefCnt = kInitial;
nsThreadSafeAutoRefCntDecrementWithLimitRunner::sLimitHits = 0;
nsCOMPtr<nsIThread> threads[kThreadCount];
for (auto& thread : threads) {
nsresult rv =
NS_NewNamedThread("AutoRefCnt Test", getter_AddRefs(thread),
new nsThreadSafeAutoRefCntDecrementWithLimitRunner);
EXPECT_NS_SUCCEEDED(rv);
}
for (const auto& thread : threads) {
thread->Shutdown();
}
EXPECT_EQ(nsThreadSafeAutoRefCntDecrementWithLimitRunner::sRefCnt,
nsrefcnt(1));
EXPECT_EQ(nsThreadSafeAutoRefCntDecrementWithLimitRunner::sLimitHits,
uint32_t(aExpectedLimitHits));
}
// Verify DecrementWithLimit with no lost updates when the limit is never hit.
TEST(AutoRefCnt, ThreadSafeAutoRefCntDecrementWithLimitNoHit)
{
RunDecrementWithLimitThreaded(0);
}
// Verify DecrementWithLimit correctly enforces the limit. With kInitial == 1
// (equal to Limit), all threads hit the limit on their first attempt,
// giving a deterministic sLimitHits == kThreadCount.
TEST(AutoRefCnt, ThreadSafeAutoRefCntDecrementWithLimitHit)
{
static const size_t kThreadCount = 4;
RunDecrementWithLimitThreaded(kThreadCount);
}
// Verify DecrementWithLimit performs exactly kDecrementsPerThread successful
// decrements before hitting the limit. Single-threaded for determinism.
TEST(AutoRefCnt, ThreadSafeAutoRefCntDecrementWithLimitSequential)
{
const nsrefcnt kDecrementsPerThread =
nsThreadSafeAutoRefCntDecrementWithLimitRunner::kDecrementsPerThread;
// Initialize so exactly kDecrementsPerThread decrements succeed before
// hitting the limit at 1.
nsThreadSafeAutoRefCntDecrementWithLimitRunner::sRefCnt =
kDecrementsPerThread + 1;
size_t successes = 0;
while (true) {
auto [ok, count] = nsThreadSafeAutoRefCntDecrementWithLimitRunner::sRefCnt
.DecrementWithLimit<1>();
if (!ok) {
break;
}
successes++;
}
EXPECT_EQ(successes, size_t(kDecrementsPerThread));
EXPECT_EQ(nsThreadSafeAutoRefCntDecrementWithLimitRunner::sRefCnt,
nsrefcnt(1));
}