Lightweight 0.20260921.0
Loading...
Searching...
No Matches
AsyncSqlTransaction.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../Api.hpp"
5#include "../SqlTransaction.hpp"
6#include "Fwd.hpp"
7
8#include <optional>
9#include <stop_token>
10
11namespace Lightweight
12{
13class SqlConnection;
14}
15
16namespace Lightweight::Async
17{
18
19/// @ingroup Async
20/// A distinct coroutine-based SQL transaction.
21///
22/// Unlike the high- and low-level async methods (which are added directly to the existing
23/// classes), a transaction is a scoped object, so it reads best as its own type. Each
24/// operation is offloaded to the connection's async backend (a worker thread, serialized per
25/// connection) and the awaiting coroutine resumes on the app's resume scheduler.
26///
27/// The underlying connection must have async enabled (SqlConnection::EnableAsync) before use and
28/// must stay async-enabled and alive for the whole lifetime of the transaction: do not destroy the
29/// connection (or return the owning pooled DataMapper, which disables async) between @ref BeginAsync
30/// and the matching @ref CommitAsync / @ref RollbackAsync — the offloaded steps capture the
31/// connection by pointer and would otherwise fail (a clear @c std::logic_error from
32/// @c SqlConnection::AsyncBackend) or dangle.
33///
34/// Always `co_await CommitAsync()` or `co_await RollbackAsync()` explicitly. If the transaction is
35/// still open when destroyed, the destructor performs a best-effort finalization (per the configured
36/// mode) and emits a warning via @c SqlLogger. That finalization is itself routed through the
37/// connection's strand (and blocks the destroying thread until it completes) so it never touches the
38/// ODBC handle concurrently with another in-flight async operation on the same connection.
39///
40/// @warning The destructor's strand-serialized finalization blocks the destroying thread until the
41/// strand has run it, so do not destroy an open transaction from any thread that the strand needs in
42/// order to make progress. That includes (a) destroying it from @e within a strand operation on its own
43/// connection, and (b) — in the multi-threaded model where the resume scheduler @e is the worker pool
44/// that backs the connection's strand — letting it be destroyed while the coroutine is resuming on one
45/// of those worker threads (with a single-worker pool this self-waits and deadlocks). The connection
46/// and its injected executors must also outlive the transaction; tearing the worker pool down first
47/// leaves the finalization undrained and the destructor blocked. Prefer explicit
48/// @ref CommitAsync / @ref RollbackAsync so the destructor never has to finalize.
49///
50/// @code
51/// auto tx = Async::AsyncSqlTransaction { dm.Connection() };
52/// co_await tx.BeginAsync();
53/// co_await dm.UpdateAsync(record);
54/// co_await tx.CommitAsync();
55/// @endcode
56class LIGHTWEIGHT_API AsyncSqlTransaction
57{
58 public:
59 /// Constructs an (not-yet-begun) async transaction over @p connection.
60 /// @param connection The async-enabled connection to run the transaction on.
61 explicit AsyncSqlTransaction(SqlConnection& connection) noexcept;
62
64 AsyncSqlTransaction& operator=(AsyncSqlTransaction const&) = delete;
66 AsyncSqlTransaction& operator=(AsyncSqlTransaction&&) = delete;
67
68 /// Best-effort synchronous finalization if still open (see class note).
70
71 /// Asynchronously begins the transaction (disables auto-commit).
72 ///
73 /// @param defaultMode How an un-finalized transaction is closed on destruction. Defaults to
74 /// @c COMMIT to match the synchronous @ref SqlTransaction, so porting sync code that
75 /// relies on commit-on-scope-exit does not silently switch to rollback.
76 /// @param isolationMode The transaction isolation level.
77 /// @param token Optional cancellation token (a default-constructed @c std::stop_token is
78 /// non-cancellable; cancellation is checked before the step is dispatched).
79 /// @return A Task that completes once the transaction has begun.
80 /// @throws std::logic_error if a transaction is already open on this object (programmer error).
81 [[nodiscard]] Task<void> BeginAsync(SqlTransactionMode defaultMode = SqlTransactionMode::COMMIT,
82 SqlIsolationMode isolationMode = SqlIsolationMode::DriverDefault,
83 std::stop_token token = {});
84
85 /// Asynchronously commits the transaction.
86 /// @note Finalization is a point of no return and is intentionally @b not cancellable: it always
87 /// runs to completion. (Abandoning it would leave the transaction open, and the destructor
88 /// would then finalize it with the configured default mode.)
89 [[nodiscard]] Task<void> CommitAsync();
90
91 /// Asynchronously rolls back the transaction.
92 /// @note Finalization is a point of no return and is intentionally @b not cancellable: it always
93 /// runs to completion (so a rollback never silently degrades into a commit-on-destruction).
94 [[nodiscard]] Task<void> RollbackAsync();
95
96 private:
97 /// Runs @p finalize (SqlTransaction::Commit or ::Rollback) on the open transaction via the
98 /// connection strand, then clears it. Shared by CommitAsync/RollbackAsync. Not cancellable.
99 /// @param finalize The finalizing member function to invoke.
100 [[nodiscard]] Task<void> FinalizeAsync(void (SqlTransaction::*finalize)());
101
102 SqlConnection* _connection;
103 std::optional<SqlTransaction> _transaction;
104};
105
106} // namespace Lightweight::Async
Task< void > BeginAsync(SqlTransactionMode defaultMode=SqlTransactionMode::COMMIT, SqlIsolationMode isolationMode=SqlIsolationMode::DriverDefault, std::stop_token token={})
AsyncSqlTransaction(SqlConnection &connection) noexcept
~AsyncSqlTransaction()
Best-effort synchronous finalization if still open (see class note).
Represents a connection to a SQL database.