Skip to content

Conversation

DreamPearl
Copy link
Contributor

@DreamPearl DreamPearl commented Nov 20, 2024

PROTON-1442

AMQP Transaction Sequence:

  1. Declare transaction:
  • Client establishes link to Broker (Transaction resource) to target with transaction coordinator type (usual types are sender/receiver) (ATTACH frame)

  • Client (Transaction Controller) sends a special message to that link to create transaction (TRANSFER frame)

  • Broker returns a disposition with the transaction id (DISPOSITION frame)

  1. Send Message in the transaction:
  • Client sends a message to the broker with transaction id in the state field.
  1. Commit/Abort

@DreamPearl DreamPearl marked this pull request as draft November 20, 2024 08:57
@DreamPearl DreamPearl force-pushed the local-transactions branch 2 times, most recently from 74818fc to eb4514b Compare November 21, 2024 18:28
Copy link
Member

@astitcher astitcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken a fairly thorough look. If you have any questions about these review items we should discuss further.

Comment on lines 101 to 62
class
PN_CPP_CLASS_EXTERN transaction_handler {
public:
PN_CPP_EXTERN virtual ~transaction_handler();

/// Called when a local transaction is declared.
PN_CPP_EXTERN virtual void on_transaction_declared(transaction);

/// Called when a local transaction is discharged successfully.
PN_CPP_EXTERN virtual void on_transaction_committed(transaction);

/// Called when a local transaction is discharged unsuccessfully (aborted).
PN_CPP_EXTERN virtual void on_transaction_aborted(transaction);

/// Called when a local transaction declare fails.
PN_CPP_EXTERN virtual void on_transaction_declare_failed(transaction);

/// Called when the commit of a local transaction fails.
PN_CPP_EXTERN virtual void on_transaction_commit_failed(transaction);
};

} // namespace proton

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be in a different header file like message and messaging_handler

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if the API makes transactions hidden in a session then these callbacks either go away or instead become session callbacks: on_session_transaction_committed(session&), etc. I think transaction_declared() goes away entirely and it's purpose is now another use for on_session_open(session&). on_.._declare_failed should be handled by the on_session_error(session&).

Copy link
Member

@astitcher astitcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not reviewed this comprehensively, but I think I'm now very much leaning towards not making the transaction class visible to the API user at all and having the transaction methods on the session. This is essentially the API in JMS and CMS.

Comment on lines 101 to 62
class
PN_CPP_CLASS_EXTERN transaction_handler {
public:
PN_CPP_EXTERN virtual ~transaction_handler();

/// Called when a local transaction is declared.
PN_CPP_EXTERN virtual void on_transaction_declared(transaction);

/// Called when a local transaction is discharged successfully.
PN_CPP_EXTERN virtual void on_transaction_committed(transaction);

/// Called when a local transaction is discharged unsuccessfully (aborted).
PN_CPP_EXTERN virtual void on_transaction_aborted(transaction);

/// Called when a local transaction declare fails.
PN_CPP_EXTERN virtual void on_transaction_declare_failed(transaction);

/// Called when the commit of a local transaction fails.
PN_CPP_EXTERN virtual void on_transaction_commit_failed(transaction);
};

} // namespace proton

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if the API makes transactions hidden in a session then these callbacks either go away or instead become session callbacks: on_session_transaction_committed(session&), etc. I think transaction_declared() goes away entirely and it's purpose is now another use for on_session_open(session&). on_.._declare_failed should be handled by the on_session_error(session&).

transaction.accept(d);
current_batch += 1;
if(current_batch == batch_size) {
transaction = proton::transaction(); // null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should do rather commit here, that way it works as expected and the receiver is closed after expected number of messages received (with the current implementation it's not).

Copy link

@pematous pematous Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should do rather commit here, that way it works as expected and the receiver is closed after expected number of messages received (with the current implementation it's not).

above mentioned change:

-            transaction = proton::transaction(); // null
+           transaction.commit();

works as expected (against Artemis broker) when using multicast. However, when a pre-defined anycast queue is used, the commit() call ends with client segmentation fault.


void on_message(proton::delivery &d, proton::message &msg) override {
std::cout<<"# MESSAGE: " << msg.id() <<": " << msg.body() << std::endl;
transaction.accept(d);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw. I'm thinking if transaction.accept() does actually have any effect? I mean, when I comment it out, the program behaves the same. btw. is this transaction accept any different from delivery accept? looks both settles the messages related to the delivery.

Does explicit delivery settling play role in transaction mode? or are they mutually exclusive and only commits and aborts applies? Can ie. single message in transaction be rejected while other messages accepted?

I tried to accept / reject the delivery and it seems to have no effect in transaction mode (while in python, doing so makes the messages to be threat outside of the transaction, just like a normal messages, https://issues.redhat.com/browse/ENTMQCL-513).

Copy link
Member

@astitcher astitcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lopts of detailed comments here, but the main change I'd like to see is either make coordinator a subclass of target or fold it into a simple boolean option of target.

astitcher and others added 13 commits April 14, 2025 22:17
* Added an extra handler to the python binding so that we can handle
  transactioned dispositions
* Modified the Python example broker so that it understands transaction
  requests, prints some useful output about what is happening, but
  doesn't honor the transaction semantics. It will queue up transactioned
  messages immediately and also doesn't correctly handle outgoing
  message releases (but it doesn't for non-transactioned messages either
* Make it compile
* Make it fit the existing software structure better

private:
// clean up txn internally
void txn_delete();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be very surprised if this is needed here - I'd think it could only be called from the impl class

@@ -36,6 +36,7 @@
struct pn_session_t;

namespace proton {
class transaction_impl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually needed here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transaction_impl is pirely internal and should not be in an externally visible header file

/// @cond INTERNAL
friend class internal::factory<session>;
friend class session_iterator;
friend class transaction_impl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd think it's more likely that transaction_impl needs to a friend of session_impl

Comment on lines 111 to 119
PN_CPP_EXTERN bool txn_is_empty();
PN_CPP_EXTERN bool txn_is_declared();
PN_CPP_EXTERN void txn_commit();
PN_CPP_EXTERN void txn_abort();
PN_CPP_EXTERN void txn_declare();
PN_CPP_EXTERN void txn_handle_outcome(proton::tracker);
PN_CPP_EXTERN proton::tracker txn_send(proton::sender s, proton::message msg);
PN_CPP_EXTERN void txn_accept(delivery &t);
PN_CPP_EXTERN proton::connection txn_connection() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these just internal functions? They should only be needed inside session_impl

}

void transaction_impl::accept(delivery &t) {
t.settle();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs update(t, PN_ACCEPT);

t.settle();
}

void transaction_impl::update(tracker &t, uint64_t state) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be delivery not tracker

Copy link
Member

@astitcher astitcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First batch of comments


void on_message(proton::delivery &d, proton::message &msg) override {
std::cout<<"# MESSAGE: " << msg.id() <<": " << msg.body() << std::endl;
session.transaction_accept(d);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should just be accept now as this is already a transactioned session

session.transaction_accept(d);
current_batch += 1;
if(current_batch == batch_size) {
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to commit or abort transaction here

@@ -36,6 +36,7 @@
struct pn_session_t;

namespace proton {
class transaction_impl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transaction_impl is pirely internal and should not be in an externally visible header file

Comment on lines +113 to +119
PN_CPP_EXTERN void transaction_commit();
PN_CPP_EXTERN void transaction_abort();
PN_CPP_EXTERN void transaction_declare();
PN_CPP_EXTERN void transaction_handle_outcome(proton::tracker);
PN_CPP_EXTERN void attach_txn_id(proton::tracker t);
PN_CPP_EXTERN void transaction_accept(delivery &t);
PN_CPP_EXTERN proton::connection transaction_connection() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all internal functions and shouldn't be in the visible header file

@@ -105,14 +106,31 @@ PN_CPP_CLASS_EXTERN session : public internal::object<pn_session_t>, public endp
/// Get user data from this session.
PN_CPP_EXTERN void* user_data() const;

PN_CPP_EXTERN void declare_transaction(proton::transaction_handler &handler, bool settle_before_discharge = false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is meant to be called transaction_declare(...) once the internal function below is removed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants