|
| 1 | +/* |
| 2 | + * |
| 3 | + * Licensed to the Apache Software Foundation (ASF) under one |
| 4 | + * or more contributor license agreements. See the NOTICE file |
| 5 | + * distributed with this work for additional information |
| 6 | + * regarding copyright ownership. The ASF licenses this file |
| 7 | + * to you under the Apache License, Version 2.0 (the |
| 8 | + * "License"); you may not use this file except in compliance |
| 9 | + * with the License. You may obtain a copy of the License at |
| 10 | + * |
| 11 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | + * |
| 13 | + * Unless required by applicable law or agreed to in writing, |
| 14 | + * software distributed under the License is distributed on an |
| 15 | + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 16 | + * KIND, either express or implied. See the License for the |
| 17 | + * specific language governing permissions and limitations |
| 18 | + * under the License. |
| 19 | + * |
| 20 | + */ |
| 21 | + |
| 22 | +#include "options.hpp" |
| 23 | + |
| 24 | +#include <proton/connection.hpp> |
| 25 | +#include <proton/container.hpp> |
| 26 | +#include <proton/delivery.hpp> |
| 27 | +#include <proton/message.hpp> |
| 28 | +#include <proton/messaging_handler.hpp> |
| 29 | +#include <proton/receiver.hpp> |
| 30 | +#include <proton/receiver_options.hpp> |
| 31 | +#include <proton/session.hpp> |
| 32 | +#include <proton/transfer.hpp> |
| 33 | +#include <proton/work_queue.hpp> |
| 34 | + |
| 35 | +#include <algorithm> |
| 36 | +#include <condition_variable> |
| 37 | +#include <cstddef> |
| 38 | +#include <iostream> |
| 39 | +#include <mutex> |
| 40 | +#include <sstream> |
| 41 | +#include <string> |
| 42 | +#include <string_view> |
| 43 | +#include <thread> |
| 44 | +#include <vector> |
| 45 | + |
| 46 | +// Interactive transaction receiver: 'declare' to start a transaction, |
| 47 | +// 'fetch' [n] to receive one or n messages, 'commit' or 'abort' to finish, 'quit' to exit. |
| 48 | + |
| 49 | +class tx_recv_interactive : public proton::messaging_handler { |
| 50 | + private: |
| 51 | + std::string conn_url_; |
| 52 | + std::string addr_; |
| 53 | + |
| 54 | + proton::connection connection_; |
| 55 | + proton::receiver receiver_; |
| 56 | + proton::session session_; |
| 57 | + proton::work_queue* work_queue_ = nullptr; |
| 58 | + |
| 59 | + std::mutex lock_; |
| 60 | + std::condition_variable ready_cv_; |
| 61 | + bool ready_ = false; |
| 62 | + |
| 63 | + void do_declare() { |
| 64 | + session_.transaction_declare(); |
| 65 | + } |
| 66 | + |
| 67 | + void do_fetch(int n) { |
| 68 | + receiver_.add_credit(n); |
| 69 | + } |
| 70 | + |
| 71 | + void do_commit() { |
| 72 | + session_.transaction_commit(); |
| 73 | + } |
| 74 | + |
| 75 | + void do_abort() { |
| 76 | + session_.transaction_abort(); |
| 77 | + } |
| 78 | + |
| 79 | + void do_quit() { |
| 80 | + connection_.close(); |
| 81 | + } |
| 82 | + |
| 83 | + public: |
| 84 | + tx_recv_interactive(const std::string& url, const std::string& addr) |
| 85 | + : conn_url_(url), addr_(addr) {} |
| 86 | + |
| 87 | + void on_container_start(proton::container& c) override { |
| 88 | + c.connect(conn_url_); |
| 89 | + } |
| 90 | + |
| 91 | + void on_connection_open(proton::connection& conn) override { |
| 92 | + connection_ = conn; |
| 93 | + work_queue_ = &conn.work_queue(); |
| 94 | + // credit_window(0) so we control flow via "fetch" |
| 95 | + receiver_ = conn.open_receiver(addr_, proton::receiver_options().credit_window(0)); |
| 96 | + } |
| 97 | + |
| 98 | + void on_session_open(proton::session& s) override { |
| 99 | + session_ = s; |
| 100 | + { |
| 101 | + auto l = std::lock_guard(lock_); |
| 102 | + ready_ = true; |
| 103 | + } |
| 104 | + ready_cv_.notify_all(); |
| 105 | + } |
| 106 | + |
| 107 | + void on_session_transaction_declared(proton::session& s) override { |
| 108 | + std::cout << "transaction declared" << std::endl; |
| 109 | + } |
| 110 | + |
| 111 | + void on_session_transaction_committed(proton::session& s) override { |
| 112 | + std::cout << "transaction committed" << std::endl; |
| 113 | + } |
| 114 | + |
| 115 | + void on_session_transaction_aborted(proton::session& s) override { |
| 116 | + std::cout << "transaction aborted" << std::endl; |
| 117 | + for (auto rcv : s.receivers()) { |
| 118 | + for (auto t : rcv.unsettled_transfers()) { |
| 119 | + static_cast<proton::delivery&>(t).release(); |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + void on_session_transaction_error(proton::session& s) override { |
| 125 | + std::cout << "transaction error" << std::endl; |
| 126 | + for (auto rcv : s.receivers()) { |
| 127 | + for (auto t : rcv.unsettled_transfers()) { |
| 128 | + static_cast<proton::delivery&>(t).release(); |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + void on_message(proton::delivery& d, proton::message& msg) override { |
| 134 | + std::cout << d.tag() << ": " << msg.body() << std::endl; |
| 135 | + d.accept(); |
| 136 | + } |
| 137 | + |
| 138 | + void on_session_error(proton::session& s) override { |
| 139 | + std::cout << "Session error: " << s.error().what() << std::endl; |
| 140 | + s.connection().close(); |
| 141 | + } |
| 142 | + |
| 143 | + // Thread-safe: wait until handler is ready to accept commands |
| 144 | + void wait_ready() { |
| 145 | + auto l = std::unique_lock(lock_); |
| 146 | + ready_cv_.wait(l, [this] { return ready_; }); |
| 147 | + } |
| 148 | + |
| 149 | + // Thread-safe: schedule work on the container thread |
| 150 | + void declare() { |
| 151 | + work_queue_->add(proton::make_work(&tx_recv_interactive::do_declare, this)); |
| 152 | + } |
| 153 | + void fetch(int n) { |
| 154 | + work_queue_->add(proton::make_work(&tx_recv_interactive::do_fetch, this, n)); |
| 155 | + } |
| 156 | + void commit() { |
| 157 | + work_queue_->add(proton::make_work(&tx_recv_interactive::do_commit, this)); |
| 158 | + } |
| 159 | + void abort() { |
| 160 | + work_queue_->add(proton::make_work(&tx_recv_interactive::do_abort, this)); |
| 161 | + } |
| 162 | + /// List pending (unsettled) deliveries using the session's receiver iterator. |
| 163 | + void list_pending() { |
| 164 | + auto count = std::size_t(0); |
| 165 | + for (auto rcv : session_.receivers()) { |
| 166 | + for (auto t : rcv.unsettled_transfers()) { |
| 167 | + (void)t; |
| 168 | + ++count; |
| 169 | + } |
| 170 | + } |
| 171 | + std::cout << count << " pending delivery(ies)" << std::endl; |
| 172 | + for (auto rcv : session_.receivers()) { |
| 173 | + for (auto t : rcv.unsettled_transfers()) { |
| 174 | + auto& d = static_cast<proton::delivery&>(t); |
| 175 | + std::cout << " " << d.tag() << " disposition=" << d.state() << std::endl; |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | + void quit() { |
| 180 | + work_queue_->add(proton::make_work(&tx_recv_interactive::do_quit, this)); |
| 181 | + } |
| 182 | +}; |
| 183 | + |
| 184 | +using command_fn = bool (*)(tx_recv_interactive& recv, const std::vector<std::string>& args); |
| 185 | + |
| 186 | +static bool cmd_declare(tx_recv_interactive& recv, const std::vector<std::string>&) { |
| 187 | + recv.declare(); |
| 188 | + return false; |
| 189 | +} |
| 190 | +static bool cmd_fetch(tx_recv_interactive& recv, const std::vector<std::string>& args) { |
| 191 | + auto n = 1; |
| 192 | + if (!args.empty()) { |
| 193 | + try { |
| 194 | + n = std::stoi(args[0]); |
| 195 | + if (n < 1) n = 1; |
| 196 | + } catch (...) { |
| 197 | + std::cout << "fetch: expected positive number, got '" << args[0] << "'" << std::endl; |
| 198 | + return false; |
| 199 | + } |
| 200 | + } |
| 201 | + recv.fetch(n); |
| 202 | + return false; |
| 203 | +} |
| 204 | +static bool cmd_commit(tx_recv_interactive& recv, const std::vector<std::string>&) { |
| 205 | + recv.commit(); |
| 206 | + return false; |
| 207 | +} |
| 208 | +static bool cmd_abort(tx_recv_interactive& recv, const std::vector<std::string>&) { |
| 209 | + recv.abort(); |
| 210 | + return false; |
| 211 | +} |
| 212 | +static bool cmd_pending(tx_recv_interactive& recv, const std::vector<std::string>&) { |
| 213 | + recv.list_pending(); |
| 214 | + return false; |
| 215 | +} |
| 216 | +static bool cmd_quit(tx_recv_interactive&, const std::vector<std::string>&) { |
| 217 | + return true; |
| 218 | +} |
| 219 | + |
| 220 | +struct command_entry { |
| 221 | + const char* name; |
| 222 | + command_fn fn; |
| 223 | +}; |
| 224 | + |
| 225 | +// Lexicographically sorted by name for std::lower_bound lookup |
| 226 | +static constexpr command_entry COMMAND_TABLE[] = { |
| 227 | + {"abort", cmd_abort}, |
| 228 | + {"commit", cmd_commit}, |
| 229 | + {"declare", cmd_declare}, |
| 230 | + {"fetch", cmd_fetch}, |
| 231 | + {"pending", cmd_pending}, |
| 232 | + {"quit", cmd_quit}, |
| 233 | +}; |
| 234 | +static constexpr std::size_t COMMAND_TABLE_SIZE = sizeof(COMMAND_TABLE) / sizeof(COMMAND_TABLE[0]); |
| 235 | + |
| 236 | +static void print_command_list(std::ostream& os) { |
| 237 | + for (std::size_t i = 0; i < COMMAND_TABLE_SIZE; ++i) { |
| 238 | + if (i) os << ", "; |
| 239 | + os << COMMAND_TABLE[i].name; |
| 240 | + } |
| 241 | +} |
| 242 | + |
| 243 | +static std::vector<std::string> split_args(const std::string& line) { |
| 244 | + std::vector<std::string> out; |
| 245 | + std::istringstream is(line); |
| 246 | + for (std::string word; is >> word;) out.push_back(word); |
| 247 | + return out; |
| 248 | +} |
| 249 | + |
| 250 | +static const command_entry* find_command(std::string_view name) { |
| 251 | + auto it = std::lower_bound(std::begin(COMMAND_TABLE), std::end(COMMAND_TABLE), name, |
| 252 | + [](const command_entry& e, std::string_view s) { |
| 253 | + return std::string_view(e.name) < s; |
| 254 | + }); |
| 255 | + if (it != std::end(COMMAND_TABLE) && std::string_view(it->name) == name) |
| 256 | + return &*it; |
| 257 | + return nullptr; |
| 258 | +} |
| 259 | + |
| 260 | +static bool execute_command(tx_recv_interactive& recv, const command_entry& cmd, const std::vector<std::string>& args) { |
| 261 | + auto cmd_args = std::vector<std::string>(args.begin() + 1, args.end()); |
| 262 | + return cmd.fn(recv, cmd_args); |
| 263 | +} |
| 264 | + |
| 265 | +int main(int argc, char** argv) { |
| 266 | + auto conn_url = std::string("//127.0.0.1:5672"); |
| 267 | + auto addr = std::string("examples"); |
| 268 | + auto opts = example::options(argc, argv); |
| 269 | + |
| 270 | + opts.add_value(conn_url, 'u', "url", "connection URL", "URL"); |
| 271 | + opts.add_value(addr, 'a', "address", "address to receive messages from", "ADDR"); |
| 272 | + |
| 273 | + try { |
| 274 | + opts.parse(); |
| 275 | + |
| 276 | + auto recv = tx_recv_interactive(conn_url, addr); |
| 277 | + auto container = proton::container(recv); |
| 278 | + auto container_thread = std::thread([&container]() { container.run(); }); |
| 279 | + |
| 280 | + recv.wait_ready(); |
| 281 | + std::cout << "Commands: "; |
| 282 | + print_command_list(std::cout); |
| 283 | + std::cout << " (e.g. fetch 5)" << std::endl; |
| 284 | + |
| 285 | + auto line = std::string(); |
| 286 | + while (std::cout << "> " << std::flush, std::getline(std::cin, line)) { |
| 287 | + auto args = split_args(line); |
| 288 | + if (args.empty()) |
| 289 | + continue; |
| 290 | + auto* cmd = find_command(args[0]); |
| 291 | + if (cmd) { |
| 292 | + if (execute_command(recv, *cmd, args)) |
| 293 | + break; |
| 294 | + } else { |
| 295 | + std::cout << "Unknown command. Use "; |
| 296 | + print_command_list(std::cout); |
| 297 | + std::cout << "." << std::endl; |
| 298 | + } |
| 299 | + } |
| 300 | + |
| 301 | + recv.quit(); |
| 302 | + container_thread.join(); |
| 303 | + return 0; |
| 304 | + } catch (const example::bad_option& e) { |
| 305 | + std::cout << opts << std::endl << e.what() << std::endl; |
| 306 | + } catch (const std::exception& e) { |
| 307 | + std::cerr << e.what() << std::endl; |
| 308 | + } |
| 309 | + |
| 310 | + return 1; |
| 311 | +} |
0 commit comments