Skip to content

Commit b140d4d

Browse files
committed
Add AttachmentPool
1 parent 8522727 commit b140d4d

4 files changed

Lines changed: 978 additions & 0 deletions

File tree

src/fb-cpp/AttachmentPool.cpp

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
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+
34+
using namespace fbcpp;
35+
using namespace fbcpp::impl;
36+
37+
38+
static std::chrono::steady_clock::time_point steadyNow()
39+
{
40+
return std::chrono::steady_clock::now();
41+
}
42+
43+
static 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+
59+
AttachmentPool::AttachmentPool(Client& client, std::string uri, const AttachmentPoolOptions& options)
60+
: client{&client},
61+
uri{std::move(uri)},
62+
options{options}
63+
{
64+
if (this->options.getAttachmentOptions().getCreateDatabase())
65+
{
66+
throw FbCppException(
67+
"AttachmentPoolOptions::setAttachmentOptions must not enable AttachmentOptions::setCreateDatabase");
68+
}
69+
70+
if (this->options.getMaxSize() < 1u)
71+
throw FbCppException("AttachmentPoolOptions::setMaxSize must be at least 1");
72+
73+
if (this->options.getMinSize() > this->options.getMaxSize())
74+
throw FbCppException("AttachmentPoolOptions::setMinSize must not be greater than setMaxSize");
75+
76+
for (std::size_t i = 0u; i < this->options.getMinSize(); ++i)
77+
{
78+
auto entry = std::make_unique<AttachmentPoolEntry>();
79+
entry->attachment =
80+
std::make_unique<Attachment>(*this->client, this->uri, this->options.getAttachmentOptions());
81+
entry->createdAt = entry->lastReturnedAt = steadyNow();
82+
83+
available.push_back(entry.get());
84+
entries.push_back(std::move(entry));
85+
}
86+
}
87+
88+
AttachmentPool::~AttachmentPool() noexcept
89+
{
90+
assert(inUse == 0u && pending == 0u);
91+
92+
try
93+
{
94+
close();
95+
}
96+
catch (...)
97+
{
98+
// swallow
99+
}
100+
}
101+
102+
std::size_t AttachmentPool::size()
103+
{
104+
std::lock_guard mutexGuard{mutex};
105+
return entries.size();
106+
}
107+
108+
std::size_t AttachmentPool::availableCount()
109+
{
110+
std::lock_guard mutexGuard{mutex};
111+
return available.size();
112+
}
113+
114+
std::size_t AttachmentPool::inUseCount()
115+
{
116+
std::lock_guard mutexGuard{mutex};
117+
return inUse;
118+
}
119+
120+
PooledAttachment AttachmentPool::acquire()
121+
{
122+
auto lease = acquireImpl(true);
123+
assert(lease.has_value());
124+
return std::move(*lease);
125+
}
126+
127+
std::optional<PooledAttachment> AttachmentPool::tryAcquire()
128+
{
129+
return acquireImpl(false);
130+
}
131+
132+
void AttachmentPool::close()
133+
{
134+
std::unique_lock mutexGuard{mutex};
135+
closed = true;
136+
137+
while (!available.empty())
138+
{
139+
auto* entry = available.front();
140+
available.pop_front();
141+
142+
auto owned = extractEntry(entries, entry);
143+
144+
mutexGuard.unlock();
145+
owned.reset(); // Disconnects (network I/O) outside the lock.
146+
mutexGuard.lock();
147+
}
148+
}
149+
150+
std::optional<PooledAttachment> AttachmentPool::acquireImpl(bool throwOnFailure)
151+
{
152+
std::unique_lock mutexGuard{mutex};
153+
154+
if (closed)
155+
{
156+
if (throwOnFailure)
157+
throw FbCppException("AttachmentPool is closed");
158+
159+
return std::nullopt;
160+
}
161+
162+
const auto deadline = steadyNow() + options.getAcquireTimeout();
163+
164+
while (true)
165+
{
166+
evictLocked(mutexGuard);
167+
168+
if (!available.empty())
169+
{
170+
auto* entry = available.front();
171+
available.pop_front();
172+
++inUse;
173+
174+
if (options.getValidateOnAcquire())
175+
{
176+
mutexGuard.unlock();
177+
178+
bool valid = true;
179+
180+
try
181+
{
182+
StatusWrapper statusWrapper{*client};
183+
// FIXME: Add ping method to Attachment class and use it instead of accessing the handle directly.
184+
entry->attachment->getHandle()->ping(&statusWrapper);
185+
}
186+
catch (...)
187+
{
188+
valid = false;
189+
}
190+
191+
mutexGuard.lock();
192+
193+
if (!valid)
194+
{
195+
--inUse;
196+
auto owned = extractEntry(entries, entry);
197+
198+
mutexGuard.unlock();
199+
owned.reset(); // Disconnects (network I/O) outside the lock.
200+
mutexGuard.lock();
201+
202+
continue;
203+
}
204+
}
205+
206+
return PooledAttachment{*this, *entry};
207+
}
208+
209+
if (entries.size() + pending < options.getMaxSize())
210+
{
211+
++pending;
212+
mutexGuard.unlock();
213+
214+
std::unique_ptr<AttachmentPoolEntry> newEntry;
215+
std::exception_ptr failure;
216+
217+
try
218+
{
219+
newEntry = std::make_unique<AttachmentPoolEntry>();
220+
newEntry->attachment = std::make_unique<Attachment>(*client, uri, options.getAttachmentOptions());
221+
newEntry->createdAt = newEntry->lastReturnedAt = steadyNow();
222+
}
223+
catch (...)
224+
{
225+
failure = std::current_exception();
226+
}
227+
228+
mutexGuard.lock();
229+
--pending;
230+
231+
if (failure)
232+
{
233+
availableCondition.notify_one(); // Let another waiter try instead.
234+
235+
if (throwOnFailure)
236+
std::rethrow_exception(failure);
237+
238+
return std::nullopt;
239+
}
240+
241+
auto* entryPtr = newEntry.get();
242+
entries.push_back(std::move(newEntry));
243+
++inUse;
244+
245+
return PooledAttachment{*this, *entryPtr};
246+
}
247+
248+
const auto remaining = deadline - steadyNow();
249+
250+
if (remaining <= std::chrono::steady_clock::duration::zero() ||
251+
availableCondition.wait_for(mutexGuard, remaining) == std::cv_status::timeout)
252+
{
253+
if (throwOnFailure)
254+
throw FbCppException("Timed out waiting for an available connection from AttachmentPool");
255+
256+
return std::nullopt;
257+
}
258+
}
259+
}
260+
261+
void AttachmentPool::evictLocked(std::unique_lock<std::mutex>& lock)
262+
{
263+
assert(lock.owns_lock());
264+
265+
const auto currentTime = steadyNow();
266+
std::vector<std::unique_ptr<AttachmentPoolEntry>> toDestroy;
267+
268+
for (auto it = available.begin(); it != available.end();)
269+
{
270+
auto* entry = *it;
271+
bool evict = false;
272+
273+
if (options.getMaxLifetime() && currentTime - entry->createdAt >= *options.getMaxLifetime())
274+
evict = true;
275+
else if (options.getIdleTimeout() && entries.size() > options.getMinSize() &&
276+
currentTime - entry->lastReturnedAt >= *options.getIdleTimeout())
277+
evict = true;
278+
279+
if (evict)
280+
{
281+
it = available.erase(it);
282+
283+
if (auto owned = extractEntry(entries, entry))
284+
toDestroy.push_back(std::move(owned));
285+
}
286+
else
287+
++it;
288+
}
289+
290+
if (!toDestroy.empty())
291+
{
292+
lock.unlock();
293+
toDestroy.clear(); // Disconnects (network I/O) outside the lock.
294+
lock.lock();
295+
}
296+
}
297+
298+
void AttachmentPool::release(AttachmentPoolEntry& entry) noexcept
299+
{
300+
try
301+
{
302+
bool resetFailed = false;
303+
304+
if (!closed && options.getSessionResetOnRelease() && entry.attachment && entry.attachment->isValid())
305+
{
306+
try
307+
{
308+
StatusWrapper statusWrapper{*client};
309+
static constexpr std::string_view resetSql = "ALTER SESSION RESET";
310+
// FIXME: Add a session-reset method to Attachment class and use it instead of the handle.
311+
entry.attachment->getHandle()->execute(&statusWrapper, nullptr,
312+
static_cast<unsigned>(resetSql.length()), resetSql.data(), SQL_DIALECT_V6, nullptr, nullptr,
313+
nullptr, nullptr);
314+
}
315+
catch (...)
316+
{
317+
resetFailed = true; // Force this connection to be discarded below.
318+
}
319+
}
320+
321+
std::unique_ptr<AttachmentPoolEntry> toDestroy;
322+
323+
{ // scope
324+
std::lock_guard mutexGuard{mutex};
325+
326+
assert(inUse > 0u);
327+
--inUse;
328+
329+
const bool discard = closed || !entry.attachment || !entry.attachment->isValid() || resetFailed;
330+
331+
if (discard)
332+
toDestroy = extractEntry(entries, &entry);
333+
else
334+
{
335+
entry.lastReturnedAt = steadyNow();
336+
available.push_back(&entry);
337+
}
338+
}
339+
340+
availableCondition.notify_one();
341+
// toDestroy (if any) disconnects here, outside the lock.
342+
}
343+
catch (...)
344+
{
345+
// swallow
346+
}
347+
}

0 commit comments

Comments
 (0)