├── Makefile ├── .gitignore ├── LICENSE ├── testrunner.cc ├── README.md └── thingpool.hh /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS:=-std=gnu++17 -Wall -O -MMD -MP -pthread 2 | 3 | PROGRAMS = testrunner 4 | 5 | all: $(PROGRAMS) 6 | 7 | clean: 8 | rm -f *~ *.o *.d test $(PROGRAMS) 9 | 10 | -include *.d 11 | 12 | testrunner: testrunner.o 13 | $(CXX) -std=gnu++17 $^ -o $@ 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 bert hubert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /testrunner.cc: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "doctest.h" 3 | #include "thingpool.hh" 4 | using namespace std; 5 | 6 | struct TestThing 7 | { 8 | static unsigned int s_instances; 9 | TestThing() 10 | { 11 | ++s_instances; 12 | } 13 | ~TestThing() 14 | { 15 | --s_instances; 16 | } 17 | }; 18 | 19 | unsigned int TestThing::s_instances; 20 | 21 | TEST_CASE("basic test") { 22 | ThingPool tp; 23 | { 24 | auto lease1 = tp.getLease(); 25 | CHECK(lease1->s_instances == 1); 26 | auto lease2 = tp.getLease(); 27 | CHECK(lease1->s_instances == 2); 28 | } 29 | CHECK(TestThing::s_instances == 2); 30 | CHECK(tp.d_maxout==2); 31 | CHECK(tp.d_out==0); 32 | 33 | tp.clear(); 34 | CHECK(TestThing::s_instances == 0); 35 | } 36 | 37 | TEST_CASE("release test") { 38 | ThingPool tp; 39 | 40 | auto lease1 = tp.getLease(); 41 | CHECK(lease1->s_instances == 1); 42 | auto lease2 = tp.getLease(); 43 | CHECK(lease1->s_instances == 2); 44 | CHECK(tp.d_out==2); 45 | lease2.release(); 46 | 47 | CHECK(TestThing::s_instances == 2); 48 | CHECK(tp.d_maxout==2); 49 | CHECK(tp.d_out==1); 50 | 51 | tp.clear(); 52 | CHECK(TestThing::s_instances == 1); 53 | } 54 | 55 | TEST_CASE("abandon test") { 56 | ThingPool tp; 57 | auto lease1 = tp.getLease(); 58 | CHECK(lease1->s_instances == 1); 59 | 60 | auto lease2 = tp.getLease(); 61 | CHECK(lease1->s_instances == 2); 62 | CHECK(tp.d_out==2); 63 | lease2.abandon(); 64 | CHECK(lease1->s_instances == 1); 65 | CHECK(tp.d_out==1); 66 | } 67 | 68 | TEST_CASE("wrong destruction test") { 69 | auto tp = new ThingPool(); 70 | auto lease1 = tp->getLease(); 71 | CHECK(lease1->s_instances == 1); 72 | 73 | REQUIRE_THROWS_AS(delete tp, std::runtime_error); 74 | } 75 | 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThingPool 2 | Minimalistic, easy to use, generic object pool. 3 | 4 | (C) 2024 Bert Hubert - MIT License 5 | 6 | Often you have a class (or Thing) that a single thread can use exclusively, 7 | but which has some cost to create and tear down. The canonical example is 8 | a database connection. 9 | 10 | From ThingPool you can request a Thing, and if need be it will instantiate a 11 | fresh instance for you. Or, in the common case, it will provide you with an 12 | instance it made earlier. 13 | 14 | Syntax: 15 | 16 | ```C++ 17 | ThingPool tp("tk.sqlite3", SQLWFlag::ReadOnly); 18 | ``` 19 | 20 | This says you want objects of type SQLiteWriter, and that they should be 21 | created as: `new SQLiteWriter("tk.sqlite3", SQLWFlag::ReadOnly);` 22 | 23 | Requesting an instance goes like this: 24 | 25 | ```C++ 26 | auto lease = tp.getLease(); 27 | lease->queryT("select count(1) from Document"); 28 | ``` 29 | 30 | As long as 'lease' is alive, the instance is all yours. Once lease goes out of 31 | scope, the object is returned to the pool. The -> syntax allows you to call 32 | methods on SQLiteWriter. If you need a reference to your Thing, use get() (much 33 | like smart pointers). 34 | 35 | Many threads can use getLease() at the same time, and returns are of course 36 | also threadsafe. 37 | 38 | If you no longer need a lease, you can call its release() method to return it 39 | to the pool. If you think the state of your object is bad, you can call the 40 | abandon() method, which will delete the object and not return it to the pool. 41 | 42 | If you allow ThingPool to go out of scope (or if you destroy it) while there 43 | are still active leases, this will throw an exception and likely kill your 44 | process. Don't do this. There is likely no better robust way to deal with this 45 | situation. 46 | 47 | # Including ThingPool 48 | You can simply include the thingpool.hh file. There is a small Makefile in 49 | this repository, and you can use that to build the testrunner to exercise 50 | the code. 51 | 52 | # That's it 53 | Enjoy, feedback is welcome on bert@hubertnet.nl ! 54 | 55 | -------------------------------------------------------------------------------- /thingpool.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* (C) 2024 Bert Hubert - MIT License 10 | 11 | Often you have an class (or Thing) that a single thread can use exclusively, 12 | but which has some cost to create and tear down. The canonical example is 13 | a database connection. 14 | 15 | From ThingPool you can request a Thing, and if need be it will instantiate a 16 | fresh instance for you. Or, in the common case, it will provide you with an 17 | instance it made earlier. 18 | 19 | Syntax: 20 | 21 | ThingPool tp("tk.sqlite3", SQLWFlag::ReadOnly); 22 | 23 | This says you want objects of type SQLiteWriter, and that they should be 24 | created as: new SQLiteWriter("tk.sqlite3", SQLWFlag::ReadOnly); 25 | 26 | Requesting an instance goes like this: 27 | 28 | auto lease = tp.getLease(); 29 | lease->queryT("select count(1) from Document"); 30 | 31 | As long as 'lease' is alive, the instance is all yours. Once lease goes out of 32 | scope, the object is returned to the pool. The -> syntax allows you to call 33 | methods on SQLiteWriter. If you need a reference to your Thing, use get() (much 34 | like smart pointers). 35 | 36 | Many threads can use getLease() at the same time, and returns are of course 37 | also threadsafe. 38 | 39 | If you no longer need a lease, you can call its release() method to return it 40 | to the pool. If you think the state of your object is bad, you can call the 41 | abandon() method, which will delete the object and not return it to the pool. 42 | 43 | If you allow ThreadPool to go out of scope (or if you destroy it) while there 44 | are still active leases, this will throw an exception and likely kill your 45 | process. Don't do this. There is likely no better robust way to deal with this 46 | situation. 47 | 48 | And that's it. Enjoy, feedback is welcome on bert@hubertnet.nl ! 49 | */ 50 | 51 | template 52 | struct ThingPool 53 | { 54 | std::deque d_pool; 55 | std::function d_maker; 56 | std::mutex d_lock; 57 | std::atomic d_out=0; 58 | std::atomic d_maxout=0; 59 | 60 | // lifted with gratitude from https://stackoverflow.com/questions/15537817/c-how-to-store-a-parameter-pack-as-a-variable 61 | template 62 | ThingPool(Args... args) 63 | { 64 | d_maker = [args...]() { return new T(args...); }; 65 | } 66 | 67 | ~ThingPool() noexcept(false) 68 | { 69 | std::lock_guard l(d_lock); 70 | 71 | if(d_out) 72 | throw std::runtime_error("Destroying ThingPool while there are still " + std::to_string(d_out) + " leases outstanding"); 73 | 74 | for(auto& t : d_pool) { 75 | // std::cout<<"Deleting thing "<<(void*)t< l(d_lock); 83 | // cout<<"Received "<<(void*)thing<<" back for the pool"< l(d_lock); 97 | for(auto& t : d_pool) { 98 | delete t; 99 | } 100 | d_pool.clear(); 101 | } 102 | 103 | struct Lease 104 | { 105 | explicit Lease(ThingPool* parent, T* thing) 106 | : d_parent(parent), d_thing(thing) 107 | { 108 | // cout<<"Instantiated a lease for "<<(void*)thing<giveBack(d_thing); 116 | } 117 | else { 118 | // cout<<"Lease destroyed, not returning thing"<giveBack(d_thing); 125 | d_thing = nullptr; 126 | d_parent = nullptr; 127 | } 128 | 129 | void abandon() 130 | { 131 | d_parent->abandon(d_thing); 132 | d_thing = nullptr; 133 | d_parent = nullptr; 134 | } 135 | 136 | Lease(const Lease&) = delete; 137 | 138 | Lease(Lease&& rhs) 139 | { 140 | d_parent = rhs.d_parent; 141 | d_thing = rhs.d_thing; 142 | rhs.d_parent = 0; 143 | } 144 | 145 | T* operator->() { 146 | return d_thing; 147 | } 148 | 149 | T& get() 150 | { 151 | return *d_thing; 152 | } 153 | 154 | ThingPool* d_parent; 155 | T* d_thing; 156 | }; 157 | 158 | Lease getLease() 159 | { 160 | std::lock_guard lck(d_lock); 161 | if(d_pool.empty()) { 162 | d_pool.push_back(d_maker()); 163 | // cout<<"Created new thing "<<(void*)d_pool.front()< d_maxout) 169 | d_maxout = (unsigned int)d_out; 170 | return l; 171 | } 172 | }; 173 | --------------------------------------------------------------------------------