HLCC Documentation 3.0.0
Loading...
Searching...
No Matches
requestor.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2020-2025 European Southern Observatory (ESO)
2//
3// SPDX-License-Identifier: LGPL-3.0-only
4
13#ifndef HLCC_CPPUTIL_REQUESTOR_HPP
14#define HLCC_CPPUTIL_REQUESTOR_HPP
15
16
17#include <atomic>
18
19#include <mal/Cii.hpp>
20#include <mal/utility/future.hpp>
21
22#include <rad/logger.hpp>
23
24
25
26namespace hlcc::cpputil {
27
28
38template <typename INTERFACE_TYPE>
39class Requestor {
40public:
45 explicit Requestor(log4cplus::Logger& logger)
46 : m_mutex {},
47 m_logger{logger},
48 m_uri{},
49 m_client{}
50 {
51 }
52
60 explicit Requestor(log4cplus::Logger& logger, const elt::mal::Uri& uri,
61 const std::optional<elt::mal::Mal::Properties> mal_properties = {})
62 : Requestor(logger) {
63 SetConnectionInfo(uri, mal_properties);
64 }
65
72 void SetConnectionInfo(const elt::mal::Uri& uri,
73 const std::optional<elt::mal::Mal::Properties> mal_properties = {}) {
74
75 std::lock_guard lck {m_mutex};
76
77 if (uri != m_uri) {
78 // Close previous instance. Probably would be done automatically after being dereferenced.
79 if (m_client) {
80 LOG4CPLUS_DEBUG(m_logger, "SetConnectionInfo will close old client");
81 m_client->close();
82 }
83
84 try {
85 // Create the MAL client.
86 // Note that (at least with ZMQ MAL), client construction immediately triggers an async connection attempt.
87 m_client = elt::mal::CiiFactory::getInstance().getClient<INTERFACE_TYPE>(
88 uri, elt::mal::rr::qos::QoS::DEFAULT,
89 mal_properties ? *mal_properties : elt::mal::Mal::Properties());
90 m_uri = uri;
91 LOG4CPLUS_DEBUG(m_logger, "Created rr client for " << uri);
92
93 // Register connection listener that logs all later connections and disconnections.
94 // This registration itself is async, which means that if immediately afterwards we call Connect
95 // or connect implicitly by using the interface, then the new connection may not be logged yet.
96 // We copy-capture the uri instead of using m_uri because these two may diverge when switching to a new URI.
97 m_client->registerConnectionListener([this, uri](bool connected) {
98 m_connected.store(connected);
99 LOG4CPLUS_DEBUG(m_logger, "Connected with " << uri << ": " << connected);
100 });
101 } catch (const std::exception& ex) {
102 // This happens, for example, when Nomad does not resolve the address and gives us a URI such as "zpb.rr:///TrackCmds".
103 // We just log the error and leave m_client empty or closed. A following call to Connect will fail.
104 LOG4CPLUS_WARN(m_logger, "Failed to create rr client for " << uri << ": " << ex.what());
105 }
106 }
107 }
108
115 std::shared_ptr<INTERFACE_TYPE>& GetInterface() {
116 std::lock_guard lck {m_mutex};
117 if (!m_client) {
118 LOG4CPLUS_DEBUG(m_logger, "Requestor::GetInterface() called without having an interface client. The user must call SetConnectionInfo first.");
119 // still we return the empty pointer.
120 }
121 return m_client;
122 }
123
135 bool Connect(std::chrono::seconds conn_timeout) {
136 std::lock_guard lck {m_mutex};
137 if (!m_client) {
138 LOG4CPLUS_DEBUG(m_logger, "Requestor::Connect() failed because of missing MAL client.");
139 return false;
140 }
141 if (m_connected) {
142 LOG4CPLUS_DEBUG(m_logger, "Requestor::Connect() skipped because it is already connected.");
143 return true;
144 }
145
146 elt::mal::future<void> conn_future = {};
147 try {
148 conn_future = m_client->asyncConnect();
149 } catch (const elt::mal::MalException & ex) {
150 // MAL header: if connect was called with already set promise
151 // Not sure how this would happen. Perhaps through a delay in callback update of m_connected?
152 LOG4CPLUS_WARN(m_logger, "Requestor::Connect() ClientAsync::asyncConnect call failed: " << ex.what());
153 return false;
154 }
155
156 ::boost::chrono::seconds conn_timeout_cii{conn_timeout.count()};
157 LOG4CPLUS_TRACE(m_logger, "Requestor::Connect() called asyncConnect() and will wait max " << conn_timeout_cii.count() << " s.");
158 bool success = false;
159 try {
160 auto future_status = conn_future.wait_for(conn_timeout_cii);
161 success = (future_status == boost::future_status::ready); // ready, timeout, deferred
162 LOG4CPLUS_TRACE(m_logger, "Requestor::Connect() returned from waiting for connection, success=" << success);
163 } catch (std::exception& ex) {
164 LOG4CPLUS_INFO(m_logger, "Failed to wait for connection: " << ex.what());
165 } catch (...) {
166 LOG4CPLUS_INFO(m_logger, "Failed to wait for connection. Non-std ex.");
167 }
168 m_connected = success; // registerConnectionListener has a delay, thus we set our flag directly
169 return success;
170 }
171
172 Requestor(const Requestor&) = delete;
173 Requestor& operator=(const Requestor&) = delete;
174
175private:
176 mutable std::recursive_mutex m_mutex;
177 log4cplus::Logger& m_logger;
178 elt::mal::Uri m_uri;
179 std::shared_ptr<INTERFACE_TYPE> m_client; // Share pointer to MAL Client
180
184 std::atomic<bool> m_connected{false};
185};
186
187
188
189} // namespace hlcc::cpputil
190
191#endif // HLCC_CPPUTIL_REQUESTOR_HPP
Definition requestor.hpp:39
Requestor(const Requestor &)=delete
Requestor(log4cplus::Logger &logger, const elt::mal::Uri &uri, const std::optional< elt::mal::Mal::Properties > mal_properties={})
Definition requestor.hpp:60
Requestor(log4cplus::Logger &logger)
Definition requestor.hpp:45
Requestor & operator=(const Requestor &)=delete
bool Connect(std::chrono::seconds conn_timeout)
Definition requestor.hpp:135
std::shared_ptr< INTERFACE_TYPE > & GetInterface()
Definition requestor.hpp:115
void SetConnectionInfo(const elt::mal::Uri &uri, const std::optional< elt::mal::Mal::Properties > mal_properties={})
Definition requestor.hpp:72
Definition ciiTypesToString.cpp:7