Skip to content

Commit f5e9c7c

Browse files
committed
Add AttachmentPool
1 parent 8522727 commit f5e9c7c

4 files changed

Lines changed: 977 additions & 0 deletions

File tree

src/fb-cpp/AttachmentPool.cpp

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
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 (options.getAttachmentOptions().getCreateDatabase())
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+
87+
AttachmentPool::~AttachmentPool() noexcept
88+
{
89+
assert(inUse == 0u && pending == 0u);
90+
91+
try
92+
{
93+
close();
94+
}
95+
catch (...)
96+
{
97+
// swallow
98+
}
99+
}
100+
101+
std::size_t AttachmentPool::size()
102+
{
103+
std::lock_guard mutexGuard{mutex};
104+
return entries.size();
105+
}
106+
107+
std::size_t AttachmentPool::availableCount()
108+
{
109+
std::lock_guard mutexGuard{mutex};
110+
return available.size();
111+
}
112+
113+
std::size_t AttachmentPool::inUseCount()
114+
{
115+
std::lock_guard mutexGuard{mutex};
116+
return inUse;
117+
}
118+
119+
PooledAttachment AttachmentPool::acquire()
120+
{
121+
auto lease = acquireImpl(true);
122+
assert(lease.has_value());
123+
return std::move(*lease);
124+
}
125+
126+
std::optional<PooledAttachment> AttachmentPool::tryAcquire()
127+
{
128+
return acquireImpl(false);
129+
}
130+
131+
void AttachmentPool::close()
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+
149+
std::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+
StatusWrapper statusWrapper{*client};
182+
// FIXME: Add ping method to Attachment class and use it instead of accessing the handle directly.
183+
entry->attachment->getHandle()->ping(&statusWrapper);
184+
}
185+
catch (...)
186+
{
187+
valid = false;
188+
}
189+
190+
mutexGuard.lock();
191+
192+
if (!valid)
193+
{
194+
--inUse;
195+
auto owned = extractEntry(entries, entry);
196+
197+
mutexGuard.unlock();
198+
owned.reset(); // Disconnects (network I/O) outside the lock.
199+
mutexGuard.lock();
200+
201+
continue;
202+
}
203+
}
204+
205+
return PooledAttachment{*this, *entry};
206+
}
207+
208+
if (entries.size() + pending < options.getMaxSize())
209+
{
210+
++pending;
211+
mutexGuard.unlock();
212+
213+
std::unique_ptr<AttachmentPoolEntry> newEntry;
214+
std::exception_ptr failure;
215+
216+
try
217+
{
218+
newEntry = std::make_unique<AttachmentPoolEntry>();
219+
newEntry->attachment = std::make_unique<Attachment>(*client, uri, options.getAttachmentOptions());
220+
newEntry->createdAt = newEntry->lastReturnedAt = steadyNow();
221+
}
222+
catch (...)
223+
{
224+
failure = std::current_exception();
225+
}
226+
227+
mutexGuard.lock();
228+
--pending;
229+
230+
if (failure)
231+
{
232+
availableCondition.notify_one(); // Let another waiter try instead.
233+
234+
if (throwOnFailure)
235+
std::rethrow_exception(failure);
236+
237+
return std::nullopt;
238+
}
239+
240+
auto* entryPtr = newEntry.get();
241+
entries.push_back(std::move(newEntry));
242+
++inUse;
243+
244+
return PooledAttachment{*this, *entryPtr};
245+
}
246+
247+
const auto remaining = deadline - steadyNow();
248+
249+
if (remaining <= std::chrono::steady_clock::duration::zero() ||
250+
availableCondition.wait_for(mutexGuard, remaining) == std::cv_status::timeout)
251+
{
252+
if (throwOnFailure)
253+
throw FbCppException("Timed out waiting for an available connection from AttachmentPool");
254+
255+
return std::nullopt;
256+
}
257+
}
258+
}
259+
260+
void AttachmentPool::evictLocked(std::unique_lock<std::mutex>& lock)
261+
{
262+
assert(lock.owns_lock());
263+
264+
const auto currentTime = steadyNow();
265+
std::vector<std::unique_ptr<AttachmentPoolEntry>> toDestroy;
266+
267+
for (auto it = available.begin(); it != available.end();)
268+
{
269+
auto* entry = *it;
270+
bool evict = false;
271+
272+
if (options.getMaxLifetime() && currentTime - entry->createdAt >= *options.getMaxLifetime())
273+
evict = true;
274+
else if (options.getIdleTimeout() && entries.size() > options.getMinSize() &&
275+
currentTime - entry->lastReturnedAt >= *options.getIdleTimeout())
276+
evict = true;
277+
278+
if (evict)
279+
{
280+
it = available.erase(it);
281+
282+
if (auto owned = extractEntry(entries, entry))
283+
toDestroy.push_back(std::move(owned));
284+
}
285+
else
286+
++it;
287+
}
288+
289+
if (!toDestroy.empty())
290+
{
291+
lock.unlock();
292+
toDestroy.clear(); // Disconnects (network I/O) outside the lock.
293+
lock.lock();
294+
}
295+
}
296+
297+
void AttachmentPool::release(AttachmentPoolEntry& entry) noexcept
298+
{
299+
try
300+
{
301+
bool resetFailed = false;
302+
303+
if (!closed && options.getSessionResetOnRelease() && entry.attachment && entry.attachment->isValid())
304+
{
305+
try
306+
{
307+
StatusWrapper statusWrapper{*client};
308+
static constexpr std::string_view resetSql = "ALTER SESSION RESET";
309+
// FIXME: Add a session-reset method to Attachment class and use it instead of the handle.
310+
entry.attachment->getHandle()->execute(&statusWrapper, nullptr,
311+
static_cast<unsigned>(resetSql.length()), resetSql.data(), SQL_DIALECT_V6, nullptr, nullptr,
312+
nullptr, nullptr);
313+
}
314+
catch (...)
315+
{
316+
resetFailed = true; // Force this connection to be discarded below.
317+
}
318+
}
319+
320+
std::unique_ptr<AttachmentPoolEntry> toDestroy;
321+
322+
{ // scope
323+
std::lock_guard mutexGuard{mutex};
324+
325+
assert(inUse > 0u);
326+
--inUse;
327+
328+
const bool discard = closed || !entry.attachment || !entry.attachment->isValid() || resetFailed;
329+
330+
if (discard)
331+
toDestroy = extractEntry(entries, &entry);
332+
else
333+
{
334+
entry.lastReturnedAt = steadyNow();
335+
available.push_back(&entry);
336+
}
337+
}
338+
339+
availableCondition.notify_one();
340+
// toDestroy (if any) disconnects here, outside the lock.
341+
}
342+
catch (...)
343+
{
344+
// swallow
345+
}
346+
}

0 commit comments

Comments
 (0)