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