This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-proton.git
commit e99e6306489a71d47cc3aaee516720c2700420be Author: Andrew Stitcher <[email protected]> AuthorDate: Tue May 5 23:42:11 2026 -0400 PROTON-1442: Improve C++ transaction API documentation This code was written with the assistance of Cursor. --- cpp/docs/pages.dox | 1 + cpp/docs/transactions.md | 107 +++++++++++++++++++++++++++++++ cpp/include/proton/messaging_handler.hpp | 5 +- cpp/include/proton/session.hpp | 15 +++++ 4 files changed, 127 insertions(+), 1 deletion(-) diff --git a/cpp/docs/pages.dox b/cpp/docs/pages.dox index 4660015e6..ee4352fcd 100644 --- a/cpp/docs/pages.dox +++ b/cpp/docs/pages.dox @@ -5,3 +5,4 @@ /// @page mt_page Multi-threading /// @page io_page IO integration /// @page tracing Tracing +/// @page transactions_page Transactions diff --git a/cpp/docs/transactions.md b/cpp/docs/transactions.md new file mode 100644 index 000000000..bde4cb137 --- /dev/null +++ b/cpp/docs/transactions.md @@ -0,0 +1,107 @@ +# Transactions {#transactions_page} + +**Unsettled API** — Session transactions and related handler callbacks +are still evolving; see @ref mainpage "Conventions". + +This page documents the C++ API contract for AMQP 1.0 local +transactions (OASIS AMQP 1.0 §4.6). Automated coverage lives in +`tests/cpp/tx_tests/` and is run by `tests/cpp/test_tx_tester.py` +against `python/examples/broker.py`. + +## Lifecycle + +| Application call | Success | Failure | +|------------------|---------|---------| +| `session::transaction_declare()` | `messaging_handler::on_session_transaction_declared` | `messaging_handler::on_session_transaction_error` | +| `session::transaction_commit()` | `messaging_handler::on_session_transaction_committed` | `messaging_handler::on_session_transaction_aborted` with `session::transaction_error()` set | +| `session::transaction_abort()` | `messaging_handler::on_session_transaction_aborted` | (rollback cannot fail at the AMQP level) | + +After a successful discharge (commit or abort), `session::transaction_is_declared()` +returns `false`. `session::transaction_id()` returns the coordinator-assigned +identifier while a transaction is declared or discharging, and remains +available for the duration of discharge callbacks; it is cleared when those +callbacks return. + +Declare sends `amqp:declare:list` on a coordinator link advertising +`amqp:local-transactions`. Commit and abort send `amqp:discharge:list` +with the transaction id and a `fail` flag (`false` for commit, `true` +for abort). + +## Proton session guarantees + +These rules are enforced synchronously (they throw `proton::error`): + +- **One active transaction per session.** A second `transaction_declare()` + while a transaction is already active is rejected. +- **No declare with unsettled outgoing deliveries.** All outgoing transfers + on the session during a transaction are treated as part of that + transaction; declare is only allowed when every non-coordinator sender + link on the session has no unsettled deliveries. +- **Discharge requires a declared transaction.** `transaction_commit()` and + `transaction_abort()` throw if no transaction is in the declared state. + +After discharge completes, a new transaction may be declared on the same +session. + +## Messaging under a transaction + +While `transaction_is_declared()` is true: + +- `sender::send()` attaches `amqp:transactional-state:list` to outgoing + transfers. The peer responds with a provisional outcome; the application + receives `on_transactional_accept`, `on_transactional_reject`, or + `on_transactional_release` as appropriate. +- `delivery::accept()`, `reject()`, `release()`, and `modify()` attach + transactional state instead of settling immediately. Incoming deliveries + stay unsettled until discharge; `on_delivery_settle` is not raised for + them before commit or abort. + +On **successful commit**: + +- Incoming deliveries with provisional dispositions are settled. +- Outgoing provisional outcomes become final: `on_tracker_accept`, + `on_tracker_reject`, or `on_tracker_release`, followed by + `on_tracker_settle` when the peer settles. + +On **abort or failed commit**: + +- Actions taken under the transaction are rolled back (as if they never + happened). +- Unsettled outgoing trackers on the session are settled locally without + a disposition update. +- Unsettled incoming deliveries are dispositioned with `modified` (failed) + by default (`transaction_options::auto_modify_on_abort`, default `true`). + +Failed commit is reported through `on_session_transaction_aborted`, not +`on_session_transaction_error`. Use `session::transaction_error()` inside +the aborted callback to distinguish a broker-rejected commit from an +explicit abort. + +## Transaction options + +`transaction_options::auto_modify_on_abort(bool)` controls whether Proton +automatically modifies unsettled incoming deliveries when a transaction +aborts or a commit discharge is rejected. The default is `true`. When +`false`, the application must update those deliveries itself. + +## Test environment + +CI runs the script suite against `python/examples/broker.py` with a +2-second transaction timeout (`-t 2`). Scripts are numbered so they +execute in order against a single broker instance; an initial setup +script seeds the target queue. + +**Future work (out of scope today):** broker abstraction in the test +harness (e.g. `TX_TESTER_BROKER_CMD` / `TX_TESTER_BROKER_URL`) so the +same scripts can be run against Artemis or other peers; non-fatal +assertions for broker-specific provisional disposition timing. + +Provisional disposition callbacks depend on the peer implementing +transactional state promptly. The example broker does; other brokers may +differ. + +## Examples + +- `cpp/examples/tx_recv.cpp` — transactional receive with commit/abort batches +- `cpp/examples/tx_send.cpp` — transactional send with provisional outcomes +- `tests/cpp/tx_tester.cpp` — scriptable transaction tester used by the suite diff --git a/cpp/include/proton/messaging_handler.hpp b/cpp/include/proton/messaging_handler.hpp index 1d368d894..42a2466a9 100644 --- a/cpp/include/proton/messaging_handler.hpp +++ b/cpp/include/proton/messaging_handler.hpp @@ -239,7 +239,10 @@ PN_CPP_CLASS_EXTERN messaging_handler { /// Fallback error handling. PN_CPP_EXTERN virtual void on_error(const error_condition&); - /// **Unsettled API** - Called when a local transaction is declared. + /// **Unsettled API** - Transaction lifecycle and messaging callbacks. + /// @see transactions_page + /// + /// Called when a local transaction is declared. PN_CPP_EXTERN virtual void on_session_transaction_declared(session&); /// **Unsettled API** - Called when a local transaction is discharged successfully. diff --git a/cpp/include/proton/session.hpp b/cpp/include/proton/session.hpp index 24438caa9..d7ad67415 100644 --- a/cpp/include/proton/session.hpp +++ b/cpp/include/proton/session.hpp @@ -106,18 +106,33 @@ PN_CPP_CLASS_EXTERN session : public internal::object<pn_session_t>, public endp PN_CPP_EXTERN void* user_data() const; /// **Unsettled API** - Declare a new local transaction on this session. + /// + /// When the transactions coordinator accepts, the application receives + /// messaging_handler::on_session_transaction_declared. A rejected declare is reported as + /// messaging_handler::on_session_transaction_error. + /// + /// @see transactions_page PN_CPP_EXTERN void transaction_declare(); /// **Unsettled API** - Commit the currently declared transaction. + /// + /// Outcome is delivered asynchronously via messaging_handler::on_session_transaction_committed + /// or messaging_handler::on_session_transaction_aborted. PN_CPP_EXTERN void transaction_commit(); /// **Unsettled API** - Abort the currently declared transaction. + /// + /// This completes with messaging_handler::on_session_transaction_aborted (see also + /// transaction_commit()). PN_CPP_EXTERN void transaction_abort(); /// **Unsettled API** - Return true if a transaction is currently declared. PN_CPP_EXTERN bool transaction_is_declared() const; /// **Unsettled API** - Return the identifier of the current transaction. + /// + /// There will be a valid transaction id from the tranacrtion declared callback until after any transaction + /// dicharge callback. PN_CPP_EXTERN binary transaction_id() const; /// **Unsettled API** - Return the error condition associated with transaction. --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
