Skip to content

Commit 4848d87

Browse files
committed
WIP: Introduce C++ interactive tx recv tester
Probably shouldn't be in examples as it's really a tester
1 parent 26565da commit 4848d87

2 files changed

Lines changed: 313 additions & 1 deletion

File tree

cpp/examples/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ foreach(example
6262
multithreaded_client
6363
multithreaded_client_flow_control
6464
tx_send
65-
tx_recv)
65+
tx_recv
66+
tx_recv_interactive)
6667
add_executable(${example} ${example}.cpp)
6768
target_link_libraries(${example} Proton::cpp Threads::Threads)
6869
endforeach()
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
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

Comments
 (0)