Skip to content

Commit 70227d0

Browse files
committed
Add AttachmentPool
1 parent bfbb322 commit 70227d0

4 files changed

Lines changed: 970 additions & 0 deletions

File tree

src/fb-cpp/AttachmentPool.cpp

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
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+
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+
258+
void 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+
295+
void 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+
}

0 commit comments

Comments
 (0)