diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c65cc8723..33ceb3cf2 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -120,6 +120,7 @@ set(qpid-proton-cpp-source src/terminus.cpp src/timestamp.cpp src/tracker.cpp + src/transaction.cpp src/transfer.cpp src/transport.cpp src/type_id.cpp diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 587edfc52..766d34b2e 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -60,7 +60,9 @@ foreach(example scheduled_send service_bus multithreaded_client - multithreaded_client_flow_control) + multithreaded_client_flow_control + tx_send + tx_recv) add_executable(${example} ${example}.cpp) target_link_libraries(${example} Proton::cpp Threads::Threads) endforeach() diff --git a/cpp/examples/tx_recv.cpp b/cpp/examples/tx_recv.cpp new file mode 100644 index 000000000..07d2061e4 --- /dev/null +++ b/cpp/examples/tx_recv.cpp @@ -0,0 +1,129 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "options.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +class tx_recv : public proton::messaging_handler, proton::transaction_handler { + private: + proton::receiver receiver; + std::string url; + int expected; + int batch_size; + int current_batch = 0; + int committed = 0; + + proton::session session; + proton::transaction transaction; + public: + tx_recv(const std::string &s, int c, int b): + url(s), expected(c), batch_size(b) {} + + void on_container_start(proton::container &c) override { + receiver = c.open_receiver(url); + } + + void on_session_open(proton::session &s) override { + session = s; + std::cout << " [on_session_open] declare_txn started..." << std::endl; + s.declare_transaction(*this); + std::cout << " [on_session_open] declare_txn ended..." << std::endl; + } + + void on_transaction_declare_failed(proton::transaction) {} + void on_transaction_commit_failed(proton::transaction t) { + std::cout << "Transaction Commit Failed" << std::endl; + t.connection().close(); + exit(-1); + } + + void on_transaction_declared(proton::transaction t) override { + std::cout << "[on_transaction_declared] txn called " << (&t) + << std::endl; + std::cout << "[on_transaction_declared] txn is_empty " << (t.is_empty()) + << "\t" << transaction.is_empty() << std::endl; + receiver.add_credit(batch_size); + transaction = t; + } + + void on_message(proton::delivery &d, proton::message &msg) override { + std::cout<<"# MESSAGE: " << msg.id() <<": " << msg.body() << std::endl; + transaction.accept(d); + current_batch += 1; + if(current_batch == batch_size) { + transaction = proton::transaction(); // null + } + } + + void on_transaction_committed(proton::transaction t) override { + committed += current_batch; + current_batch = 0; + std::cout<<" [OnTxnCommitted] Committed:"<< committed<< std::endl; + if(committed == expected) { + std::cout << "All messages committed" << std::endl; + t.connection().close(); + } + else { + session.declare_transaction(*this); + } + } + +}; + +int main(int argc, char **argv) { + std::string address("127.0.0.1:5672/examples"); + int message_count = 9; + int batch_size = 3; + example::options opts(argc, argv); + + opts.add_value(address, 'a', "address", "connect and send to URL", "URL"); + opts.add_value(message_count, 'm', "messages", "number of messages to send", "COUNT"); + opts.add_value(batch_size, 'b', "batch_size", "number of messages in each transaction", "BATCH_SIZE"); + + try { + opts.parse(); + + tx_recv recv(address, message_count, batch_size); + proton::container(recv).run(); + + return 0; + } catch (const example::bad_option& e) { + std::cout << opts << std::endl << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } + + return 1; +} diff --git a/cpp/examples/tx_send.cpp b/cpp/examples/tx_send.cpp new file mode 100644 index 000000000..6acee58fd --- /dev/null +++ b/cpp/examples/tx_send.cpp @@ -0,0 +1,175 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "options.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +class tx_send : public proton::messaging_handler, proton::transaction_handler { + private: + proton::sender sender; + std::string url; + int total; + int batch_size; + int sent; + int batch_index = 0; + int current_batch = 0; + int committed = 0; + int confirmed = 0; + + proton::session session; + proton::transaction transaction; + public: + tx_send(const std::string &s, int c, int b): + url(s), total(c), batch_size(b), sent(0) {} + + void on_container_start(proton::container &c) override { + sender = c.open_sender(url); + } + + void on_session_open(proton::session &s) override { + session = s; + std::cout << " [on_session_open] declare_txn started..." << std::endl; + s.declare_transaction(*this); + std::cout << " [on_session_open] declare_txn ended..." << std::endl; + } + + void on_transaction_declare_failed(proton::transaction) {} + void on_transaction_commit_failed(proton::transaction t) { + std::cout << "Transaction Commit Failed" << std::endl; + t.connection().close(); + exit(-1); + } + + void on_transaction_declared(proton::transaction t) override { + std::cout << "[on_transaction_declared] txn called " << (&t) + << std::endl; + std::cout << "[on_transaction_declared] txn is_empty " << (t.is_empty()) + << "\t" << transaction.is_empty() << std::endl; + transaction = t; + + send(sender); + } + + void on_sendable(proton::sender &s) override { + std::cout << " [OnSendable] transaction: " << &transaction + << std::endl; + send(s); + } + + void send(proton::sender &s) { + static int unique_id = 10000; + while (!transaction.is_empty() && sender.credit() && + (committed + current_batch) < total) { + proton::message msg; + std::map m; + m["sequence"] = committed + current_batch; + + msg.id(unique_id++); + msg.body(m); + std::cout << "##### [example] transaction send msg: " << msg + << std::endl; + transaction.send(sender, msg); + current_batch += 1; + if(current_batch == batch_size) + { + std::cout << " >> Txn attempt commit" << std::endl; + if (batch_index % 2 == 0) { + transaction.commit(); + } else { + transaction.abort(); + } + + transaction = proton::transaction(); + batch_index++; + } + } + } + + void on_tracker_accept(proton::tracker &t) override { + confirmed += 1; + std::cout << " [example] on_tracker_accept:" << confirmed + << std::endl; + } + + void on_transaction_committed(proton::transaction t) override { + committed += current_batch; + current_batch = 0; + std::cout<<" [OnTxnCommitted] Committed:"<< committed<< std::endl; + if(committed == total) { + std::cout << "All messages committed" << std::endl; + t.connection().close(); + } + else { + session.declare_transaction(*this); + } + } + + void on_transaction_aborted(proton::transaction t) override { + std::cout << "Meesages Aborted ....." << std::endl; + current_batch = 0; + session.declare_transaction(*this); + } + + void on_sender_close(proton::sender &s) override { + current_batch = 0; + } + +}; + +int main(int argc, char **argv) { + std::string address("127.0.0.1:5672/examples"); + int message_count = 6; + int batch_size = 3; + example::options opts(argc, argv); + + opts.add_value(address, 'a', "address", "connect and send to URL", "URL"); + opts.add_value(message_count, 'm', "messages", "number of messages to send", "COUNT"); + opts.add_value(batch_size, 'b', "batch_size", "number of messages in each transaction", "BATCH_SIZE"); + + try { + opts.parse(); + + tx_send send(address, message_count, batch_size); + proton::container(send).run(); + + return 0; + } catch (const example::bad_option& e) { + std::cout << opts << std::endl << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } + + return 1; +} diff --git a/cpp/include/proton/container.hpp b/cpp/include/proton/container.hpp index 839cf8137..c2c6d206e 100644 --- a/cpp/include/proton/container.hpp +++ b/cpp/include/proton/container.hpp @@ -326,6 +326,7 @@ class PN_CPP_CLASS_EXTERN container { friend class receiver_options; friend class sender_options; friend class work_queue; + friend class transaction; /// @endcond }; diff --git a/cpp/include/proton/fwd.hpp b/cpp/include/proton/fwd.hpp index 801d26932..9fe5fc303 100644 --- a/cpp/include/proton/fwd.hpp +++ b/cpp/include/proton/fwd.hpp @@ -55,6 +55,8 @@ class source_options; class ssl; class target_options; class tracker; +class transaction; +class transaction_handler; class transport; class url; class void_function0; diff --git a/cpp/include/proton/session.hpp b/cpp/include/proton/session.hpp index 60522c817..702bd0db4 100644 --- a/cpp/include/proton/session.hpp +++ b/cpp/include/proton/session.hpp @@ -105,6 +105,8 @@ PN_CPP_CLASS_EXTERN session : public internal::object, public endp /// Get user data from this session. PN_CPP_EXTERN void* user_data() const; + PN_CPP_EXTERN transaction declare_transaction(proton::transaction_handler &handler, bool settle_before_discharge = false); + /// @cond INTERNAL friend class internal::factory; friend class session_iterator; diff --git a/cpp/include/proton/target_options.hpp b/cpp/include/proton/target_options.hpp index f5fe99177..d09f4094e 100644 --- a/cpp/include/proton/target_options.hpp +++ b/cpp/include/proton/target_options.hpp @@ -88,6 +88,8 @@ class target_options { /// **Unsettled API** Set the dynamic node properties. PN_CPP_EXTERN target_options& dynamic_properties(const target::dynamic_property_map&); + PN_CPP_EXTERN target_options& type(const int); + private: void apply(target&) const; diff --git a/cpp/include/proton/transaction.hpp b/cpp/include/proton/transaction.hpp new file mode 100644 index 000000000..6ca0fac96 --- /dev/null +++ b/cpp/include/proton/transaction.hpp @@ -0,0 +1,125 @@ +#ifndef PROTON_TRANSACTION_HPP +#define PROTON_TRANSACTION_HPP + + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +#include "./fwd.hpp" +#include "./internal/export.hpp" +#include "./sender.hpp" +#include "./tracker.hpp" +#include "./container.hpp" + +/// @file +/// @copybrief proton::transaction + +namespace proton { + +class transaction_handler; + +// TODO: This should not be accessible to users. +class transaction_impl { + public: + proton::sender txn_ctrl; + proton::transaction_handler *handler = nullptr; + proton::binary id; + proton::tracker _declare; + proton::tracker _discharge; + bool failed = false; + std::vector pending; + + void commit(); + void abort(); + void declare(); + proton::tracker send(proton::sender s, proton::message msg); + + void discharge(bool failed); + void release_pending(); + void accept(delivery &d); + void update(tracker &d, uint64_t state); + void set_id(binary _id); + + proton::tracker send_ctrl(proton::symbol descriptor, proton::value _value); + void handle_outcome(proton::tracker t); + transaction_impl(proton::sender &_txn_ctrl, + proton::transaction_handler &_handler, + bool _settle_before_discharge); + + // delete copy and assignment operator to ensure no copy of this object is + // every made transaction_impl(const transaction_impl&) = delete; + // transaction_impl& operator=(const transaction_impl&) = delete; +}; + +class +PN_CPP_CLASS_EXTERN transaction { + private: + // PN_CPP_EXTERN transaction(proton::sender& _txn_ctrl, + // proton::transaction_handler& _handler, bool _settle_before_discharge); + + static transaction mk_transaction_impl(sender &s, transaction_handler &h, + bool f); + PN_CPP_EXTERN transaction(transaction_impl *impl); + transaction_impl *_impl; + + public: + // TODO: + // PN_CPP_EXTERN transaction(transaction &o); + PN_CPP_EXTERN transaction(); + PN_CPP_EXTERN ~transaction(); + PN_CPP_EXTERN bool is_empty(); + PN_CPP_EXTERN void commit(); + PN_CPP_EXTERN void abort(); + PN_CPP_EXTERN void declare(); + PN_CPP_EXTERN void handle_outcome(proton::tracker); + PN_CPP_EXTERN proton::tracker send(proton::sender s, proton::message msg); + PN_CPP_EXTERN void accept(delivery &t); + PN_CPP_EXTERN proton::connection connection() const; + + friend class transaction_impl; + friend class session; +}; + +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 + +#endif // PROTON_TRANSACTION_HPP diff --git a/cpp/include/proton/transfer.hpp b/cpp/include/proton/transfer.hpp index cf0474a75..ada5305c5 100644 --- a/cpp/include/proton/transfer.hpp +++ b/cpp/include/proton/transfer.hpp @@ -33,9 +33,26 @@ /// @copybrief proton::transfer struct pn_delivery_t; +// struct pn_disposition_t; namespace proton { +// class disposition : public internal::object { +// /// @cond INTERNAL +// disposition(pn_disposition_t *d) : internal::object(d) {} +// /// @endcond + +// public: +// /// Create an empty disposition. +// disposition() : internal::object(0) {} + +// proton::value data() const; + +// /// @cond INTERNAL +// friend class internal::factory; +// /// @endcond +// }; + /// The base class for delivery and tracker. class transfer : public internal::object { /// @cond INTERNAL @@ -77,20 +94,28 @@ class transfer : public internal::object { /// Return true if the transfer has been settled. PN_CPP_EXTERN bool settled() const; + // Set transaction + PN_CPP_EXTERN void transaction(transaction t); + + PN_CPP_EXTERN class transaction transaction() const; + /// Set user data on this transfer. PN_CPP_EXTERN void user_data(void* user_data) const; /// Get user data from this transfer. PN_CPP_EXTERN void* user_data() const; + // PN_CPP_EXTERN disposition remote(); + // PN_CPP_EXTERN disposition local(); + /// @cond INTERNAL friend class internal::factory; /// @endcond }; -/// Human-readalbe name of the transfer::state +/// Human-readable name of the transfer::state PN_CPP_EXTERN std::string to_string(enum transfer::state); -/// Human-readalbe name of the transfer::state +/// Human-readable name of the transfer::state PN_CPP_EXTERN std::ostream& operator<<(std::ostream&, const enum transfer::state); } // proton diff --git a/cpp/src/contexts.hpp b/cpp/src/contexts.hpp index 7ebab1d7b..e496e7578 100644 --- a/cpp/src/contexts.hpp +++ b/cpp/src/contexts.hpp @@ -25,6 +25,7 @@ #include "reconnect_options_impl.hpp" #include "proton/work_queue.hpp" +#include "proton/transaction.hpp" #include "proton/message.hpp" #include "proton/object.h" @@ -41,6 +42,7 @@ namespace proton { class proton_handler; class connector; +class transaction; namespace io {class link_namer;} @@ -161,6 +163,7 @@ class transfer_context : public context { transfer_context() : user_data_(nullptr) {} static transfer_context& get(pn_delivery_t* s); + std::unique_ptr transaction_; void* user_data_; }; diff --git a/cpp/src/handler.cpp b/cpp/src/handler.cpp index 1632efda6..d9bb34313 100644 --- a/cpp/src/handler.cpp +++ b/cpp/src/handler.cpp @@ -34,6 +34,7 @@ #include "proton/connection.h" #include "proton/session.h" +#include "proton/tracker.hpp" namespace proton { diff --git a/cpp/src/messaging_adapter.cpp b/cpp/src/messaging_adapter.cpp index f90cd7613..3eb0c11f1 100644 --- a/cpp/src/messaging_adapter.cpp +++ b/cpp/src/messaging_adapter.cpp @@ -30,6 +30,7 @@ #include "proton/receiver_options.hpp" #include "proton/sender.hpp" #include "proton/sender_options.hpp" +#include "proton/target_options.hpp" #include "proton/session.hpp" #include "proton/tracker.hpp" #include "proton/transport.hpp" @@ -40,6 +41,7 @@ #include #include +#include #include #include #include @@ -69,7 +71,8 @@ void on_link_flow(messaging_handler& handler, pn_event_t* event) { // TODO: process session flow data, if no link-specific data, just return. if (!lnk) return; int state = pn_link_state(lnk); - if ((state&PN_LOCAL_ACTIVE) && (state&PN_REMOTE_ACTIVE)) { + if (pn_terminus_get_type(pn_link_remote_target(lnk)) == PN_COORDINATOR || + ((state & PN_LOCAL_ACTIVE) && (state & PN_REMOTE_ACTIVE))) { link_context& lctx = link_context::get(lnk); if (pn_link_is_sender(lnk)) { if (pn_link_credit(lnk) > 0) { @@ -109,14 +112,37 @@ void message_decode(message& msg, proton::delivery delivery) { msg.decode(buf); pn_link_advance(unwrap(link)); } - void on_delivery(messaging_handler& handler, pn_event_t* event) { pn_link_t *lnk = pn_event_link(event); pn_delivery_t *dlv = pn_event_delivery(event); link_context& lctx = link_context::get(lnk); Tracing& ot = Tracing::getTracing(); + if (pn_terminus_get_type(pn_link_remote_target(lnk))==PN_COORDINATOR) { + std::cout << " on_delivery: COOORINDATOR.. TRACKER MADE: " + << std::endl; - if (pn_link_is_receiver(lnk)) { + if (pn_delivery_updated(dlv)) { + tracker t(make_wrapper(dlv)); + ot.on_settled_span(t); + switch (pn_delivery_remote_state(dlv)) { + case PN_ACCEPTED: + handler.on_tracker_accept(t); + break; + case PN_REJECTED: + handler.on_tracker_reject(t); + break; + case PN_RELEASED: + case PN_MODIFIED: + handler.on_tracker_release(t); + break; + } + if (t.settled()) { + handler.on_tracker_settle(t); + if (lctx.auto_settle) + t.settle(); + } + } + } else if (pn_link_is_receiver(lnk)) { delivery d(make_wrapper(dlv)); if (pn_delivery_aborted(dlv)) { pn_delivery_settle(dlv); @@ -274,23 +300,39 @@ void on_link_local_open(messaging_handler& handler, pn_event_t* event) { void on_link_remote_open(messaging_handler& handler, pn_event_t* event) { auto lnk = pn_event_link(event); - // Currently don't implement (transaction) coordinator + int type = pn_terminus_get_type(pn_link_remote_target(lnk)); + std::cout << " on_link_remote_open, type:" << type << std::endl; if (pn_terminus_get_type(pn_link_remote_target(lnk))==PN_COORDINATOR) { - auto error = pn_link_condition(lnk); - pn_condition_set_name(error, "amqp:not-implemented"); - pn_link_close(lnk); + auto cond = pn_link_condition(lnk); + if (pn_condition_is_set(cond)) { + std::cout<<" Got condition on_link_remote_open(.PN_COORDINATOR): " + << pn_event_type_name(pn_event_type(event)) << " " + << pn_condition_get_name(cond) << " " + << pn_condition_get_description(cond) << std::endl; + + pn_condition_set_name(cond, "amqp:on_link_remote_open:FAILED"); + pn_link_close(lnk); + return; + } + std::cout<<" IN on_link_remote_open(.PN_COORDINATOR) success " << std::endl; + std::cout<<" IN on_link_remote_open(.PN_COORDINATOR) have handler " << &handler << std::endl; + return; } if (pn_link_state(lnk) & PN_LOCAL_UNINIT) { // Incoming link // Copy source and target from remote end. + std::cout<<" Inside on_link_remote_open() .. PN_LOCAL_UNINIT " << std::endl; + pn_terminus_copy(pn_link_source(lnk), pn_link_remote_source(lnk)); pn_terminus_copy(pn_link_target(lnk), pn_link_remote_target(lnk)); } if (pn_link_is_receiver(lnk)) { + std::cout<<" Inside on_link_remote_open() .. pn_link_is_receiver " << std::endl; receiver r(make_wrapper(lnk)); handler.on_receiver_open(r); credit_topup(lnk); } else { + std::cout<<" Inside on_link_remote_open() .. sender " << std::endl; sender s(make_wrapper(lnk)); handler.on_sender_open(s); } diff --git a/cpp/src/node_options.cpp b/cpp/src/node_options.cpp index fd489baf3..ad8293efc 100644 --- a/cpp/src/node_options.cpp +++ b/cpp/src/node_options.cpp @@ -162,6 +162,7 @@ class target_options::impl { option expiry_policy; option > capabilities; option dynamic_properties; + option type; void apply(target& t) { node_address(t, address, dynamic, anonymous); @@ -175,6 +176,9 @@ class target_options::impl { get(dynamic_properties.value, target_map); value(pn_terminus_properties(unwrap(t))) = target_map; } + if (type.set) { + pn_terminus_set_type(unwrap(t), pn_terminus_type_t(type.value)); + } } }; @@ -200,6 +204,7 @@ target_options& target_options::dynamic_properties(const target::dynamic_propert impl_->dynamic_properties = c; return *this; } +target_options& target_options::type(int t) { impl_->type = t; return *this;} void target_options::apply(target& s) const { impl_->apply(s); } diff --git a/cpp/src/proactor_container_impl.cpp b/cpp/src/proactor_container_impl.cpp index 228002e38..34d1e19cc 100644 --- a/cpp/src/proactor_container_impl.cpp +++ b/cpp/src/proactor_container_impl.cpp @@ -26,6 +26,7 @@ #include "proton/listener.hpp" #include "proton/reconnect_options.hpp" #include "proton/ssl.hpp" +#include "proton/target_options.hpp" #include "proton/transport.hpp" #include "proton/url.hpp" @@ -33,12 +34,17 @@ #include "proton/listener.h" #include "proton/proactor.h" #include "proton/transport.h" +#include "proton/transaction.hpp" + +#include "proton/delivery.h" #include "contexts.hpp" #include "messaging_adapter.hpp" #include "reconnect_options_impl.hpp" #include "proton_bits.hpp" +#include + #include #include @@ -48,7 +54,7 @@ #include // XXXX: Debug -//#include +#include namespace proton { @@ -860,4 +866,45 @@ void container::impl::stop(const proton::error_condition& err) { pn_condition_free(error_condition); } +// TODO: declare this in separate internal header file +// extern transaction mk_transaction_impl(sender&, transaction_handler&, bool); + +// transaction container::impl::declare_transaction(proton::connection conn, proton::transaction_handler &handler, bool settle_before_discharge) { +// class InternalTransactionHandler : public proton::messaging_handler { +// // TODO: auto_settle + +// void on_tracker_settle(proton::tracker &t) override { +// std::cout<<" [InternalTransactionHandler][on_tracker_settle] called with tracker.txn" +// << std::endl; +// if (!t.transaction().is_empty()) { +// t.transaction().handle_outcome(t); +// } +// } +// }; + +// proton::target_options t; +// std::vector cap = {proton::symbol("amqp:local-transactions")}; +// t.capabilities(cap); +// t.type(PN_COORDINATOR); + +// proton::sender_options so; +// so.name("txn-ctrl"); +// so.target(t); +// static InternalTransactionHandler internal_handler; // internal_handler going out of scope. Fix it +// so.handler(internal_handler); +// std::cout<<" [declare_transaction] txn-name sender open with handler: " << &internal_handler << std::endl; + +// static proton::sender s = conn.open_sender("does not matter", so); + +// settle_before_discharge = false; + +// std::cout<<" [declare_transaction] calling mk_transaction_impl" << std::endl; + +// auto txn = +// transaction::mk_transaction_impl(s, handler, settle_before_discharge); +// std::cout<<" [declare_transaction] txn address:" << &txn << std::endl; + +// return txn; +// } + } diff --git a/cpp/src/proactor_container_impl.hpp b/cpp/src/proactor_container_impl.hpp index 1f87e7636..5d8b92a8b 100644 --- a/cpp/src/proactor_container_impl.hpp +++ b/cpp/src/proactor_container_impl.hpp @@ -91,6 +91,7 @@ class container::impl { template static messaging_handler* get_handler(T s); messaging_handler* get_handler(pn_event_t *event); static work_queue::impl* make_work_queue(container&); + transaction declare_transaction(proton::connection conn, proton::transaction_handler &handler, bool settle_before_discharge = false); private: class common_work_queue; diff --git a/cpp/src/proton_bits.hpp b/cpp/src/proton_bits.hpp index 48b9f5fd8..b4350e0f6 100644 --- a/cpp/src/proton_bits.hpp +++ b/cpp/src/proton_bits.hpp @@ -20,6 +20,7 @@ */ #include #include +#include #include #include @@ -41,6 +42,7 @@ struct pn_connection_t; struct pn_session_t; struct pn_link_t; struct pn_delivery_t; +struct pn_disposition_t; struct pn_condition_t; struct pn_acceptor_t; struct pn_terminus_t; @@ -60,6 +62,7 @@ class sender; class receiver; class transfer; class tracker; +class disposition; class delivery; class error_condition; class acceptor; @@ -98,6 +101,9 @@ template <> struct wrapped { typedef pn_link_t type; }; template <> struct wrapped { typedef pn_link_t type; }; template <> struct wrapped { typedef pn_delivery_t type; }; template <> struct wrapped { typedef pn_delivery_t type; }; +template <> struct wrapped { + typedef pn_disposition_t type; +}; template <> struct wrapped { typedef pn_delivery_t type; }; template <> struct wrapped { typedef pn_condition_t type; }; template <> struct wrapped { typedef pn_terminus_t type; }; @@ -111,6 +117,9 @@ template <> struct wrapper { typedef connection type; }; template <> struct wrapper { typedef session type; }; template <> struct wrapper { typedef link type; }; template <> struct wrapper { typedef transfer type; }; +template <> struct wrapper { + typedef disposition type; +}; template <> struct wrapper { typedef error_condition type; }; template <> struct wrapper { typedef terminus type; }; diff --git a/cpp/src/session.cpp b/cpp/src/session.cpp index b8f777a00..148d35993 100644 --- a/cpp/src/session.cpp +++ b/cpp/src/session.cpp @@ -24,6 +24,9 @@ #include "proton/receiver_options.hpp" #include "proton/sender_options.hpp" #include "proton/session_options.hpp" +#include "proton/target_options.hpp" +#include "proton/transaction.hpp" +#include "proton/messaging_handler.hpp" #include "contexts.hpp" #include "link_namer.hpp" @@ -34,6 +37,9 @@ #include +// XXXX: Debug +#include + namespace proton { session::~session() = default; @@ -148,4 +154,43 @@ void* session::user_data() const { return sctx.user_data_; } +transaction session::declare_transaction(proton::transaction_handler &handler, bool settle_before_discharge) { + proton::connection conn = this->connection(); + class InternalTransactionHandler : public proton::messaging_handler { + // TODO: auto_settle + + void on_tracker_settle(proton::tracker &t) override { + std::cout<<" [InternalTransactionHandler][on_tracker_settle] called with tracker.txn" + << std::endl; + if (!t.transaction().is_empty()) { + t.transaction().handle_outcome(t); + } + } + }; + + proton::target_options t; + std::vector cap = {proton::symbol("amqp:local-transactions")}; + t.capabilities(cap); + t.type(PN_COORDINATOR); + + proton::sender_options so; + so.name("txn-ctrl"); + so.target(t); + static InternalTransactionHandler internal_handler; // internal_handler going out of scope. Fix it + so.handler(internal_handler); + std::cout<<" [declare_transaction] txn-name sender open with handler: " << &internal_handler << std::endl; + + static proton::sender s = conn.open_sender("does not matter", so); + + settle_before_discharge = false; + + std::cout<<" [declare_transaction] calling mk_transaction_impl" << std::endl; + + auto txn = + transaction::mk_transaction_impl(s, handler, settle_before_discharge); + std::cout<<" [declare_transaction] txn address:" << &txn << std::endl; + + return txn; +} + } // namespace proton diff --git a/cpp/src/tracker.cpp b/cpp/src/tracker.cpp index e703767ef..5715f12b8 100644 --- a/cpp/src/tracker.cpp +++ b/cpp/src/tracker.cpp @@ -26,12 +26,13 @@ #include "proton_bits.hpp" #include "types_internal.hpp" #include "proton/binary.hpp" +#include "proton/transaction.hpp" #include namespace proton { -tracker::tracker(pn_delivery_t *d): transfer(make_wrapper(d)) {} +tracker::tracker(pn_delivery_t *d) : transfer(make_wrapper(d)) {} sender tracker::sender() const { return make_wrapper(pn_delivery_link(pn_object())); } binary tracker::tag() const { return bin(pn_delivery_tag(pn_object())); } } diff --git a/cpp/src/transaction.cpp b/cpp/src/transaction.cpp new file mode 100644 index 000000000..d21568519 --- /dev/null +++ b/cpp/src/transaction.cpp @@ -0,0 +1,250 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "proton/transaction.hpp" +#include "proton/delivery.h" +#include "proton/delivery.hpp" +#include "proton/message.hpp" +#include "proton/target_options.hpp" +#include "proton/tracker.hpp" +#include "proton/transfer.hpp" + +#include "proton_bits.hpp" +#include + +#include + +namespace proton { + +transaction_handler::~transaction_handler() = default; +void transaction_handler::on_transaction_declared(transaction) {} +void transaction_handler::on_transaction_committed(transaction) {} +void transaction_handler::on_transaction_aborted(transaction) {} +void transaction_handler::on_transaction_declare_failed(transaction) {} +void transaction_handler::on_transaction_commit_failed(transaction) {} + +transaction::transaction() : _impl(NULL) {} // empty transaction, not yet ready +// transaction::transaction(proton::sender& _txn_ctrl, +// proton::transaction_handler& _handler, bool _settle_before_discharge) : +// _impl(std::make_shared(_txn_ctrl, _handler, +// _settle_before_discharge)) {} +transaction::transaction(transaction_impl *impl) + : _impl(impl) {} +// transaction::transaction( transaction_impl* impl): _impl(impl){} +transaction::~transaction() = default; +void transaction::commit() { _impl->commit(); } +void transaction::abort() { _impl->abort(); } +void transaction::declare() { _impl->declare(); } +bool transaction::is_empty() { return _impl == NULL; } +void transaction::accept(delivery &t) { return _impl->accept(t); } +proton::tracker transaction::send(proton::sender s, proton::message msg) { + return _impl->send(s, msg); +} +void transaction::handle_outcome(proton::tracker t) { + std::cout << " transaction::handle_outcome = NO OP base class " + << std::endl; + _impl->handle_outcome(t); +} + +transaction_impl::transaction_impl(proton::sender &_txn_ctrl, + proton::transaction_handler &_handler, + bool _settle_before_discharge) + : txn_ctrl(_txn_ctrl), handler(&_handler) { + // bool settle_before_discharge = _settle_before_discharge; + declare(); +} + +void transaction_impl::commit() { + discharge(false); +} + +void transaction_impl::abort() { + discharge(true); +} + +void transaction_impl::declare() { + std::cout<<" [transaction_impl][declare] staring it" << std::endl; + + proton::symbol descriptor("amqp:declare:list"); + // proton::value _value = vd; + // TODO: How to make list; + std::list vd; + proton::value i_am_null; + vd.push_back(i_am_null); + proton::value _value = vd; + std::cout<<" [transaction_impl::declare()] value to send_ctrl: " << _value<< std::endl; + _declare = send_ctrl(descriptor, _value ); + std::cout << " transaction_impl::declare()... txn_impl i am is " << this + << std::endl; + std::cout << " [transaction_impl::declare()] _declare is : " << _declare + << std::endl; +} + +void transaction_impl::discharge(bool _failed) { + failed = _failed; + proton::symbol descriptor("amqp:discharge:list"); + std::list vd; + vd.push_back(id); + vd.push_back(failed); + proton::value _value = vd; + _discharge = send_ctrl(descriptor, _value); +} + +void transaction_impl::set_id(binary _id) { + std::cout << " TXN ID: " << _id << " from " << this << std::endl; + id = _id; +} + +proton::tracker transaction_impl::send_ctrl(proton::symbol descriptor, proton::value _value) { + proton::value msg_value; + proton::codec::encoder enc(msg_value); + enc << proton::codec::start::described() + << descriptor + << _value + << proton::codec::finish(); + + + proton::message msg = msg_value; + std::cout << " [transaction_impl::send_ctrl] sending " << msg << std::endl; + proton::tracker delivery = txn_ctrl.send(msg); + std::cout << " # declare, delivery as tracker: " << delivery + << std::endl; + delivery.transaction(transaction(this)); + std::cout + << " [transaction_impl::send_ctrl] sending done. I guess queued! " + << delivery << std::endl; + return delivery; +} + +proton::tracker transaction_impl::send(proton::sender s, proton::message msg) { + proton::tracker tracker = s.send(msg); + std::cout << " transaction_impl::send " << id << ", done: " << msg + << " tracker: " << tracker << std::endl; + update(tracker, 0x34); + std::cout << " transaction_impl::send, update" << std::endl; + return tracker; +} + +void transaction_impl::accept(delivery &t) { + // TODO: settle-before-discharge + t.settle(); + // pending.push_back(d); +} + +// TODO: use enum transfer::state +void transaction_impl::update(tracker &t, uint64_t state) { + if (state) { + proton::value data(pn_disposition_data(pn_delivery_local(unwrap(t)))); + std::list data_to_send; + data_to_send.push_back(id); + data = data_to_send; + + pn_delivery_update(unwrap(t), state); + // pn_delivery_settle(o); + // delivery.update(0x34) + } +} + +void transaction_impl::release_pending() { + for (auto d : pending) { + // d.update(released); + // d.settle(); + // TODO: fix it + delivery d2(make_wrapper(unwrap(d))); + d2.release(); + } + pending.clear(); +} + +void transaction_impl::handle_outcome(proton::tracker t) { + + // std::vector _data = + // proton::get>(val); + auto txn = t.transaction(); + std::cout << " ## handle_outcome::txn_impl i am is " << this << std::endl; + std::cout << " ## handle_outcome::_declare is " << _declare << std::endl; + std::cout << " ## handle_outcome::tracker is " << t << std::endl; + + pn_disposition_t *disposition = pn_delivery_remote(unwrap(t)); + // TODO: handle outcome + if(_declare == t) { + std::cout << " transaction_impl::handle_outcome => got _declare" + << std::endl; + proton::value val(pn_disposition_data(disposition)); + auto vd = get>(val); + if (vd.size() > 0) { + txn._impl->set_id(vd[0]); + std::cout << " transaction_impl: handle_outcome.. txn_declared " + "got txnid:: " + << vd[0] << std::endl; + handler->on_transaction_declared(txn); + } else if (pn_disposition_is_failed(disposition)) { + std::cout << " transaction_impl: handle_outcome.. " + "txn_declared_failed pn_disposition_is_failed " + << std::endl; + handler->on_transaction_declare_failed(txn); + } else { + std::cout + << " transaction_impl: handle_outcome.. txn_declared_failed " + << std::endl; + handler->on_transaction_declare_failed(txn); + } + } else if (_discharge == t) { + if (pn_disposition_is_failed(disposition)) { + if (!failed) { + std::cout + << " transaction_impl: handle_outcome.. commit failed " + << std::endl; + handler->on_transaction_commit_failed(txn); + // release pending + } + } else { + if (failed) { + handler->on_transaction_aborted(txn); + std::cout + << " transaction_impl: handle_outcome.. txn aborted" + << std::endl; + // release pending + } else { + handler->on_transaction_committed(txn); + std::cout + << " transaction_impl: handle_outcome.. txn commited" + << std::endl; + } + } + pending.clear(); + } else { + std::cout << " transaction_impl::handle_outcome => got NONE!" + << std::endl; + } +} + + +transaction transaction::mk_transaction_impl(sender &s, transaction_handler &h, + bool f) { + return transaction(new transaction_impl(s, h, f)); +} + +proton::connection transaction::connection() const { + return _impl->txn_ctrl.connection(); +} + +} diff --git a/cpp/src/transfer.cpp b/cpp/src/transfer.cpp index 063254267..ce3779dc2 100644 --- a/cpp/src/transfer.cpp +++ b/cpp/src/transfer.cpp @@ -22,6 +22,7 @@ #include "proton/delivery.hpp" #include "proton/connection.hpp" +// #include "proton/transaction.hpp" #include "proton/link.hpp" #include "proton/session.hpp" @@ -31,6 +32,7 @@ #include "proton_bits.hpp" +#include #include namespace proton { @@ -50,6 +52,16 @@ enum transfer::state transfer::state() const { return static_cast(pn std::string to_string(enum transfer::state s) { return pn_disposition_type_name(s); } std::ostream& operator<<(std::ostream& o, const enum transfer::state s) { return o << to_string(s); } +void transfer::transaction(proton::transaction t) { + transfer_context &cc = transfer_context::get(pn_object()); + cc.transaction_ = std::make_unique(t); +} + +transaction transfer::transaction() const { + transfer_context& cc = transfer_context::get(pn_object()); + return *cc.transaction_; +} + void transfer::user_data(void* user_data) const { transfer_context& cc = transfer_context::get(pn_object()); cc.user_data_ = user_data; @@ -60,4 +72,23 @@ void* transfer::user_data() const { return cc.user_data_; } +// disposition transfer::remote() { +// auto me = pn_object(); +// std::cout << " transfer::remote ME => " << me << std::endl; + +// auto dd = pn_delivery_remote(me); +// std::cout << " transfer::remote dd => " << dd << std::endl; +// std::cout << " transfer::remote.data dd => " +// << proton::value(pn_disposition_data(dd)) << std::endl; +// auto d2 = make_wrapper(dd); +// std::cout << " transfer::remote d2 ready => " << std::endl; +// return d2; +// } +// disposition transfer::local() { +// return make_wrapper(pn_delivery_local(pn_object())); +// } + +// proton::value disposition::data() const { +// return proton::value(pn_disposition_data(pn_object())); +// } } diff --git a/python/examples/broker.py b/python/examples/broker.py index 753b7af3f..3f59c9d3a 100755 --- a/python/examples/broker.py +++ b/python/examples/broker.py @@ -22,7 +22,7 @@ import optparse import uuid -from proton import Endpoint +from proton import Condition, Described, Disposition, Endpoint, Terminus from proton.handlers import MessagingHandler from proton.reactor import Container @@ -70,9 +70,11 @@ def _deliver_to(self, consumers): class Broker(MessagingHandler): def __init__(self, url): - super(Broker, self).__init__() + super().__init__(auto_accept=False) self.url = url self.queues = {} + self.txns = set() + self.acceptor = None def on_start(self, event): self.acceptor = event.container.listen(self.url) @@ -86,23 +88,63 @@ def on_connection_opening(self, event): event.connection.offered_capabilities = 'ANONYMOUS-RELAY' def on_link_opening(self, event): - if event.link.is_sender: - if event.link.remote_source.dynamic: + link = event.link + if link.is_sender: + if link.remote_source.dynamic: address = str(uuid.uuid4()) - event.link.source.address = address + link.source.address = address q = Queue(True) self.queues[address] = q - q.subscribe(event.link) - elif event.link.remote_source.address: - event.link.source.address = event.link.remote_source.address - self._queue(event.link.source.address).subscribe(event.link) - elif event.link.remote_target.address: - event.link.target.address = event.link.remote_target.address + q.subscribe(link) + elif link.remote_source.address: + link.source.address = link.remote_source.address + self._queue(link.source.address).subscribe(link) + elif link.remote_target.type == Terminus.COORDINATOR: + # Set up transaction coordinator + # Should check for compatible capabilities + # requested = link.remote_target.capabilities.get_object() + link.target.type = Terminus.COORDINATOR + link.target.copy(link.remote_target) + elif link.remote_target.address: + link.target.address = link.remote_target.address def _unsubscribe(self, link): if link.source.address in self.queues and self.queues[link.source.address].unsubscribe(link): del self.queues[link.source.address] + def _allocate_txn(self): + tid = bytes(str(uuid.uuid4()), 'UTF8') + self.txns.add(tid) + return tid + + def _settle_txn(self, tid): + self.txns.remove(tid) + + def _coordinator_message(self, msg, delivery): + body = msg.body + if isinstance(body, Described): + d = body.descriptor + if d == "amqp:declare:list": + # Allocate transaction id + tid = self._allocate_txn() + print(f"Declare: txn-id={tid}") + delivery.local.data = [tid] + delivery.update(0x33) + elif d == "amqp:discharge:list": + # Always accept commit/abort! + value = body.value + tid = bytes(value[0]) + failed = bool(value[1]) + if tid in self.txns: + print(f"Discharge: txn-id={tid}, failed={failed}") + self._settle_txn(tid) + delivery.update(Disposition.ACCEPTED) + else: + print(f"Discharge unknown txn-id: txn-id={tid}, failed={failed}") + delivery.local.condition = Condition('amqp:transaction:unknown-id') + delivery.update(Disposition.REJECTED) + delivery.settle() + def on_link_closing(self, event): if event.link.is_sender: self._unsubscribe(event.link) @@ -124,10 +166,62 @@ def on_sendable(self, event): self._queue(event.link.source.address).dispatch(event.link) def on_message(self, event): - address = event.link.target.address + link = event.link + delivery = event.delivery + msg = event.message + if link.target.type == Terminus.COORDINATOR: + # Deal with special transaction messages + self._coordinator_message(msg, delivery) + return + + address = link.target.address if address is None: - address = event.message.address + address = msg.address + + # Is this a transactioned message? + disposition = delivery.remote + if disposition.type == 0x34: + tid = bytes(disposition.data[0]) + if tid in self.txns: + print(f"Message: txn-id={tid}") + else: + print(f"Message unknown txn-id: txn-id={tid}") + delivery.local.condition = Condition('amqp:transaction:unknown-id') + delivery.update(Disposition.REJECTED) + delivery.settle() + return + self._queue(address).publish(event.message) + delivery.update(Disposition.ACCEPTED) + delivery.settle() + + def on_accepted(self, event): + delivery = event.delivery + print(f"Accept: delivery={delivery}") + + def on_rejected(self, event): + delivery = event.delivery + print(f"Reject: delivery={delivery}") + + def on_released(self, event): + delivery = event.delivery + print(f"Released: delivery={delivery}") + + def on_delivery_updated(self, event): + # Is this a transactioned delivery update? + delivery = event.delivery + disposition = delivery.remote + if disposition.type == 0x34: + tid = bytes(disposition.data[0]) + outcome = disposition.data[1] + if tid in self.txns: + print(f"Delivery update: txn-id={tid} outcome={outcome}") + else: + print(f"Message unknown txn-id: txn-id={tid}") + delivery.local.condition = Condition('amqp:transaction:unknown-id') + delivery.update(Disposition.REJECTED) + delivery.settle() + return def main(): diff --git a/python/proton/_handlers.py b/python/proton/_handlers.py index b225dda34..1771e3ad2 100644 --- a/python/proton/_handlers.py +++ b/python/proton/_handlers.py @@ -75,6 +75,8 @@ def on_delivery(self, event: Event): self.on_rejected(event) elif dlv.remote_state == Delivery.RELEASED or dlv.remote_state == Delivery.MODIFIED: self.on_released(event) + else: + self.on_delivery_updated(event) if dlv.settled: self.on_settled(event) if self.auto_settle: @@ -123,6 +125,17 @@ def on_released(self, event: Event): if self.delegate is not None: _dispatch(self.delegate, 'on_released', event) + def on_delivery_updated(self, event: Event): + """ + Called when the remote peer updates the status of a delivery to any state that is not + 'ACCEPT', 'REJECT', 'RELEASE' or 'MODIFY' + + :param event: The underlying event object. Use this to obtain further + information on the event. + """ + if self.delegate is not None: + _dispatch(self.delegate, 'on_delivery_updated', event) + def on_settled(self, event: Event): """ Called when the remote peer has settled the outgoing