fb-cpp 0.0.2
A modern C++ wrapper for the Firebird database API
Loading...
Searching...
No Matches
AttachmentPool.cpp
1/*
2 * MIT License
3 *
4 * Copyright (c) 2026 Adriano dos Santos Fernandes
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25#include "AttachmentPool.h"
26#include "Client.h"
27#include "Exception.h"
28#include <algorithm>
29#include <cassert>
30#include <exception>
31#include <string_view>
32#include <utility>
33
34using namespace fbcpp;
35using namespace fbcpp::impl;
36
37
38static std::chrono::steady_clock::time_point steadyNow()
39{
40 return std::chrono::steady_clock::now();
41}
42
43static std::unique_ptr<AttachmentPoolEntry> extractEntry(
44 std::vector<std::unique_ptr<AttachmentPoolEntry>>& entries, AttachmentPoolEntry* entry)
45{
46 const auto it = std::find_if(
47 entries.begin(), entries.end(), [entry](const auto& candidate) { return candidate.get() == entry; });
48
49 if (it == entries.end())
50 return nullptr;
51
52 auto owned = std::move(*it);
53 entries.erase(it);
54
55 return owned;
56}
57
58
59AttachmentPool::AttachmentPool(Client& client_, std::string uri_, const AttachmentPoolOptions& options)
60 : client{&client_},
61 uri{std::move(uri_)},
62 options{options}
63{
65 {
66 throw FbCppException(
67 "AttachmentPoolOptions::setAttachmentOptions must not enable AttachmentOptions::setCreateDatabase");
68 }
69
70 if (options.getMaxSize() < 1u)
71 throw FbCppException("AttachmentPoolOptions::setMaxSize must be at least 1");
72
73 if (options.getMinSize() > options.getMaxSize())
74 throw FbCppException("AttachmentPoolOptions::setMinSize must not be greater than setMaxSize");
75
76 for (std::size_t i = 0u; i < options.getMinSize(); ++i)
77 {
78 auto entry = std::make_unique<AttachmentPoolEntry>();
79 entry->attachment = std::make_unique<Attachment>(*client, uri, options.getAttachmentOptions());
80 entry->createdAt = entry->lastReturnedAt = steadyNow();
81
82 available.push_back(entry.get());
83 entries.push_back(std::move(entry));
84 }
85}
86
88{
89 assert(inUse == 0u && pending == 0u);
90
91 try
92 {
93 close();
94 }
95 catch (...)
96 {
97 // swallow
98 }
99}
100
102{
103 std::lock_guard mutexGuard{mutex};
104 return entries.size();
105}
106
108{
109 std::lock_guard mutexGuard{mutex};
110 return available.size();
111}
112
114{
115 std::lock_guard mutexGuard{mutex};
116 return inUse;
117}
118
120{
121 auto lease = acquireImpl(true);
122 assert(lease.has_value());
123 return std::move(*lease);
124}
125
126std::optional<PooledAttachment> AttachmentPool::tryAcquire()
127{
128 return acquireImpl(false);
129}
130
132{
133 std::unique_lock mutexGuard{mutex};
134 closed = true;
135
136 while (!available.empty())
137 {
138 auto* entry = available.front();
139 available.pop_front();
140
141 auto owned = extractEntry(entries, entry);
142
143 mutexGuard.unlock();
144 owned.reset(); // Disconnects (network I/O) outside the lock.
145 mutexGuard.lock();
146 }
147}
148
149std::optional<PooledAttachment> AttachmentPool::acquireImpl(bool throwOnFailure)
150{
151 std::unique_lock mutexGuard{mutex};
152
153 if (closed)
154 {
155 if (throwOnFailure)
156 throw FbCppException("AttachmentPool is closed");
157
158 return std::nullopt;
159 }
160
161 const auto deadline = steadyNow() + options.getAcquireTimeout();
162
163 while (true)
164 {
165 evictLocked(mutexGuard);
166
167 if (!available.empty())
168 {
169 auto* entry = available.front();
170 available.pop_front();
171 ++inUse;
172
173 if (options.getValidateOnAcquire())
174 {
175 mutexGuard.unlock();
176
177 bool valid = true;
178
179 try
180 {
181 entry->attachment->ping();
182 }
183 catch (...)
184 {
185 valid = false;
186 }
187
188 mutexGuard.lock();
189
190 if (!valid)
191 {
192 --inUse;
193 auto owned = extractEntry(entries, entry);
194
195 mutexGuard.unlock();
196 owned.reset(); // Disconnects (network I/O) outside the lock.
197 mutexGuard.lock();
198
199 continue;
200 }
201 }
202
203 return PooledAttachment{*this, *entry};
204 }
205
206 if (entries.size() + pending < options.getMaxSize())
207 {
208 ++pending;
209 mutexGuard.unlock();
210
211 std::unique_ptr<AttachmentPoolEntry> newEntry;
212 std::exception_ptr failure;
213
214 try
215 {
216 newEntry = std::make_unique<AttachmentPoolEntry>();
217 newEntry->attachment = std::make_unique<Attachment>(*client, uri, options.getAttachmentOptions());
218 newEntry->createdAt = newEntry->lastReturnedAt = steadyNow();
219 }
220 catch (...)
221 {
222 failure = std::current_exception();
223 }
224
225 mutexGuard.lock();
226 --pending;
227
228 if (failure)
229 {
230 availableCondition.notify_one(); // Let another waiter try instead.
231
232 if (throwOnFailure)
233 std::rethrow_exception(failure);
234
235 return std::nullopt;
236 }
237
238 auto* entryPtr = newEntry.get();
239 entries.push_back(std::move(newEntry));
240 ++inUse;
241
242 return PooledAttachment{*this, *entryPtr};
243 }
244
245 const auto remaining = deadline - steadyNow();
246
247 if (remaining <= std::chrono::steady_clock::duration::zero() ||
248 availableCondition.wait_for(mutexGuard, remaining) == std::cv_status::timeout)
249 {
250 if (throwOnFailure)
251 throw FbCppException("Timed out waiting for an available connection from AttachmentPool");
252
253 return std::nullopt;
254 }
255 }
256}
257
258void AttachmentPool::evictLocked(std::unique_lock<std::mutex>& lock)
259{
260 assert(lock.owns_lock());
261
262 const auto currentTime = steadyNow();
263 std::vector<std::unique_ptr<AttachmentPoolEntry>> toDestroy;
264
265 for (auto it = available.begin(); it != available.end();)
266 {
267 auto* entry = *it;
268 bool evict = false;
269
270 if (options.getMaxLifetime() && currentTime - entry->createdAt >= *options.getMaxLifetime())
271 evict = true;
272 else if (options.getIdleTimeout() && entries.size() > options.getMinSize() &&
273 currentTime - entry->lastReturnedAt >= *options.getIdleTimeout())
274 evict = true;
275
276 if (evict)
277 {
278 it = available.erase(it);
279
280 if (auto owned = extractEntry(entries, entry))
281 toDestroy.push_back(std::move(owned));
282 }
283 else
284 ++it;
285 }
286
287 if (!toDestroy.empty())
288 {
289 lock.unlock();
290 toDestroy.clear(); // Disconnects (network I/O) outside the lock.
291 lock.lock();
292 }
293}
294
295void AttachmentPool::release(AttachmentPoolEntry& entry) noexcept
296{
297 try
298 {
299 bool resetFailed = false;
300
301 if (!closed && options.getSessionResetOnRelease() && entry.attachment && entry.attachment->isValid())
302 {
303 try
304 {
305 entry.attachment->resetSession();
306 }
307 catch (...)
308 {
309 resetFailed = true; // Force this connection to be discarded below.
310 }
311 }
312
313 std::unique_ptr<AttachmentPoolEntry> toDestroy;
314
315 { // scope
316 std::lock_guard mutexGuard{mutex};
317
318 assert(inUse > 0u);
319 --inUse;
320
321 const bool discard = closed || !entry.attachment || !entry.attachment->isValid() || resetFailed;
322
323 if (discard)
324 toDestroy = extractEntry(entries, &entry);
325 else
326 {
327 entry.lastReturnedAt = steadyNow();
328 available.push_back(&entry);
329 }
330 }
331
332 availableCondition.notify_one();
333 // toDestroy (if any) disconnects here, outside the lock.
334 }
335 catch (...)
336 {
337 // swallow
338 }
339}
bool getCreateDatabase() const
Returns whether the database should be created instead of connected to.
Definition Attachment.h:170
Represents options used when creating an AttachmentPool object.
const std::optional< std::chrono::milliseconds > & getIdleTimeout() const
Returns the maximum time a connection may stay idle in the pool before being closed.
std::size_t getMaxSize() const
Returns the maximum number of connections the pool may create.
bool getValidateOnAcquire() const
Returns whether a connection is validated (via ping) before being handed out by acquire().
const std::optional< std::chrono::milliseconds > & getMaxLifetime() const
Returns the maximum lifetime of a connection, regardless of usage.
std::chrono::milliseconds getAcquireTimeout() const
Returns how long acquire() waits for a connection to become available before throwing.
const AttachmentOptions & getAttachmentOptions() const
Returns the options used to create each pooled Attachment.
std::size_t getMinSize() const
Returns the minimum number of connections kept warm by the pool.
std::size_t inUseCount()
Returns the number of connections currently leased out by the pool.
PooledAttachment acquire()
Acquires a connection from the pool, creating one if needed and allowed by getMaxSize().
void close()
Closes every available connection and marks the pool as closed.
std::size_t availableCount()
Returns the number of connections currently available (not leased) in the pool.
~AttachmentPool() noexcept
Closes every idle connection in the pool.
std::size_t size()
Returns the total number of connections currently created by the pool (in use plus available).
std::optional< PooledAttachment > tryAcquire()
Attempts to acquire a connection from the pool without throwing.
AttachmentPool(Client &client, std::string uri, const AttachmentPoolOptions &options={})
Constructs an AttachmentPool that connects to the database specified by the URI using the specified C...
Represents a Firebird client library instance.
Definition Client.h:53
Base exception class for all fb-cpp exceptions.
Definition Exception.h:230
RAII lease for an Attachment borrowed from an AttachmentPool.
fb-cpp namespace.
Definition Attachment.h:45