PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::client::remote_node_manager::impl Struct Reference
Collaboration diagram for kcenon::pacs::client::remote_node_manager::impl:
Collaboration graph

Classes

struct  pooled_association
 

Public Member Functions

void notify_status_change (std::string_view node_id, node_status status)
 
void update_node_status (const std::string &node_id, node_status status, const std::string &error_msg="")
 
kcenon::pacs::VoidResult perform_echo (const remote_node &node)
 
void health_check_loop ()
 
void load_nodes_from_repo ()
 

Public Attributes

std::shared_ptr< storage::node_repositoryrepo
 
std::shared_ptr< di::ILoggerlogger
 
node_manager_config config
 
node_status_callback status_callback
 
std::mutex callback_mutex
 
std::unordered_map< std::string, remote_nodenode_cache
 
std::mutex cache_mutex
 
std::unordered_map< std::string, node_statisticsstatistics
 
std::mutex stats_mutex
 
std::unordered_map< std::string, std::deque< pooled_association > > connection_pool
 
std::mutex pool_mutex
 
std::atomic< bool > health_check_running {false}
 
std::thread health_check_thread
 
std::condition_variable health_check_cv
 
std::mutex health_check_mutex
 

Detailed Description

Definition at line 47 of file remote_node_manager.cpp.

Member Function Documentation

◆ health_check_loop()

void kcenon::pacs::client::remote_node_manager::impl::health_check_loop ( )
inline

Definition at line 211 of file remote_node_manager.cpp.

211 {
212 while (health_check_running.load()) {
213 // Verify all nodes
214 std::vector<std::string> node_ids;
215 {
216 std::lock_guard<std::mutex> lock(cache_mutex);
217 for (const auto& [id, _] : node_cache) {
218 node_ids.push_back(id);
219 }
220 }
221
222 for (const auto& id : node_ids) {
223 if (!health_check_running.load()) break;
224
225 std::optional<remote_node> node;
226 {
227 std::lock_guard<std::mutex> lock(cache_mutex);
228 auto it = node_cache.find(id);
229 if (it != node_cache.end()) {
230 node = it->second;
231 }
232 }
233
234 if (node) {
236
237 auto result = perform_echo(*node);
238 if (result.is_ok()) {
240
241 // Update statistics
242 std::lock_guard<std::mutex> lock(stats_mutex);
243 statistics[id].successful_operations++;
244 statistics[id].last_activity = std::chrono::system_clock::now();
245 } else {
246 update_node_status(id, node_status::offline, result.error().message);
247
248 // Update statistics
249 std::lock_guard<std::mutex> lock(stats_mutex);
250 statistics[id].failed_operations++;
251 }
252 }
253 }
254
255 // Wait for next interval
256 std::unique_lock<std::mutex> lock(health_check_mutex);
257 health_check_cv.wait_for(lock, config.health_check_interval, [this] {
258 return !health_check_running.load();
259 });
260 }
261 }
@ verifying
Verification in progress.
@ offline
Node is not responding.
@ online
Node is responding to C-ECHO.
@ id
Implant Displaced (alternate code)
std::chrono::seconds health_check_interval
Interval between automatic health checks.
void update_node_status(const std::string &node_id, node_status status, const std::string &error_msg="")
kcenon::pacs::VoidResult perform_echo(const remote_node &node)
std::unordered_map< std::string, remote_node > node_cache

References cache_mutex, config, health_check_cv, kcenon::pacs::client::node_manager_config::health_check_interval, health_check_mutex, health_check_running, node_cache, kcenon::pacs::client::offline, kcenon::pacs::client::online, perform_echo(), stats_mutex, update_node_status(), and kcenon::pacs::client::verifying.

Referenced by kcenon::pacs::client::remote_node_manager::start_health_check().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ load_nodes_from_repo()

void kcenon::pacs::client::remote_node_manager::impl::load_nodes_from_repo ( )
inline

Definition at line 263 of file remote_node_manager.cpp.

263 {
264 if (!repo) return;
265
266#ifdef PACS_WITH_DATABASE_SYSTEM
267 auto nodes_result = repo->find_all();
268 if (nodes_result.is_err()) return;
269
270 std::lock_guard<std::mutex> lock(cache_mutex);
271 for (auto& node : nodes_result.value()) {
272 node_cache[node.node_id] = std::move(node);
273 }
274#else
275 auto nodes = repo->find_all();
276 std::lock_guard<std::mutex> lock(cache_mutex);
277 for (auto& node : nodes) {
278 node_cache[node.node_id] = std::move(node);
279 }
280#endif
281 }
std::shared_ptr< storage::node_repository > repo

References cache_mutex, node_cache, and repo.

Referenced by kcenon::pacs::client::remote_node_manager::remote_node_manager().

Here is the caller graph for this function:

◆ notify_status_change()

void kcenon::pacs::client::remote_node_manager::impl::notify_status_change ( std::string_view node_id,
node_status status )
inline

Definition at line 87 of file remote_node_manager.cpp.

87 {
88 std::lock_guard<std::mutex> lock(callback_mutex);
89 if (status_callback) {
90 status_callback(node_id, status);
91 }
92 }

References callback_mutex, and status_callback.

Referenced by update_node_status().

Here is the caller graph for this function:

◆ perform_echo()

kcenon::pacs::VoidResult kcenon::pacs::client::remote_node_manager::impl::perform_echo ( const remote_node & node)
inline

Definition at line 123 of file remote_node_manager.cpp.

123 {
124 using namespace network;
125
126 // Build association config
127 association_config assoc_config;
128 assoc_config.calling_ae_title = config.local_ae_title;
129 assoc_config.called_ae_title = node.ae_title;
130 assoc_config.proposed_contexts.push_back({
131 1,
132 std::string(verification_sop_class_uid),
133 default_transfer_syntaxes
134 });
135
136 // Connect
137 auto connect_result = association::connect(
138 node.host,
139 node.port,
140 assoc_config,
141 std::chrono::duration_cast<association::duration>(node.connection_timeout)
142 );
143
144 if (connect_result.is_err()) {
147 "Failed to connect to node: " + node.node_id,
148 connect_result.error().message);
149 }
150
151 auto& assoc = connect_result.value();
152
153 // Get accepted context
154 auto context_id = assoc.accepted_context_id(verification_sop_class_uid);
155 if (!context_id) {
156 assoc.abort();
159 "Verification SOP Class not accepted by " + node.node_id);
160 }
161
162 // Build C-ECHO-RQ
163 auto echo_rq = dimse::make_c_echo_rq(1);
164
165 // Send request
166 auto send_result = assoc.send_dimse(*context_id, echo_rq);
167 if (send_result.is_err()) {
168 assoc.abort();
171 "Failed to send C-ECHO-RQ to " + node.node_id);
172 }
173
174 // Receive response
175 auto recv_result = assoc.receive_dimse(
176 std::chrono::duration_cast<association::duration>(node.dimse_timeout)
177 );
178
179 if (recv_result.is_err()) {
180 assoc.abort();
183 "Failed to receive C-ECHO-RSP from " + node.node_id);
184 }
185
186 const auto& [recv_context_id, response] = recv_result.value();
187
188 // Verify response
189 if (response.command() != dimse::command_field::c_echo_rsp) {
190 assoc.abort();
193 "Unexpected response from " + node.node_id);
194 }
195
196 // Check status (status_success is a constexpr, not enum member)
197 if (response.status() != dimse::status_success) {
198 [[maybe_unused]] auto release_result = assoc.release();
201 "C-ECHO failed with status: " +
202 std::to_string(static_cast<uint16_t>(response.status())));
203 }
204
205 // Release association
206 [[maybe_unused]] auto release_result = assoc.release();
207
208 return kcenon::pacs::ok();
209 }
constexpr int no_acceptable_context
Definition result.h:103
constexpr int receive_failed
Definition result.h:97
constexpr int send_failed
Definition result.h:96
constexpr int connection_failed
Definition result.h:94
constexpr int dimse_error
Definition result.h:90
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249
std::string local_ae_title
Our AE Title for outgoing associations.

References kcenon::pacs::client::remote_node::ae_title, config, kcenon::pacs::error_codes::connection_failed, kcenon::pacs::client::remote_node::connection_timeout, kcenon::pacs::error_codes::dimse_error, kcenon::pacs::client::remote_node::dimse_timeout, kcenon::pacs::client::remote_node::host, kcenon::pacs::client::node_manager_config::local_ae_title, kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::client::remote_node::node_id, kcenon::pacs::pacs_void_error(), kcenon::pacs::client::remote_node::port, kcenon::pacs::error_codes::receive_failed, and kcenon::pacs::error_codes::send_failed.

Referenced by health_check_loop(), and kcenon::pacs::client::remote_node_manager::verify_node().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ update_node_status()

void kcenon::pacs::client::remote_node_manager::impl::update_node_status ( const std::string & node_id,
node_status status,
const std::string & error_msg = "" )
inline

Definition at line 94 of file remote_node_manager.cpp.

95 {
96 // Update cache
97 {
98 std::lock_guard<std::mutex> lock(cache_mutex);
99 auto it = node_cache.find(node_id);
100 if (it != node_cache.end()) {
101 auto old_status = it->second.status;
102 it->second.status = status;
103 if (status == node_status::online) {
104 it->second.last_verified = std::chrono::system_clock::now();
105 } else if (status == node_status::error || status == node_status::offline) {
106 it->second.last_error = std::chrono::system_clock::now();
107 it->second.last_error_message = error_msg;
108 }
109
110 // Notify if status changed
111 if (old_status != status) {
112 notify_status_change(node_id, status);
113 }
114 }
115 }
116
117 // Update repository (ignore result as this is best-effort)
118 if (repo) {
119 [[maybe_unused]] auto result = repo->update_status(node_id, status, error_msg);
120 }
121 }
@ error
Node returned an error.
constexpr dicom_tag status
Status.
void notify_status_change(std::string_view node_id, node_status status)

References cache_mutex, kcenon::pacs::client::error, node_cache, notify_status_change(), kcenon::pacs::client::offline, kcenon::pacs::client::online, and repo.

Referenced by kcenon::pacs::client::remote_node_manager::acquire_association(), health_check_loop(), and kcenon::pacs::client::remote_node_manager::verify_node().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ cache_mutex

◆ callback_mutex

std::mutex kcenon::pacs::client::remote_node_manager::impl::callback_mutex

◆ config

◆ connection_pool

◆ health_check_cv

std::condition_variable kcenon::pacs::client::remote_node_manager::impl::health_check_cv

◆ health_check_mutex

std::mutex kcenon::pacs::client::remote_node_manager::impl::health_check_mutex

Definition at line 81 of file remote_node_manager.cpp.

Referenced by health_check_loop().

◆ health_check_running

std::atomic<bool> kcenon::pacs::client::remote_node_manager::impl::health_check_running {false}

◆ health_check_thread

std::thread kcenon::pacs::client::remote_node_manager::impl::health_check_thread

◆ logger

◆ node_cache

◆ pool_mutex

◆ repo

◆ statistics

◆ stats_mutex

◆ status_callback

node_status_callback kcenon::pacs::client::remote_node_manager::impl::status_callback

The documentation for this struct was generated from the following file: