/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the * LICENSE file in the root directory of this source tree) and the GPLv2 (found * in the COPYING file in the root directory of this source tree). */ #pragma once #include #include #include #include #include namespace pzstd { /** * An unbounded pool of resources. * A `ResourcePool` requires a factory function that takes allocates `T*` and * a free function that frees a `T*`. * Calling `ResourcePool::get()` will give you a new `ResourcePool::UniquePtr` * to a `T`, and when it goes out of scope the resource will be returned to the * pool. * The `ResourcePool` *must* survive longer than any resources it hands out. * Remember that `ResourcePool` hands out mutable `T`s, so make sure to clean * up the resource before or after every use. */ template class ResourcePool { public: class Deleter; using Factory = std::function; using Free = std::function; using UniquePtr = std::unique_ptr; private: std::mutex mutex_; Factory factory_; Free free_; std::vector resources_; unsigned inUse_; public: /** * Creates a `ResourcePool`. * * @param factory The function to use to create new resources. * @param free The function to use to free resources created by `factory`. */ ResourcePool(Factory factory, Free free) : factory_(std::move(factory)), free_(std::move(free)), inUse_(0) {} /** * @returns A unique pointer to a resource. The resource is null iff * there are no available resources and `factory()` returns null. */ UniquePtr get() { std::lock_guard lock(mutex_); if (!resources_.empty()) { UniquePtr resource{resources_.back(), Deleter{*this}}; resources_.pop_back(); ++inUse_; return resource; } UniquePtr resource{factory_(), Deleter{*this}}; ++inUse_; return resource; } ~ResourcePool() noexcept { assert(inUse_ == 0); for (const auto resource : resources_) { free_(resource); } } class Deleter { ResourcePool *pool_; public: explicit Deleter(ResourcePool &pool) : pool_(&pool) {} void operator() (T *resource) { std::lock_guard lock(pool_->mutex_); // Make sure we don't put null resources into the pool if (resource) { pool_->resources_.push_back(resource); } assert(pool_->inUse_ > 0); --pool_->inUse_; } }; }; }