/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <thread>
#include <folly/portability/GTest.h>
#include <folly/compression/CompressionContextPool.h>
#include <folly/compression/CompressionContextPoolSingletons.h>
#include <folly/compression/CompressionCoreLocalContextPool.h>
namespace folly {
namespace compression {
namespace {
std::atomic<size_t> numFoos{0};
std::atomic<size_t> numDeleted{0};
class Foo {
public:
void use() {
EXPECT_FALSE(used_);
used_ = true;
}
void reset() { used_ = false; }
private:
bool used_{false};
};
struct FooCreator {
Foo* operator()() {
numFoos++;
return new Foo();
}
};
struct BadFooCreator {
Foo* operator()() {
numFoos++;
return nullptr;
}
};
struct FooDeleter {
void operator()(Foo* f) {
numDeleted++;
delete f;
}
};
struct FooResetter {
void operator()(Foo* f) { f->reset(); }
};
using Pool = CompressionContextPool<Foo, FooCreator, FooDeleter, FooResetter>;
using BadPool =
CompressionContextPool<Foo, BadFooCreator, FooDeleter, FooResetter>;
} // anonymous namespace
class CompressionContextPoolTest : public testing::Test {
protected:
void SetUp() override { pool_ = std::make_unique<Pool>(); }
void TearDown() override { pool_.reset(); }
std::unique_ptr<Pool> pool_;
};
TEST_F(CompressionContextPoolTest, testGet) {
auto ptr = pool_->get();
EXPECT_TRUE(ptr);
EXPECT_EQ(pool_->created_count(), 1);
}
TEST_F(CompressionContextPoolTest, testSame) {
Pool::Object* tmp;
{
auto ptr = pool_->get();
tmp = ptr.get();
}
{
auto ptr = pool_->get();
EXPECT_EQ(tmp, ptr.get());
}
EXPECT_EQ(pool_->size(), 1);
}
TEST_F(CompressionContextPoolTest, testFree) {
EXPECT_EQ(pool_->size(), 0);
{
auto ptr = pool_->get();
EXPECT_TRUE(ptr);
EXPECT_EQ(pool_->size(), 0);
}
EXPECT_EQ(pool_->size(), 1);
EXPECT_EQ(numFoos.load(), 1);
EXPECT_EQ(numDeleted.load(), 0);
pool_.reset();
EXPECT_EQ(numFoos.load(), numDeleted.load());
}
TEST_F(CompressionContextPoolTest, testDifferent) {
{
auto ptr1 = pool_->get();
auto ptr2 = pool_->get();
EXPECT_NE(ptr1.get(), ptr2.get());
}
EXPECT_EQ(pool_->created_count(), 2);
EXPECT_EQ(pool_->size(), 2);
}
TEST_F(CompressionContextPoolTest, testLifo) {
auto ptr1 = pool_->get();
auto ptr2 = pool_->get();
auto ptr3 = pool_->get();
auto t1 = ptr1.get();
auto t2 = ptr2.get();
auto t3 = ptr3.get();
EXPECT_NE(t1, t2);
EXPECT_NE(t1, t3);
EXPECT_NE(t2, t3);
ptr3.reset();
ptr2.reset();
ptr1.reset();
ptr1 = pool_->get();
EXPECT_EQ(ptr1.get(), t1);
ptr1.reset();
ptr1 = pool_->get();
EXPECT_EQ(ptr1.get(), t1);
ptr2 = pool_->get();
EXPECT_EQ(ptr2.get(), t2);
ptr1.reset();
ptr2.reset();
ptr1 = pool_->get();
EXPECT_EQ(ptr1.get(), t2);
ptr2 = pool_->get();
EXPECT_EQ(ptr2.get(), t1);
ptr3 = pool_->get();
EXPECT_EQ(ptr3.get(), t3);
EXPECT_EQ(pool_->created_count(), numFoos.load());
}
TEST_F(CompressionContextPoolTest, testExplicitCreatorDeleter) {
pool_ = std::make_unique<Pool>(FooCreator(), FooDeleter());
auto ptr = pool_->get();
EXPECT_TRUE(ptr);
}
TEST_F(CompressionContextPoolTest, testMultithread) {
constexpr size_t numThreads = 64 / (folly::kIsSanitizeThread ? 4 : 1);
constexpr size_t numIters = (1 << 14) / (folly::kIsSanitizeThread ? 4 : 1);
std::vector<std::thread> ts;
for (size_t i = 0; i < numThreads; i++) {
ts.emplace_back([&pool = *pool_]() {
for (size_t n = 0; n < numIters; n++) {
auto ref = pool.get();
ref.get();
ref.reset();
}
});
}
for (auto& t : ts) {
t.join();
}
EXPECT_LE(numFoos.load(), numThreads);
EXPECT_LE(numDeleted.load(), 0);
EXPECT_EQ(pool_->created_count(), numFoos.load());
EXPECT_EQ(pool_->created_count(), pool_->size());
}
TEST_F(CompressionContextPoolTest, testBadCreate) {
BadPool pool;
EXPECT_THROW(pool.get(), std::bad_alloc);
}
TEST_F(CompressionContextPoolTest, testReset) {
Pool::Object* tmp;
{
auto ptr = pool_->get();
ptr->use();
tmp = ptr.get();
}
{
auto ptr = pool_->get();
ptr->use();
EXPECT_EQ(ptr.get(), tmp);
}
}
TEST_F(CompressionContextPoolTest, testFlush) {
pool_->get();
pool_->flush_deep();
pool_->get();
EXPECT_EQ(pool_->created_count(), 2);
}
class CompressionCoreLocalContextPoolTest : public testing::Test {
protected:
using Pool = CompressionCoreLocalContextPool<
Foo,
FooCreator,
FooDeleter,
FooResetter,
8>;
void SetUp() override { pool_ = std::make_unique<Pool>(); }
void TearDown() override { pool_.reset(); }
std::unique_ptr<Pool> pool_;
};
TEST_F(CompressionCoreLocalContextPoolTest, testGet) {
auto ptr = pool_->get();
EXPECT_TRUE(ptr);
}
TEST_F(CompressionCoreLocalContextPoolTest, testSame) {
Pool::Object* tmp;
{
auto ptr = pool_->get();
tmp = ptr.get();
}
{
auto ptr = pool_->get();
EXPECT_EQ(tmp, ptr.get());
}
}
TEST_F(CompressionCoreLocalContextPoolTest, testDifferent) {
auto ptr1 = pool_->get();
auto ptr2 = pool_->get();
EXPECT_NE(ptr1.get(), ptr2.get());
}
TEST_F(CompressionCoreLocalContextPoolTest, testSwap) {
auto ptr1 = pool_->get();
auto ptr2 = pool_->get();
EXPECT_NE(ptr1.get(), ptr2.get());
auto tmp1 = ptr1.get();
auto tmp2 = ptr2.get();
ptr2.reset();
ptr1.reset();
ptr2 = pool_->get();
EXPECT_EQ(ptr2.get(), tmp2);
ptr1 = pool_->get();
EXPECT_EQ(ptr1.get(), tmp1);
ptr1.reset();
ptr2.reset();
ptr1 = pool_->get();
EXPECT_EQ(ptr1.get(), tmp1);
ptr2 = pool_->get();
EXPECT_EQ(ptr2.get(), tmp2);
}
TEST_F(CompressionCoreLocalContextPoolTest, testFlush) {
pool_->get();
pool_->flush_deep();
pool_->get();
EXPECT_EQ(pool_->created_count(), 2);
}
TEST_F(CompressionCoreLocalContextPoolTest, testMultithread) {
constexpr size_t numThreads = 64;
constexpr size_t numIters = 1 << 14;
std::vector<std::thread> ts;
for (size_t i = 0; i < numThreads; i++) {
ts.emplace_back([&pool = *pool_]() {
for (size_t n = 0; n < numIters; n++) {
auto ref = pool.get();
CHECK(ref);
ref.reset();
}
});
}
for (auto& t : ts) {
t.join();
}
EXPECT_LE(numFoos.load(), numThreads);
}
TEST_F(CompressionCoreLocalContextPoolTest, testReset) {
Pool::Object* tmp1;
Pool::Object* tmp2;
{
auto ptr1 = pool_->get();
ptr1->use();
tmp1 = ptr1.get();
}
{
auto ptr1 = pool_->get();
auto ptr2 = pool_->get();
ptr1->use();
ptr2->use();
EXPECT_EQ(ptr1.get(), tmp1);
tmp2 = ptr2.get();
}
{
auto ptr1 = pool_->get();
auto ptr2 = pool_->get();
ptr1->use();
ptr2->use();
EXPECT_EQ(ptr1.get(), tmp2);
EXPECT_EQ(ptr2.get(), tmp1);
}
}
#ifdef FOLLY_COMPRESSION_HAS_ZSTD_CONTEXT_POOL_SINGLETONS
TEST(CompressionContextPoolSingletonsTest, testSingletons) {
EXPECT_NE(contexts::getZSTD_CCtx(), nullptr);
EXPECT_NE(contexts::getZSTD_DCtx(), nullptr);
}
TEST(CompressionContextPoolSingletonsTest, testSingletonsNull) {
EXPECT_EQ(contexts::getNULL_ZSTD_CCtx(), nullptr);
EXPECT_EQ(contexts::getNULL_ZSTD_DCtx(), nullptr);
}
#endif // FOLLY_COMPRESSION_HAS_ZSTD_CONTEXT_POOL_SINGLETONS
} // namespace compression
} // namespace folly