Lightweight 0.20260617.0
Loading...
Searching...
No Matches
Pool.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../Async/Executor.hpp"
5#include "../Async/Task.hpp"
6#include "../SqlLogger.hpp"
7#include "DataMapper.hpp"
8
9#include <cassert>
10#include <condition_variable>
11#include <coroutine>
12#include <cstdint>
13#include <deque>
14#include <memory>
15#include <mutex>
16#include <stdexcept>
17#include <vector>
18
19namespace Lightweight
20{
21
22/// Enum to define growth strategies of the pool
23///
24enum class GrowthStrategy : uint8_t
25{
26 /// Pre-create initialSize objects. Allow the total count
27 /// to grow up to maxSize. Once maxSize objects exist, callers BLOCK
28 /// until one is returned.
29 BoundedWait,
30
31 /// Pre-create initialSize objects. The pool stores up to maxSize
32 /// objects. If none are idle, a fresh object is ALWAYS created (no
33 /// waiting). On return: kept if the idle set is below maxSize, otherwise
34 /// destroyed.
35 BoundedOverflow,
36
37 /// Pre-create initialSize objects. Grow without limit. Every returned
38 /// object is always kept in the pool.
39 UnboundedGrow,
40};
41
42/// Structure to hold the configuration of the pool, including the initial size, maximum size and growth strategy.
43/// Structure is used as a template parameter for the Pool class to configure its behavior at compile time.
45{
46 /// Initial number of data mappers to pre-create and store in the pool, must be less than or equal to maxSize
47 size_t initialSize {};
48 /// Maximum number of data mappers that can exist in the pool, must be greater than or equal to initialSize
49 /// this is used for the Bounded* strategies to determine when to block or when to stop accepting returned data mappers,
50 /// for the UnboundedGrow strategy this is ignored
51 size_t maxSize {};
52 /// Strategy to determine how the pool should grow when there are no idle data mappers available, default is BoundedWait
53 /// which blocks until a data mapper is returned to the pool
54 GrowthStrategy growthStrategy { GrowthStrategy::BoundedWait };
55};
56
57/// A thread-safe pool of DataMapper instances with the policy configured by the PoolConfig template parameter.
58/// The pool allows acquiring and returning DataMapper instances, and manages the lifecycle of these instances according to
59/// the specified growth strategy.
60template <PoolConfig Config>
61class Pool
62{
63 public:
64 /// A wrapper around a DataMapper that returns it to the pool when destroyed
65 /// can be created only from the Pool and is move-only to ensure it is always
66 /// returned to the pool when it goes out of scope
68 {
69 private:
70 friend class Pool;
71
72 explicit PooledDataMapper(Pool& pool, std::unique_ptr<DataMapper> dm) noexcept:
73 _dm { std::move(dm) },
74 _pool { pool }
75 {
76 }
77
78 public:
79 PooledDataMapper() = delete;
80 PooledDataMapper(PooledDataMapper const&) = delete;
81
82 /// Move constructor for the pooled data mapper, the only public
83 /// constructor, allows moving the pooled data mapper but not copying it
85 _dm { std::move(other._dm) },
86 _pool { other._pool }
87 {
88 }
89 PooledDataMapper& operator=(PooledDataMapper const&) = delete;
90 PooledDataMapper& operator=(PooledDataMapper&&) = delete;
91 ~PooledDataMapper() noexcept
92 {
93 if (_dm)
94 ReturnToPool();
95 }
96
97 /// Access the underlying data mapper via pointer semantics
98 DataMapper* operator->() const noexcept
99 {
100 return _dm.get();
101 }
102
103 /// Access the underlying data mapper via reference semantics
104 /// This is useful for passing the pooled data mapper to functions
105 /// that expect a DataMapper reference
106 [[nodiscard]] DataMapper& Get() const noexcept
107 {
108 return *_dm;
109 }
110
111 private:
112 void ReturnToPool() noexcept
113 {
114 _pool.Return(std::move(_dm));
115 _dm = nullptr;
116 }
117
118 std::unique_ptr<DataMapper> _dm;
119 Pool& _pool;
120 };
121
122 private:
123 struct WaiterNode; // defined below; referenced by ReturnLocked's signature.
124
125 /// Detaches the async backend from a returned mapper's connection before it is idled or handed
126 /// off, so a recycled connection never carries references to executors that may since have been
127 /// destroyed (the next @c AcquireAsync re-enables it fresh). Shared by every @c Return overload.
128 ///
129 /// @warning The caller must not return a mapper that still has an async operation in flight on it:
130 /// dropping the backend destroys the strand/executors an outstanding offloaded step references and
131 /// races the worker still touching the ODBC handle. Await every async op before returning.
132 /// @param dm The mapper whose connection's async backend is dropped.
133 static void DropAsyncBackend(DataMapper& dm) noexcept
134 {
135 dm.Connection().DisableAsync();
136 }
137
138 /// always return the data mapper to the pool for this strategy
139 void Return(std::unique_ptr<DataMapper> dm) noexcept
140 requires(Config.growthStrategy == GrowthStrategy::UnboundedGrow)
141 {
142 DropAsyncBackend(*dm);
143 std::scoped_lock lock(_mutex);
144 _idleDataMappers.push_back(std::move(dm));
145 }
146
147 /// for bounded wait strategy, return the data mapper to the pool: hand it to the next FIFO waiter
148 /// (sync or async) or idle it.
149 void Return(std::unique_ptr<DataMapper> dm) noexcept
150 requires(Config.growthStrategy == GrowthStrategy::BoundedWait)
151 {
152 DropAsyncBackend(*dm);
153 std::shared_ptr<WaiterNode> toResume;
154 {
155 std::scoped_lock const lock(_mutex);
156 toResume = ReturnLocked(std::move(dm));
157 }
158 // Resume outside the lock to avoid re-entrancy (the resumed coroutine may call back into the pool).
159 if (toResume)
160 toResume->resume->Resume(toResume->handle);
161 }
162
163 /// Hands @p dm to the next FIFO waiter (transferring the checked-out count) or idles it. Serving
164 /// @c _waiters in arrival order keeps sync @ref Acquire and async @ref AcquireAsync waiters fair.
165 ///
166 /// @pre @c _mutex is held by the caller.
167 /// @param dm The mapper to return; its async backend must already be disabled.
168 /// @return The async waiter node handed the mapper, to be resumed by the caller after releasing
169 /// @c _mutex; @c nullptr if a sync waiter was woken in place or the mapper was idled.
170 std::shared_ptr<WaiterNode> ReturnLocked(std::unique_ptr<DataMapper> dm) noexcept
171 requires(Config.growthStrategy == GrowthStrategy::BoundedWait)
172 {
173 while (!_waiters.empty())
174 {
175 auto node = _waiters.front();
176 _waiters.pop_front();
177 // _waiters only ever holds parked nodes (an async awaitable de-registers itself on
178 // abandonment; a synchronous waiter is never abandoned), but guard defensively.
179 if (node->state != WaiterNode::State::Parked)
180 continue;
181 node->state = WaiterNode::State::Fulfilled;
182 node->mapper = std::move(dm); // hand off ownership; _checkedOut stays (transferred)
183 if (node->kind == WaiterNode::Kind::Async)
184 return node; // resumed by the caller outside the lock
185 node->cv.notify_one(); // wake the blocked Acquire(); it consumes node->mapper
186 return nullptr;
187 }
188 _idleDataMappers.push_back(std::move(dm));
189 --_checkedOut;
190 return nullptr;
191 }
192
193 /// for bounded overflow strategy, only return to pool if we have capacity, otherwise just destroy the data mapper
194 void Return(std::unique_ptr<DataMapper> dm) noexcept
195 requires(Config.growthStrategy == GrowthStrategy::BoundedOverflow)
196 {
197 DropAsyncBackend(*dm);
198 std::scoped_lock lock(_mutex);
199 if (_idleDataMappers.size() < Config.maxSize)
200 _idleDataMappers.push_back(std::move(dm));
201 }
202
203 public:
204 /// Default constructor that pre-creates the initial number of data mappers and stores them in the pool
205 /// No other constructors are provided, as the pool is configured at compile time via the template parameter
206 explicit Pool()
207 {
208 _idleDataMappers.reserve(Config.initialSize);
209 for ([[maybe_unused]] auto const _: std::views::iota(0U, Config.initialSize))
210 _idleDataMappers.push_back(std::make_unique<DataMapper>());
211 }
212
213 /// Destructor. The pool manages the lifecycle of the idle data mappers; be aware that any
214 /// acquired data mappers not returned to the pool are destroyed when the pool is destroyed,
215 /// which may leak resources if not handled properly.
216 ~Pool() noexcept
217 {
218 // A parked AcquireAsync coroutine (or a thread blocked in Acquire) holds a reference back to this
219 // pool, so destroying the pool out from under it is undefined: drive every AcquireAsync task to
220 // completion and let every blocked Acquire() return first. The assert catches this in debug; the
221 // warning surfaces it in release (where the later access would be a use-after-free).
222 if (!_waiters.empty())
224 "Pool destroyed while acquirers are still waiting on it (coroutines parked in AcquireAsync "
225 "and/or threads blocked in Acquire); the pool must outlive every acquirer (drive each "
226 "AcquireAsync task to completion or destroy it first, and never destroy the pool while a "
227 "thread is blocked in Acquire). This is undefined behavior.");
228 assert(_waiters.empty() && "Pool destroyed while acquirers are still waiting on it");
229 }
230
231 Pool(Pool const&) = delete;
232 Pool& operator=(Pool const&) = delete;
233 Pool(Pool&&) = delete;
234 Pool& operator=(Pool&&) = delete;
235
236 /// Function to acquire a data mapper from the pool, the behavior of this function depends on the growth strategy
237 /// this is a specific implementation for the BoundedWait strategy, which blocks until a data mapper is available if the
238 /// pool is at maximum capacity
240 requires(Config.growthStrategy == GrowthStrategy::BoundedWait)
241 {
242 std::unique_lock lock(_mutex);
243 if (!_idleDataMappers.empty())
244 {
245 // get a data mapper from the pool
246 auto dm = std::move(_idleDataMappers.back());
247 _idleDataMappers.pop_back();
248 ++_checkedOut;
249 return PooledDataMapper(*this, std::move(dm));
250 }
251 if (_checkedOut < Config.maxSize)
252 {
253 // below capacity: create a fresh data mapper
254 ++_checkedOut;
255 return PooledDataMapper(*this, std::make_unique<DataMapper>());
256 }
257
258 // Pool exhausted: park as a FIFO waiter (fair with AcquireAsync waiters) and block until a
259 // mapper is handed to this node. The hand-off transfers a checked-out slot, so no ++_checkedOut.
260 auto node = std::make_shared<WaiterNode>(WaiterNode::Kind::Sync);
261 _waiters.push_back(node);
262 node->cv.wait(lock, [&node] { return node->state == WaiterNode::State::Fulfilled; });
263 return PooledDataMapper(*this, std::move(node->mapper));
264 }
265
266 /// Function to acquire a data mapper from the pool, the behavior of this function depends on the growth strategy
267 /// this is a specific implementation for the strategies that do not block, which always creates a new data mapper if
268 /// the pool is empty, regardless of the maximum capacity
270 requires(Config.growthStrategy != GrowthStrategy::BoundedWait)
271 {
272 std::scoped_lock lock(_mutex);
273 if (_idleDataMappers.empty())
274 {
275 // create a new data mapper and return it
276 return PooledDataMapper(*this, std::make_unique<DataMapper>());
277 }
278
279 // get a data mapper from the pool
280 auto dm = std::move(_idleDataMappers.back());
281 _idleDataMappers.pop_back();
282 return PooledDataMapper(*this, std::move(dm));
283 }
284
285 /// Asynchronously acquires a DataMapper from the pool without blocking the calling thread.
286 ///
287 /// If the pool is exhausted (BoundedWait at capacity), the awaiting coroutine is suspended and
288 /// resumed — via @p resume — when a mapper is returned, rather than parking a thread. The acquired
289 /// mapper's connection is wired for async via SqlConnection::EnableAsync(@p dbWorkers, @p resume),
290 /// so the caller can immediately co_await its async methods.
291 ///
292 /// @param dbWorkers The worker pool used to run the acquired mapper's blocking ODBC calls.
293 /// @param resume The scheduler used to resume coroutines (typically the app run loop).
294 /// @return A Task yielding a pooled DataMapper.
296 {
297 // Forward to a coroutine taking pointers (coroutines must not take reference parameters).
298 return AcquireAsyncImpl(&dbWorkers, &resume);
299 }
300
301 /// Configures the executors that the no-argument @ref AcquireAsync() overload wires acquired
302 /// mappers for, so async consumers of this pool no longer repeat the executors at every call.
303 ///
304 /// This is opt-in and scoped to the pool: only pools configured this way hand out async-enabled
305 /// mappers via the no-arg overload; synchronous @ref Acquire and connections outside the pool are
306 /// unaffected. Unlike a process-global default, the executors' lifetime is tied to this pool,
307 /// which already must outlive every acquirer.
308 ///
309 /// @warning @p dbWorkers and @p resume must outlive this pool's async use (the same contract the
310 /// explicit-argument @ref AcquireAsync overload already implies). Only references are
311 /// retained. Intended to be called once during setup, before any concurrent
312 /// @ref AcquireAsync(); it is not synchronized against in-flight acquirers.
313 /// @param dbWorkers The worker pool used to run acquired mappers' blocking ODBC calls.
314 /// @param resume The scheduler used to resume coroutines (typically the app run loop).
316 {
317 _asyncDbWorkers = &dbWorkers;
318 _asyncResume = &resume;
319 }
320
321 /// Asynchronously acquires a DataMapper using the executors previously set via
322 /// @ref SetAsyncExecutors, without blocking the calling thread.
323 ///
324 /// Equivalent to the explicit-argument @ref AcquireAsync overload with the pool's stored
325 /// executors; pass the executors to that overload to override them for a single call.
326 ///
327 /// @return A Task yielding a pooled DataMapper.
328 /// @throws std::logic_error if @ref SetAsyncExecutors has not been called on this pool.
330 {
331 if (!_asyncDbWorkers || !_asyncResume)
332 throw std::logic_error {
333 "Pool::AcquireAsync(): no async executors configured; call Pool::SetAsyncExecutors(...) first "
334 "or use the explicit AcquireAsync(dbWorkers, resume) overload."
335 };
336 return AcquireAsyncImpl(_asyncDbWorkers, _asyncResume);
337 }
338
339#if defined(BUILD_TESTS)
340 [[nodiscard]] size_t IdleCount() noexcept
341 {
342 std::scoped_lock lock(_mutex);
343 return _idleDataMappers.size();
344 }
345
346 /// @return the number of parked acquirers (blocked Acquire() threads + suspended AcquireAsync
347 /// coroutines); lets tests observe parking/fairness deterministically.
348 [[nodiscard]] size_t WaiterCount() noexcept
349 {
350 std::scoped_lock lock(_mutex);
351 return _waiters.size();
352 }
353#endif
354
355 private:
356 /// A parked acquirer awaiting a DataMapper — a suspended @ref AcquireAsync coroutine (@c Kind::Async)
357 /// or a blocked synchronous @ref Acquire thread (@c Kind::Sync). Both share one FIFO queue
358 /// (@c _waiters), served in arrival order so neither kind starves the other.
359 ///
360 /// Heap-allocated and shared with the pool. Holding the handed-off mapper and @c state in this node
361 /// (not via a pointer into a coroutine frame) lets @ref Return and the awaitable's destructor
362 /// coordinate purely through node state under @c _mutex, never touching a possibly-destroyed frame.
363 struct WaiterNode
364 {
365 /// Whether this waiter is a suspended coroutine or a blocked synchronous Acquire() thread.
366 enum class Kind : std::uint8_t
367 {
368 Sync, ///< A blocked @ref Acquire thread; woken via @c cv.
369 Async, ///< A suspended @ref AcquireAsync coroutine; resumed via @c resume / @c handle.
370 };
371
372 /// Liveness of the waiter, transitioned only under @c Pool::_mutex.
373 enum class State : std::uint8_t
374 {
375 Parked, ///< Registered in @c _waiters, awaiting a mapper.
376 Fulfilled, ///< Return handed it a mapper (in @c mapper) and woke/scheduled it.
377 Abandoned, ///< The awaiting async task was destroyed (or its mapper consumed); inert.
378 };
379
380 Kind kind;
381 State state = State::Parked;
382 std::unique_ptr<DataMapper> mapper {}; ///< Filled by Return on hand-off; lives outside any frame.
383
384 // Async waiter only:
385 std::coroutine_handle<> handle {};
386 Async::IResumeScheduler* resume = nullptr;
387
388 // Sync waiter only: the blocked Acquire() waits on this CV (under Pool::_mutex). One waiter per
389 // CV, so Return's notify_one wakes exactly the served thread.
390 std::condition_variable cv {};
391
392 explicit WaiterNode(Kind nodeKind) noexcept:
393 kind { nodeKind }
394 {
395 }
396 };
397
398 /// Awaitable that acquires a DataMapper, suspending only when the pool is at capacity.
399 ///
400 /// Non-copyable/non-movable: constructed in place in the co_await expression. On suspension it
401 /// registers a shared @ref WaiterNode (@c Kind::Async) in pool._waiters; the node carries the
402 /// handed-off mapper and liveness state so Return() and this destructor coordinate safely.
403 struct AsyncAcquireAwaitable
404 {
405 Pool& pool;
406 Async::IResumeScheduler& resume;
407 std::unique_ptr<DataMapper> acquired {}; ///< Mapper obtained without suspending (idle/fresh).
408 std::shared_ptr<WaiterNode> node {}; ///< Set only while parked; shared with the pool.
409
410 AsyncAcquireAwaitable(Pool& poolRef, Async::IResumeScheduler& resumeRef) noexcept:
411 pool { poolRef },
412 resume { resumeRef }
413 {
414 }
415
416 AsyncAcquireAwaitable(AsyncAcquireAwaitable const&) = delete;
417 AsyncAcquireAwaitable& operator=(AsyncAcquireAwaitable const&) = delete;
418 AsyncAcquireAwaitable(AsyncAcquireAwaitable&&) = delete;
419 AsyncAcquireAwaitable& operator=(AsyncAcquireAwaitable&&) = delete;
420
421 /// Cleans up if the awaiting coroutine is destroyed before it consumes its mapper.
422 ///
423 /// Under pool._mutex: if still parked, de-registers the node so a later Return() never hands
424 /// off to a dead frame. If Return() already handed off a mapper (Fulfilled) that await_resume
425 /// never consumed, reclaims it into the pool so the BoundedWait checked-out count is not
426 /// leaked (possibly handing it straight to the next waiter, resumed after the lock is released).
427 ///
428 /// @warning A task that has already been handed a mapper must still be driven to completion:
429 /// the resumption Return() scheduled cannot be cancelled, so a coroutine frame with a pending
430 /// resumption must not be freed (do not destroy such a task concurrently with, or right after,
431 /// the hand-off). Likewise the pool must outlive every task acquired from it.
432 ~AsyncAcquireAwaitable()
433 {
434 if (!node)
435 return;
436 std::shared_ptr<WaiterNode> toResume;
437 {
438 std::scoped_lock const lock(pool._mutex);
439 switch (node->state)
440 {
441 case WaiterNode::State::Parked:
442 // Never fulfilled: remove ourselves so Return() won't hand off to a dead frame.
443 // Parking never incremented _checkedOut, so there is nothing to release.
444 node->state = WaiterNode::State::Abandoned;
445 std::erase(pool._waiters, node);
446 break;
447 case WaiterNode::State::Fulfilled:
448 // Handed a mapper but the task is dropped before consuming it: reclaim it,
449 // releasing this acquisition's checked-out count so the pool does not leak.
450 node->state = WaiterNode::State::Abandoned;
451 if constexpr (Config.growthStrategy == GrowthStrategy::BoundedWait)
452 {
453 if (node->mapper)
454 toResume = pool.ReturnLocked(std::move(node->mapper));
455 }
456 break;
457 case WaiterNode::State::Abandoned:
458 break;
459 }
460 }
461 if (toResume)
462 toResume->resume->Resume(toResume->handle);
463 }
464
465 [[nodiscard]] bool await_ready() const noexcept
466 {
467 return false;
468 }
469
470 bool await_suspend(std::coroutine_handle<> handle)
471 {
472 std::scoped_lock const lock(pool._mutex);
473 if (!pool._idleDataMappers.empty())
474 {
475 acquired = std::move(pool._idleDataMappers.back());
476 pool._idleDataMappers.pop_back();
477 if constexpr (Config.growthStrategy == GrowthStrategy::BoundedWait)
478 ++pool._checkedOut;
479 return false; // do not suspend — resume immediately
480 }
481 // Only BoundedWait bounds the pool and parks coroutines on exhaustion. The non-blocking
482 // strategies (BoundedOverflow — the default — and UnboundedGrow) always create a fresh
483 // mapper here, matching the synchronous Acquire() overloads, which also never suspend.
484 if constexpr (Config.growthStrategy == GrowthStrategy::BoundedWait)
485 {
486 if (pool._checkedOut >= Config.maxSize)
487 {
488 node = std::make_shared<WaiterNode>(WaiterNode::Kind::Async);
489 node->handle = handle;
490 node->resume = &resume;
491 pool._waiters.push_back(node);
492 return true; // suspend until a mapper is returned
493 }
494 ++pool._checkedOut;
495 }
496 acquired = std::make_unique<DataMapper>();
497 return false;
498 }
499
500 std::unique_ptr<DataMapper> await_resume() noexcept
501 {
502 // If we suspended, Return() placed the mapper in the shared node; take it here (on the
503 // resuming thread, with no concurrent access per the destruction contract). That leaves
504 // node->mapper empty, so the destructor treats the node as already consumed.
505 if (node)
506 return std::move(node->mapper);
507 return std::move(acquired);
508 }
509 };
510
511 Async::Task<PooledDataMapper> AcquireAsyncImpl(Async::IExecutor* dbWorkers, Async::IResumeScheduler* resume)
512 {
513 auto dm = co_await AsyncAcquireAwaitable { *this, *resume };
514 // Wrap in the RAII PooledDataMapper BEFORE the throwing EnableAsync call: if EnableAsync
515 // throws (e.g. bad_alloc), ~PooledDataMapper returns the mapper to the pool, decrementing
516 // _checkedOut and avoiding a permanent BoundedWait capacity leak.
517 auto pooled = PooledDataMapper(*this, std::move(dm));
518 pooled->Connection().EnableAsync(*dbWorkers, *resume);
519 co_return std::move(pooled);
520 }
521
522 std::mutex _mutex;
523 std::vector<std::unique_ptr<DataMapper>> _idleDataMappers;
524 size_t _checkedOut {};
525 /// Executors used by the no-argument @ref AcquireAsync() overload; set via @ref SetAsyncExecutors.
526 /// Null until configured. Only references are held; they must outlive the pool's async use.
527 Async::IExecutor* _asyncDbWorkers = nullptr;
528 Async::IResumeScheduler* _asyncResume = nullptr;
529 /// FIFO of parked acquirers (sync @ref Acquire threads and async @ref AcquireAsync coroutines) in
530 /// arrival order. Each sync waiter owns its CV inside its @ref WaiterNode, so no shared CV is needed.
531 std::deque<std::shared_ptr<WaiterNode>> _waiters;
532};
533
534// Default pool configuration, configurable via CMake options:
535// LIGHTWEIGHT_POOL_INITIAL_SIZE (default: 4)
536// LIGHTWEIGHT_POOL_MAX_SIZE (default: 16)
537// LIGHTWEIGHT_POOL_GROWTH_STRATEGY (default: BoundedOverflow)
538// Accepted values: BoundedWait, BoundedOverflow, UnboundedGrow
539
540#if !defined(LIGHTWEIGHT_POOL_INITIAL_SIZE)
541 #define LIGHTWEIGHT_POOL_INITIAL_SIZE 4
542#endif
543
544#if !defined(LIGHTWEIGHT_POOL_MAX_SIZE)
545 #define LIGHTWEIGHT_POOL_MAX_SIZE 16
546#endif
547
548#if !defined(LIGHTWEIGHT_POOL_GROWTH_STRATEGY)
549 #define LIGHTWEIGHT_POOL_GROWTH_STRATEGY BoundedOverflow
550#endif
551
552inline constexpr PoolConfig DefaultPoolConfig {
553 .initialSize = LIGHTWEIGHT_POOL_INITIAL_SIZE,
554 .maxSize = LIGHTWEIGHT_POOL_MAX_SIZE,
555 .growthStrategy = GrowthStrategy::LIGHTWEIGHT_POOL_GROWTH_STRATEGY,
556};
557
558using DataMapperPool = Pool<DefaultPoolConfig>;
559
560/// Returns the process-wide global DataMapper pool.
561///
562/// The pool is configured at compile time via the LIGHTWEIGHT_POOL_* defines.
563/// Because the singleton lives inside the Lightweight library, it is shared
564/// correctly across shared-library boundaries.
565LIGHTWEIGHT_API DataMapperPool& GlobalDataMapperPool();
566
567} // namespace Lightweight
Main API for mapping records to and from the database using high level C++ syntax.
DataMapper * operator->() const noexcept
Access the underlying data mapper via pointer semantics.
Definition Pool.hpp:98
PooledDataMapper(PooledDataMapper &&other) noexcept
Definition Pool.hpp:84
DataMapper & Get() const noexcept
Definition Pool.hpp:106
~Pool() noexcept
Definition Pool.hpp:216
PooledDataMapper Acquire()
Definition Pool.hpp:269
Async::Task< PooledDataMapper > AcquireAsync(Async::IExecutor &dbWorkers, Async::IResumeScheduler &resume)
Definition Pool.hpp:295
PooledDataMapper Acquire()
Definition Pool.hpp:239
void SetAsyncExecutors(Async::IExecutor &dbWorkers, Async::IResumeScheduler &resume) noexcept
Definition Pool.hpp:315
Async::Task< PooledDataMapper > AcquireAsync()
Definition Pool.hpp:329
static LIGHTWEIGHT_API SqlLogger & GetLogger()
Retrieves the currently configured logger.
virtual void OnWarning(std::string_view const &message)=0
Invoked on a warning.
GrowthStrategy growthStrategy
Definition Pool.hpp:54
size_t initialSize
Initial number of data mappers to pre-create and store in the pool, must be less than or equal to max...
Definition Pool.hpp:47