arrp 0.0.15
Auto returning resource pool for modern C++
Loading...
Searching...
No Matches
resource_pool.hpp
1/*
2 arrp
3 Auto returning resource pool for modern C++
4
5 BSD 3-Clause License
6
7 Copyright (c) 2026 Abdulkareem Siddiq
8 All rights reserved.
9
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
12
13 1. Redistributions of source code must retain the above copyright notice,
14 this list of conditions and the following disclaimer.
15
16 2. Redistributions in binary form must reproduce the above copyright notice,
17 this list of conditions and the following disclaimer in the documentation
18 and/or other materials provided with the distribution.
19
20 3. Neither the name of the copyright holder nor the names of its
21 contributors may be used to endorse or promote products derived from
22 this software without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
28 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 POSSIBILITY OF SUCH DAMAGE.
35 */
36
37#pragma once
38#ifndef RESOURCE_POOL_HPP
39#define RESOURCE_POOL_HPP
40
41#include <atomic>
42#include <concepts>
43#include <cstdint>
44#include <deque>
45#include <format>
46#include <mutex>
47#include <stdexcept>
48#include <type_traits>
49
50#include "private/common.hpp"
51#include "private/scoped_resource.hpp"
52#include "siddiqsoft/RunOnEnd.hpp"
53
54namespace siddiqsoft::arrp
55{
120 template <typename T, typename SRT = scoped_resource<T>, uint8_t InitCapacity = resource_pool_limits::DefaultCapacity>
121 requires((InitCapacity <= resource_pool_limits::MaxCapacity)) && NonNumericMoveConstructible<T> &&
122 std::derived_from<SRT, scoped_resource<T>>
123 class resource_pool
124 {
125 private:
127 uint8_t m_capacity {InitCapacity};
128
130 std::atomic_uint16_t m_resources_checkedout {0};
131
134 std::atomic_uint16_t m_invalidated_resources {0};
135
138 std::atomic_uint64_t m_counter_checkout {0};
139
141 std::atomic_uint64_t m_counter_ondemand_adds {0};
142
144 std::atomic_uint64_t m_counter_checkin {0};
145
147 std::atomic_uint64_t m_counter_auto_returned {0};
148
151 std::deque<T> m_pool {};
152
153#if defined(arrp_USE_RECURSIVE_MUTEX) || defined(ARRP_USE_RECURSIVE_MUTEX)
159 mutable std::recursive_mutex m_pool_lock {};
160 // It might be more expensive but the client might find this useful!
161 #warning "You're using std::recursive_mutex which is more expensive"
162#else
166 mutable std::mutex m_pool_lock {};
167#endif
168
173 std::function<SRT(resource_pool&)> m_callback_to_add_new_raw_resource_to_pool {};
174
175 public:
180 {
181 NoGrow,
182 AutoGrow
183 };
184
186 static inline std::function<SRT(resource_pool&)> CallbackDoNotAutoAddResource = [](resource_pool&) -> SRT {
187 throw std::runtime_error("No items in the pool; add something first.");
188 };
189
223 resource_pool(std::function<SRT(resource_pool&)>&& new_resource_callback)
224 {
225 if (new_resource_callback) {
226 m_callback_to_add_new_raw_resource_to_pool = std::move(new_resource_callback);
227 }
228 else {
229 // This is just in case someone sends an empty callback!
230 m_callback_to_add_new_raw_resource_to_pool = CallbackDoNotAutoAddResource;
231 }
232 }
233
237 resource_pool(auto_add_policy add_policy = auto_add_policy::NoGrow)
238 {
239 if (add_policy == auto_add_policy::NoGrow) {
240 m_callback_to_add_new_raw_resource_to_pool = CallbackDoNotAutoAddResource;
241 }
242 else if (add_policy == auto_add_policy::AutoGrow) {
243 // This method is declared here as lambda to capture the this pointer
244 // whereas if we attempted to declared it earlier as a static inline then the
245 // this pointer would not be captured.
246 m_callback_to_add_new_raw_resource_to_pool = [this](resource_pool& pool) -> SRT {
247 // Create a SRT element and wire up the auto-return callback to return
248 // the resource back to this object.
249 return SRT {T {}, [this](T&& src) {
250 this->m_counter_auto_returned++;
251 this->checkin(std::forward<T&&>(src));
252 }};
253 // Allow the compiler to use NRVO (move elision; do not use std::move here!)
254 // return temp;
255 };
256 }
257 }
258
259 // Not copy-able, not movable
261 resource_pool(resource_pool&) = delete;
262
264 resource_pool(resource_pool&& src) = delete;
265
267 resource_pool& operator=(resource_pool&) = delete;
268
270 resource_pool& operator=(resource_pool&& src) = delete;
271
285 ~resource_pool() { clear(); }
286
305 void clear() noexcept
306 {
307 std::scoped_lock l(m_pool_lock);
308 m_pool.clear();
309 }
310
334 [[nodiscard]] size_t size() const noexcept
335 {
336 std::scoped_lock l(m_pool_lock);
337 return m_pool.size();
338 }
339
377 [[nodiscard]] auto checkout() -> SRT
378 {
379 // Create a guard to decrement m_resources_checkedout if the factory callback throws
380 // This ensures we don't leak the checkout count if the factory fails
381 auto checkout_guard = [this]() {
382 if (m_resources_checkedout > 0) {
383 m_resources_checkedout--;
384 }
385 };
386
387 try {
388 // @note We use a unique_lock vs a scoped_lock to allow ourselves
389 // to create the resource outside the lock!
390 std::unique_lock l(m_pool_lock);
391
392 if (!m_pool.empty()) {
393 // The pool is non-empty; return from the pool
394 // Return first element from the pool and pop it on scope end
395 RunOnEnd pop_guard([&]() { m_pool.pop_front(); });
396
397 m_resources_checkedout++;
398 ++m_counter_checkout;
399
400 // Make a wrapper..
401 // Create a SRT element and wire up the auto-return callback to return
402 // the resource back to this object.
403 return SRT {std::move(m_pool.front()), [this](T&& src) {
404 this->m_counter_auto_returned++;
405 this->checkin(std::forward<T&&>(src));
406 }};
407 // Allow the compiler to use NRVO (move elision; do not use std::move here!)
408 // return temp;
409
410 // The pop_front() happens within this scope and within the lock!
411 }
412 else if ((m_capacity > m_pool.size() + m_resources_checkedout) && m_callback_to_add_new_raw_resource_to_pool) {
413 // We have no more items in the pool (we're starting up or everything is
414 // checked out) but we have not reached the limit. The limit is number
415 // of m_resources_checkedout + pool.size() < m_capacity We are
416 // under-capacity.. so we can return to the caller a new item..
417 m_resources_checkedout++;
418 // We should unlock the resource and ..
419 l.unlock();
420
421 ++m_counter_checkout;
422 // Update the attempted delegated calls to add new raw resource to pool.
423 ++m_counter_ondemand_adds;
424
425 // ..delegate the new resource acquisition
426 // outside the lock.
427 return m_callback_to_add_new_raw_resource_to_pool(*this);
428 }
429 else if (m_capacity > m_pool.size() + m_resources_checkedout) {
430 // We're under-capacity.. but no dynamic resource provider
431 std::cerr << std::format("We're under-capacity.. but no dynamic resource provider\n");
432 }
433 } // scope end
434 catch (std::exception& ex) {
435 checkout_guard();
436 std::cerr << std::format("Error in checkout: {}\n", ex.what());
437 throw;
438 }
439 catch (...) {
440 checkout_guard();
441 std::cerr << std::format("UNKNOWN Error in checkout\n");
442 throw;
443 }
444
445 auto msg = std::format(
446 "Pool Size:{} checkedout:{} capacity:{}", m_pool.size(), m_resources_checkedout.load(), m_capacity);
447 throw std::runtime_error(msg);
448 }
449
486 void checkin(T&& raw_resource)
487 {
488 ++m_counter_checkin;
489 {
490 std::scoped_lock l(m_pool_lock);
491
492 m_pool.push_back(std::move(raw_resource));
493 if (m_resources_checkedout > 0) m_resources_checkedout--;
494 } // lock scope end
495 }
496
497#if defined(NLOHMANN_JSON_VERSION_MAJOR)
545 nlohmann::json to_json() const
546 {
547 auto myPoolSize = this->size();
548
549 return {{"_typver", "siddiqsoft.arrp.resource_pool/0.0.0"},
550 {"capacity", m_capacity},
551 {"size", myPoolSize},
552 {"load", myPoolSize + m_resources_checkedout.load()},
553 {"invalidated", m_invalidated_resources.load()},
554 {"checkedout", m_resources_checkedout.load()},
555 {"counters",
556 {{"autoreturns", m_counter_auto_returned.load()},
557 {"newitems", m_counter_ondemand_adds.load()},
558 {"return", m_counter_checkin.load()},
559 {"borrow", m_counter_checkout.load()}}}};
560 }
561#endif
562 };
563
564#if defined(NLOHMANN_JSON_VERSION_MAJOR)
589 template <typename T, typename SRT = scoped_resource<T>, uint8_t InitCapacity = arrp::resource_pool_limits::DefaultCapacity>
590 requires((InitCapacity <= arrp::resource_pool_limits::MaxCapacity)) && NonNumericMoveConstructible<T> &&
591 std::derived_from<SRT, scoped_resource<T>>
592 static void to_json(nlohmann::json& dest, const siddiqsoft::arrp::resource_pool<T, SRT, InitCapacity>& src)
593 {
594 dest = src.to_json();
595 }
596#endif
597
598
599} // namespace siddiqsoft::arrp
600#endif
Thread-safe auto-returning resource pool for modern C++.
auto_add_policy
This controls the auto-grow (or adding items when the pool is starving) and below capacity (up to the...
resource_pool(auto_add_policy add_policy=auto_add_policy::NoGrow)
This is the default constructor.. the policy is to not auto-grow. The client code can as for AutoGrow...
resource_pool(resource_pool &)=delete
Copy constructor is deleted.
resource_pool & operator=(resource_pool &&src)=delete
Move assignment operator is deleted.
resource_pool & operator=(resource_pool &)=delete
Copy assignment operator is deleted.
resource_pool(resource_pool &&src)=delete
Move constructor is deleted.
~resource_pool()
Destructor - clears all resources from the pool.
static std::function< SRT(resource_pool &)> CallbackDoNotAutoAddResource
This callback is the default and does not grow the resource; it throws a runtime_error.