From 59c49aa5377d2d7a82194bf08f61b8fc1ce72bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20J=C4=99drzejewski?= Date: Thu, 26 Feb 2026 08:19:41 +0100 Subject: [PATCH] hmon: add C++ interface to heartbeat monitor Add C++ interface to heartbeat monitor. --- src/health_monitoring_lib/BUILD | 2 + .../cpp/health_monitor.cpp | 33 ++ .../cpp/heartbeat_monitor.cpp | 57 +++ .../cpp/include/score/hm/health_monitor.h | 6 + .../score/hm/heartbeat/heartbeat_monitor.h | 84 +++++ .../cpp/tests/health_monitor_test.cpp | 20 + .../rust/deadline/ffi.rs | 19 +- src/health_monitoring_lib/rust/ffi.rs | 341 +++++++++++++++++- .../rust/heartbeat/ffi.rs | 189 ++++++++++ .../rust/heartbeat/mod.rs | 3 + 10 files changed, 741 insertions(+), 13 deletions(-) create mode 100644 src/health_monitoring_lib/cpp/heartbeat_monitor.cpp create mode 100644 src/health_monitoring_lib/cpp/include/score/hm/heartbeat/heartbeat_monitor.h create mode 100644 src/health_monitoring_lib/rust/heartbeat/ffi.rs diff --git a/src/health_monitoring_lib/BUILD b/src/health_monitoring_lib/BUILD index 9c138eee..c58d9773 100644 --- a/src/health_monitoring_lib/BUILD +++ b/src/health_monitoring_lib/BUILD @@ -28,6 +28,7 @@ PROC_MACRO_DEPS = [ CC_SOURCES = [ "cpp/common.cpp", "cpp/deadline_monitor.cpp", + "cpp/heartbeat_monitor.cpp", "cpp/health_monitor.cpp", ] @@ -35,6 +36,7 @@ CC_HDRS = [ "cpp/include/score/hm/common.h", "cpp/include/score/hm/tag.h", "cpp/include/score/hm/deadline/deadline_monitor.h", + "cpp/include/score/hm/heartbeat/heartbeat_monitor.h", "cpp/include/score/hm/health_monitor.h", ] diff --git a/src/health_monitoring_lib/cpp/health_monitor.cpp b/src/health_monitoring_lib/cpp/health_monitor.cpp index ca4c8b2f..61b9a396 100644 --- a/src/health_monitoring_lib/cpp/health_monitor.cpp +++ b/src/health_monitoring_lib/cpp/health_monitor.cpp @@ -18,6 +18,7 @@ extern "C" { using namespace score::hm; using namespace score::hm::internal; using namespace score::hm::deadline; +using namespace score::hm::heartbeat; // Functions below must match functions defined in `crate::ffi`. @@ -30,9 +31,15 @@ FFICode health_monitor_builder_build(FFIHandle health_monitor_builder_handle, FFICode health_monitor_builder_add_deadline_monitor(FFIHandle health_monitor_builder_handle, const MonitorTag* monitor_tag, FFIHandle deadline_monitor_builder_handle); +FFICode health_monitor_builder_add_heartbeat_monitor(FFIHandle health_monitor_builder_handle, + const MonitorTag* monitor_tag, + FFIHandle heartbeat_monitor_builder_handle); FFICode health_monitor_get_deadline_monitor(FFIHandle health_monitor_handle, const MonitorTag* monitor_tag, FFIHandle* deadline_monitor_handle_out); +FFICode health_monitor_get_heartbeat_monitor(FFIHandle health_monitor_handle, + const MonitorTag* monitor_tag, + FFIHandle* heartbeat_monitor_handle_out); FFICode health_monitor_start(FFIHandle health_monitor_handle); FFICode health_monitor_destroy(FFIHandle health_monitor_handle); } @@ -71,6 +78,20 @@ HealthMonitorBuilder HealthMonitorBuilder::add_deadline_monitor(const MonitorTag return std::move(*this); } +HealthMonitorBuilder HealthMonitorBuilder::add_heartbeat_monitor(const MonitorTag& monitor_tag, + HeartbeatMonitorBuilder&& monitor) && +{ + auto monitor_handle = monitor.drop_by_rust(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle_.as_rust_handle().has_value()); + + auto result{health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle_.as_rust_handle().value(), &monitor_tag, monitor_handle.value())}; + SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); + + return std::move(*this); +} + HealthMonitorBuilder HealthMonitorBuilder::with_internal_processing_cycle(std::chrono::milliseconds cycle_duration) && { internal_processing_cycle_duration_ = cycle_duration; @@ -122,6 +143,18 @@ score::cpp::expected HealthMonitor::get_deadline_monitor return score::cpp::expected(DeadlineMonitor{handle}); } +score::cpp::expected HealthMonitor::get_heartbeat_monitor(const MonitorTag& monitor_tag) +{ + FFIHandle handle{nullptr}; + auto result{health_monitor_get_heartbeat_monitor(health_monitor_, &monitor_tag, &handle)}; + if (result != kSuccess) + { + return score::cpp::unexpected(static_cast(result)); + } + + return score::cpp::expected(HeartbeatMonitor{handle}); +} + void HealthMonitor::start() { auto result{health_monitor_start(health_monitor_)}; diff --git a/src/health_monitoring_lib/cpp/heartbeat_monitor.cpp b/src/health_monitoring_lib/cpp/heartbeat_monitor.cpp new file mode 100644 index 00000000..7a4b6b87 --- /dev/null +++ b/src/health_monitoring_lib/cpp/heartbeat_monitor.cpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/hm/heartbeat/heartbeat_monitor.h" + +namespace { +extern "C" { +using namespace score::hm; +using namespace score::hm::internal; +using namespace score::hm::heartbeat; + +FFICode heartbeat_monitor_builder_create(uint32_t range_min_ms, uint32_t range_max_ms, FFIHandle* heartbeat_monitor_builder_handle_out); +FFICode heartbeat_monitor_builder_destroy(FFIHandle heartbeat_monitor_builder_handle); +FFICode heartbeat_monitor_destroy(FFIHandle heartbeat_monitor_builder_handle); +FFICode heartbeat_monitor_heartbeat(FFIHandle heartbeat_monitor_builder_handle); +} + +FFIHandle heartbeat_monitor_builder_create_wrapper(uint32_t range_min_ms, uint32_t range_max_ms) +{ + FFIHandle handle{nullptr}; + auto result{heartbeat_monitor_builder_create(range_min_ms, range_max_ms, &handle)}; + SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); + return handle; +} + +} + +namespace score::hm::heartbeat +{ +HeartbeatMonitorBuilder::HeartbeatMonitorBuilder(const TimeRange& range) + : monitor_builder_handle_{heartbeat_monitor_builder_create_wrapper(range.min_ms(), range.max_ms()), + &heartbeat_monitor_builder_destroy} +{ +} + +HeartbeatMonitor::HeartbeatMonitor(FFIHandle monitor_handle) + : monitor_handle_{monitor_handle, &heartbeat_monitor_destroy} +{ +} + +void HeartbeatMonitor::heartbeat() +{ + auto monitor_handle{monitor_handle_.as_rust_handle()}; + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); + SCORE_LANGUAGE_FUTURECPP_ASSERT(heartbeat_monitor_heartbeat(monitor_handle.value()) == kSuccess); +} + +} // namespace score::hm::heartbeat diff --git a/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h b/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h index 369cd802..4ae11863 100644 --- a/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h +++ b/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h @@ -15,6 +15,7 @@ #include #include +#include #include namespace score::hm @@ -42,6 +43,10 @@ class HealthMonitorBuilder final HealthMonitorBuilder add_deadline_monitor(const MonitorTag& monitor_tag, deadline::DeadlineMonitorBuilder&& monitor) &&; + /// Adds a heartbeat monitor for a specific identifier tag. + HealthMonitorBuilder add_heartbeat_monitor(const MonitorTag& monitor_tag, + heartbeat::HeartbeatMonitorBuilder&& monitor) &&; + /// Sets the cycle duration for supervisor API notifications. /// This duration determines how often the health monitor notifies the supervisor that the system is alive. HealthMonitorBuilder with_supervisor_api_cycle(std::chrono::milliseconds cycle_duration) &&; @@ -72,6 +77,7 @@ class HealthMonitor final ~HealthMonitor(); score::cpp::expected get_deadline_monitor(const MonitorTag& monitor_tag); + score::cpp::expected get_heartbeat_monitor(const MonitorTag& monitor_tag); void start(); diff --git a/src/health_monitoring_lib/cpp/include/score/hm/heartbeat/heartbeat_monitor.h b/src/health_monitoring_lib/cpp/include/score/hm/heartbeat/heartbeat_monitor.h new file mode 100644 index 00000000..33662084 --- /dev/null +++ b/src/health_monitoring_lib/cpp/include/score/hm/heartbeat/heartbeat_monitor.h @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_HEARTBEAT_HEARTBEAT_MONITOR_H +#define SCORE_HM_HEARTBEAT_HEARTBEAT_MONITOR_H + +#include +#include + +namespace score::hm +{ +// Forward declaration +class HealthMonitor; +class HealthMonitorBuilder; +} // namespace score::hm + +namespace score::hm::heartbeat +{ +// Forward declaration +class HeartbeatMonitor; + +/// Builder for `HeartbeatMonitor`. +class HeartbeatMonitorBuilder final : public internal::RustDroppable +{ + public: + /// Create a new `HeartbeatMonitorBuilder`. + /// + /// - `range` - time range between heartbeats. + HeartbeatMonitorBuilder(const TimeRange& range); + + HeartbeatMonitorBuilder(const HeartbeatMonitorBuilder&) = delete; + HeartbeatMonitorBuilder& operator=(const HeartbeatMonitorBuilder&) = delete; + + HeartbeatMonitorBuilder(HeartbeatMonitorBuilder&&) = default; + HeartbeatMonitorBuilder& operator=(HeartbeatMonitorBuilder&&) = delete; + + protected: + std::optional _drop_by_rust_impl() + { + return monitor_builder_handle_.drop_by_rust(); + } + + private: + internal::DroppableFFIHandle monitor_builder_handle_; + + // Allow to hide drop_by_rust implementation + friend class internal::RustDroppable; + + // Allow HealthMonitorBuilder to access drop_by_rust implementation + friend class ::score::hm::HealthMonitorBuilder; +}; + +class HeartbeatMonitor final +{ + public: + // Delete copy, allow move + HeartbeatMonitor(const HeartbeatMonitor&) = delete; + HeartbeatMonitor& operator=(const HeartbeatMonitor&) = delete; + + HeartbeatMonitor(HeartbeatMonitor&& other) noexcept = default; + HeartbeatMonitor& operator=(HeartbeatMonitor&& other) noexcept = default; + + void heartbeat(); + + private: + explicit HeartbeatMonitor(internal::FFIHandle monitor_handle); + + // Only `HealthMonitor` is allowed to create `HeartbeatMonitor` instances. + friend class score::hm::HealthMonitor; + internal::DroppableFFIHandle monitor_handle_; +}; + +} // namespace score::hm::heartbeat + +#endif // SCORE_HM_HEARTBEAT_HEARTBEAT_MONITOR_H diff --git a/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp index 4c39ec90..cd0e6524 100644 --- a/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp +++ b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp @@ -44,8 +44,14 @@ TEST_F(HealthMonitorTest, TestName) .add_deadline(DeadlineTag("deadline_2"), TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))); + // Setup heartbeat monitor construction. + const MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; + const TimeRange heartbeat_range{std::chrono::milliseconds{100}, std::chrono::milliseconds{200}}; + auto heartbeat_monitor_builder = heartbeat::HeartbeatMonitorBuilder(heartbeat_range); + auto hm = HealthMonitorBuilder() .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) + .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) .with_internal_processing_cycle(std::chrono::milliseconds(50)) .with_supervisor_api_cycle(std::chrono::milliseconds(50)) .build(); @@ -62,9 +68,23 @@ TEST_F(HealthMonitorTest, TestName) auto deadline_mon = std::move(*deadline_monitor_res); + // Obtain heartbeat monitor from HMON. + auto heartbeat_monitor_res{hm.get_heartbeat_monitor(heartbeat_monitor_tag)}; + EXPECT_TRUE(heartbeat_monitor_res.has_value()); + + { + // Try again to get the same monitor. + auto heartbeat_monitor_res{hm.get_heartbeat_monitor(heartbeat_monitor_tag)}; + EXPECT_FALSE(heartbeat_monitor_res.has_value()); + } + + auto heartbeat_monitor{std::move(*heartbeat_monitor_res)}; + // Start HMON. hm.start(); + heartbeat_monitor.heartbeat(); + auto deadline_res = deadline_mon.get_deadline(DeadlineTag("deadline_1")); { diff --git a/src/health_monitoring_lib/rust/deadline/ffi.rs b/src/health_monitoring_lib/rust/deadline/ffi.rs index b6ae5a3a..fdee1fc1 100644 --- a/src/health_monitoring_lib/rust/deadline/ffi.rs +++ b/src/health_monitoring_lib/rust/deadline/ffi.rs @@ -81,10 +81,6 @@ pub extern "C" fn deadline_monitor_builder_add_deadline( return FFICode::NullParameter; } - if min_ms > max_ms { - return FFICode::InvalidArgument; - } - // SAFETY: // Validity of the pointer is ensured. // `DeadlineTag` type must be compatible between C++ and Rust. @@ -97,13 +93,14 @@ pub extern "C" fn deadline_monitor_builder_add_deadline( let mut deadline_monitor_builder = FFIBorrowed::new(unsafe { Box::from_raw(deadline_monitor_builder_handle as *mut DeadlineMonitorBuilder) }); - deadline_monitor_builder.add_deadline_internal( - deadline_tag, - TimeRange::new( - Duration::from_millis(min_ms as u64), - Duration::from_millis(max_ms as u64), - ), - ); + let range_min = Duration::from_millis(min_ms as u64); + let range_max = Duration::from_millis(max_ms as u64); + let range = match TimeRange::new_internal(range_min, range_max) { + Some(range) => range, + None => return FFICode::InvalidArgument, + }; + + deadline_monitor_builder.add_deadline_internal(deadline_tag, range); FFICode::Success } diff --git a/src/health_monitoring_lib/rust/ffi.rs b/src/health_monitoring_lib/rust/ffi.rs index fe86eb57..65d26610 100644 --- a/src/health_monitoring_lib/rust/ffi.rs +++ b/src/health_monitoring_lib/rust/ffi.rs @@ -12,6 +12,7 @@ // ******************************************************************************* use crate::deadline::ffi::DeadlineMonitorCpp; use crate::deadline::DeadlineMonitorBuilder; +use crate::heartbeat::HeartbeatMonitorBuilder; use crate::tag::MonitorTag; use crate::{HealthMonitor, HealthMonitorBuilder, HealthMonitorError}; use core::mem::ManuallyDrop; @@ -171,6 +172,40 @@ pub extern "C" fn health_monitor_builder_add_deadline_monitor( FFICode::Success } +#[unsafe(no_mangle)] +pub extern "C" fn health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle: FFIHandle, + monitor_tag: *const MonitorTag, + heartbeat_monitor_builder_handle: FFIHandle, +) -> FFICode { + if health_monitor_builder_handle.is_null() || monitor_tag.is_null() || heartbeat_monitor_builder_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // `MonitorTag` type must be compatible between C++ and Rust. + let monitor_tag = unsafe { *monitor_tag }; + + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `heartbeat_monitor_builder_create`. + // It is assumed that the pointer was not consumed by a call to `heartbeat_monitor_builder_destroy`. + let heartbeat_monitor_builder = + unsafe { Box::from_raw(heartbeat_monitor_builder_handle as *mut HeartbeatMonitorBuilder) }; + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_create`. + // It is assumed that the pointer was not consumed by calls to `health_monitor_builder_destroy` or `health_monitor_builder_build`. + let mut health_monitor_builder = + FFIBorrowed::new(unsafe { Box::from_raw(health_monitor_builder_handle as *mut HealthMonitorBuilder) }); + + health_monitor_builder.add_heartbeat_monitor_internal(monitor_tag, *heartbeat_monitor_builder); + + FFICode::Success +} + #[unsafe(no_mangle)] pub extern "C" fn health_monitor_get_deadline_monitor( health_monitor_handle: FFIHandle, @@ -202,6 +237,37 @@ pub extern "C" fn health_monitor_get_deadline_monitor( } } +#[unsafe(no_mangle)] +pub extern "C" fn health_monitor_get_heartbeat_monitor( + health_monitor_handle: FFIHandle, + monitor_tag: *const MonitorTag, + heartbeat_monitor_handle_out: *mut FFIHandle, +) -> FFICode { + if health_monitor_handle.is_null() || monitor_tag.is_null() || heartbeat_monitor_handle_out.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // `MonitorTag` type must be compatible between C++ and Rust. + let monitor_tag = unsafe { *monitor_tag }; + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_build`. + // It is assumed that the pointer was not consumed by a call to `health_monitor_destroy`. + let mut health_monitor = FFIBorrowed::new(unsafe { Box::from_raw(health_monitor_handle as *mut HealthMonitor) }); + + if let Some(heartbeat_monitor) = health_monitor.get_heartbeat_monitor(monitor_tag) { + unsafe { + *heartbeat_monitor_handle_out = Box::into_raw(Box::new(heartbeat_monitor)).cast(); + } + FFICode::Success + } else { + FFICode::NotFound + } +} + #[unsafe(no_mangle)] pub extern "C" fn health_monitor_start(health_monitor_handle: FFIHandle) -> FFICode { if health_monitor_handle.is_null() { @@ -244,10 +310,14 @@ mod tests { deadline_monitor_builder_create, deadline_monitor_builder_destroy, deadline_monitor_destroy, }; use crate::ffi::{ - health_monitor_builder_add_deadline_monitor, health_monitor_builder_build, health_monitor_builder_create, - health_monitor_builder_destroy, health_monitor_destroy, health_monitor_get_deadline_monitor, + health_monitor_builder_add_deadline_monitor, health_monitor_builder_add_heartbeat_monitor, + health_monitor_builder_build, health_monitor_builder_create, health_monitor_builder_destroy, + health_monitor_destroy, health_monitor_get_deadline_monitor, health_monitor_get_heartbeat_monitor, health_monitor_start, FFICode, FFIHandle, }; + use crate::heartbeat::ffi::{ + heartbeat_monitor_builder_create, heartbeat_monitor_builder_destroy, heartbeat_monitor_destroy, + }; use crate::tag::MonitorTag; use core::ptr::null_mut; @@ -455,6 +525,92 @@ mod tests { health_monitor_builder_destroy(health_monitor_builder_handle); } + #[test] + fn health_monitor_builder_add_heartbeat_monitor_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_add_heartbeat_monitor_result = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + assert_eq!(health_monitor_builder_add_heartbeat_monitor_result, FFICode::Success); + + // Clean-up. + health_monitor_builder_destroy(health_monitor_builder_handle); + } + + #[test] + fn health_monitor_builder_add_heartbeat_monitor_null_hmon_builder() { + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_add_heartbeat_monitor_result = health_monitor_builder_add_heartbeat_monitor( + null_mut(), + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + assert_eq!( + health_monitor_builder_add_heartbeat_monitor_result, + FFICode::NullParameter + ); + + // Clean-up. + heartbeat_monitor_builder_destroy(heartbeat_monitor_builder_handle); + } + + #[test] + fn health_monitor_builder_add_heartbeat_monitor_null_monitor_tag() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_add_heartbeat_monitor_result = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + null_mut(), + heartbeat_monitor_builder_handle, + ); + assert_eq!( + health_monitor_builder_add_heartbeat_monitor_result, + FFICode::NullParameter + ); + + // Clean-up. + heartbeat_monitor_builder_destroy(heartbeat_monitor_builder_handle); + health_monitor_builder_destroy(health_monitor_builder_handle); + } + + #[test] + fn health_monitor_builder_add_heartbeat_monitor_null_heartbeat_monitor_builder() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + + let health_monitor_builder_add_heartbeat_monitor_result = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + null_mut(), + ); + assert_eq!( + health_monitor_builder_add_heartbeat_monitor_result, + FFICode::NullParameter + ); + + // Clean-up. + health_monitor_builder_destroy(health_monitor_builder_handle); + } + #[test] fn health_monitor_get_deadline_monitor_succeeds() { let mut health_monitor_builder_handle: FFIHandle = null_mut(); @@ -636,6 +792,187 @@ mod tests { health_monitor_destroy(health_monitor_handle); } + #[test] + fn health_monitor_get_heartbeat_monitor_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_get_heartbeat_monitor_result = health_monitor_get_heartbeat_monitor( + health_monitor_handle, + &heartbeat_monitor_tag as *const MonitorTag, + &mut heartbeat_monitor_handle as *mut FFIHandle, + ); + assert!(!heartbeat_monitor_handle.is_null()); + assert_eq!(health_monitor_get_heartbeat_monitor_result, FFICode::Success); + + // Clean-up. + heartbeat_monitor_destroy(heartbeat_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_heartbeat_monitor_already_taken() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_1_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_2_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + // First get. + let health_monitor_get_heartbeat_monitor_result_1 = health_monitor_get_heartbeat_monitor( + health_monitor_handle, + &heartbeat_monitor_tag as *const MonitorTag, + &mut heartbeat_monitor_1_handle as *mut FFIHandle, + ); + assert!(!heartbeat_monitor_1_handle.is_null()); + assert_eq!(health_monitor_get_heartbeat_monitor_result_1, FFICode::Success); + + // Second get. + let health_monitor_get_heartbeat_monitor_result_2 = health_monitor_get_heartbeat_monitor( + health_monitor_handle, + &heartbeat_monitor_tag as *const MonitorTag, + &mut heartbeat_monitor_2_handle as *mut FFIHandle, + ); + assert!(heartbeat_monitor_2_handle.is_null()); + assert_eq!(health_monitor_get_heartbeat_monitor_result_2, FFICode::NotFound); + + // Clean-up. + heartbeat_monitor_destroy(heartbeat_monitor_1_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_heartbeat_monitor_null_hmon() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_get_heartbeat_monitor_result = health_monitor_get_heartbeat_monitor( + null_mut(), + &heartbeat_monitor_tag as *const MonitorTag, + &mut heartbeat_monitor_handle as *mut FFIHandle, + ); + assert!(heartbeat_monitor_handle.is_null()); + assert_eq!(health_monitor_get_heartbeat_monitor_result, FFICode::NullParameter); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_heartbeat_monitor_null_monitor_tag() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_get_heartbeat_monitor_result = health_monitor_get_heartbeat_monitor( + health_monitor_handle, + null_mut(), + &mut heartbeat_monitor_handle as *mut FFIHandle, + ); + assert!(heartbeat_monitor_handle.is_null()); + assert_eq!(health_monitor_get_heartbeat_monitor_result, FFICode::NullParameter); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_heartbeat_monitor_null_heartbeat_monitor() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_get_heartbeat_monitor_result = health_monitor_get_heartbeat_monitor( + health_monitor_handle, + &heartbeat_monitor_tag as *const MonitorTag, + null_mut(), + ); + assert_eq!(health_monitor_get_heartbeat_monitor_result, FFICode::NullParameter); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + #[test] fn health_monitor_start_succeeds() { let mut health_monitor_builder_handle: FFIHandle = null_mut(); diff --git a/src/health_monitoring_lib/rust/heartbeat/ffi.rs b/src/health_monitoring_lib/rust/heartbeat/ffi.rs new file mode 100644 index 00000000..4001e53a --- /dev/null +++ b/src/health_monitoring_lib/rust/heartbeat/ffi.rs @@ -0,0 +1,189 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* +use crate::common::TimeRange; +use crate::ffi::{FFIBorrowed, FFICode, FFIHandle}; +use crate::heartbeat::{HeartbeatMonitor, HeartbeatMonitorBuilder}; +use core::time::Duration; + +#[unsafe(no_mangle)] +pub extern "C" fn heartbeat_monitor_builder_create( + range_min_ms: u32, + range_max_ms: u32, + heartbeat_monitor_builder_handle_out: *mut FFIHandle, +) -> FFICode { + if heartbeat_monitor_builder_handle_out.is_null() { + return FFICode::NullParameter; + } + + let range_min = Duration::from_millis(range_min_ms as u64); + let range_max = Duration::from_millis(range_max_ms as u64); + let range = match TimeRange::new_internal(range_min, range_max) { + Some(range) => range, + None => return FFICode::InvalidArgument, + }; + + let heartbeat_monitor_builder = HeartbeatMonitorBuilder::new(range); + unsafe { + *heartbeat_monitor_builder_handle_out = Box::into_raw(Box::new(heartbeat_monitor_builder)).cast(); + } + + FFICode::Success +} + +#[unsafe(no_mangle)] +pub extern "C" fn heartbeat_monitor_builder_destroy(heartbeat_monitor_builder_handle: FFIHandle) -> FFICode { + if heartbeat_monitor_builder_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `heartbeat_monitor_builder_create`. + unsafe { + let _ = Box::from_raw(heartbeat_monitor_builder_handle as *mut HeartbeatMonitorBuilder); + } + + FFICode::Success +} + +#[unsafe(no_mangle)] +pub extern "C" fn heartbeat_monitor_destroy(heartbeat_monitor_handle: FFIHandle) -> FFICode { + if heartbeat_monitor_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_get_heartbeat_monitor`. + unsafe { + let _ = Box::from_raw(heartbeat_monitor_handle as *mut HeartbeatMonitor); + } + + FFICode::Success +} + +#[unsafe(no_mangle)] +pub extern "C" fn heartbeat_monitor_heartbeat(heartbeat_monitor_handle: FFIHandle) -> FFICode { + if heartbeat_monitor_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_get_heartbeat_monitor`. + // It is assumed that the pointer was not consumed by a call to `heartbeat_monitor_destroy`. + let monitor = FFIBorrowed::new(unsafe { Box::from_raw(heartbeat_monitor_handle as *mut HeartbeatMonitor) }); + + monitor.heartbeat(); + + FFICode::Success +} + +#[score_testing_macros::test_mod_with_log] +#[cfg(all(test, not(loom)))] +mod tests { + use crate::ffi::{ + health_monitor_builder_add_heartbeat_monitor, health_monitor_builder_build, health_monitor_builder_create, + health_monitor_destroy, health_monitor_get_heartbeat_monitor, FFICode, FFIHandle, + }; + use crate::heartbeat::ffi::{ + heartbeat_monitor_builder_create, heartbeat_monitor_builder_destroy, heartbeat_monitor_destroy, + heartbeat_monitor_heartbeat, + }; + use crate::tag::MonitorTag; + use core::ptr::null_mut; + + #[test] + fn heartbeat_monitor_builder_create_succeeds() { + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + + let heartbeat_monitor_builder_create_result = + heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + assert!(!heartbeat_monitor_builder_handle.is_null()); + assert_eq!(heartbeat_monitor_builder_create_result, FFICode::Success); + + // Clean-up. + // NOTE: `heartbeat_monitor_builder_destroy` positive path is already tested here. + let heartbeat_monitor_builder_destroy_result = + heartbeat_monitor_builder_destroy(heartbeat_monitor_builder_handle); + assert_eq!(heartbeat_monitor_builder_destroy_result, FFICode::Success); + } + + #[test] + fn heartbeat_monitor_builder_create_invalid_range() { + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + + let heartbeat_monitor_builder_create_result = + heartbeat_monitor_builder_create(10000, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + assert_eq!(heartbeat_monitor_builder_create_result, FFICode::InvalidArgument); + } + + #[test] + fn heartbeat_monitor_builder_create_null_builder() { + let heartbeat_monitor_builder_create_result = heartbeat_monitor_builder_create(100, 200, null_mut()); + assert_eq!(heartbeat_monitor_builder_create_result, FFICode::NullParameter); + } + + #[test] + fn heartbeat_monitor_builder_destroy_null_builder() { + let heartbeat_monitor_builder_destroy_result = heartbeat_monitor_builder_destroy(null_mut()); + assert_eq!(heartbeat_monitor_builder_destroy_result, FFICode::NullParameter); + } + + #[test] + fn heartbeat_monitor_destroy_null_monitor() { + let heartbeat_monitor_destroy_result = heartbeat_monitor_destroy(null_mut()); + assert_eq!(heartbeat_monitor_destroy_result, FFICode::NullParameter); + } + + #[test] + fn heartbeat_monitor_heartbeat_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_builder_handle: FFIHandle = null_mut(); + let mut heartbeat_monitor_handle: FFIHandle = null_mut(); + + let heartbeat_monitor_tag = MonitorTag::from("heartbeat_monitor"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = heartbeat_monitor_builder_create(100, 200, &mut heartbeat_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_heartbeat_monitor( + health_monitor_builder_handle, + &heartbeat_monitor_tag as *const MonitorTag, + heartbeat_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_heartbeat_monitor( + health_monitor_handle, + &heartbeat_monitor_tag as *const MonitorTag, + &mut heartbeat_monitor_handle as *mut FFIHandle, + ); + + let heartbeat_monitor_heartbeat_result = heartbeat_monitor_heartbeat(heartbeat_monitor_handle); + assert_eq!(heartbeat_monitor_heartbeat_result, FFICode::Success); + + // Clean-up. + heartbeat_monitor_destroy(heartbeat_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn heartbeat_monitor_heartbeat_null_monitor() { + let heartbeat_monitor_heartbeat_result = heartbeat_monitor_heartbeat(null_mut()); + assert_eq!(heartbeat_monitor_heartbeat_result, FFICode::NullParameter); + } +} diff --git a/src/health_monitoring_lib/rust/heartbeat/mod.rs b/src/health_monitoring_lib/rust/heartbeat/mod.rs index 4afe2ab5..a8b6e306 100644 --- a/src/health_monitoring_lib/rust/heartbeat/mod.rs +++ b/src/health_monitoring_lib/rust/heartbeat/mod.rs @@ -16,3 +16,6 @@ mod heartbeat_state; pub(crate) use heartbeat_monitor::HeartbeatEvaluationError; pub use heartbeat_monitor::{HeartbeatMonitor, HeartbeatMonitorBuilder}; + +// FFI bindings +pub(super) mod ffi;