diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..900517a2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,48 @@ +BasedOnStyle: Google +AccessModifierOffset: -2 +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +# Empty is required in AllowShortFunctionsOnASingleLine over Inline because Inline contradicts with AUTOSAR rule A7-1-7 +# Such rule is no longer existing in MISRA C++:2023, once we are fully migrated to MISRA C++:2023, switching to Inline +# could be reconsidered +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBraces: Custom +ColumnLimit: 120 +DerivePointerAlignment: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^(<|")(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdargh|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h(>|")$' + Priority: 2 + - Regex: '^(<|")(cstdlib|csignal|csetjmp|cstdarg|typeinfo|typeindex|type_traits|bitset|functional|utility|ctime|chrono|cstddef|initializer_list|tuple|any|optional|variant|new|memory|scoped_allocator|memory_resource|climits|cfloat|cstdint|cinttypes|limits|exception|stdexcept|cassert|system_error|cerrno|cctype|cwctype|cstring|cwchar|cuchar|string|string_view|array|vector|deque|list|forward_list|set|map|unordered_set|unordered_map|stack|queue|algorithm|execution|teratorslibrary|iterator|cmath|complex|valarray|random|numeric|ratio|cfenv|iosfwd|ios|istream|ostream|iostream|fstream|sstream|strstream|iomanip|streambuf|cstdio|locale|clocale|codecvt|regex|atomic|thread|mutex|shared_mutex|future|condition_variable|filesystem|ciso646|ccomplex|ctgmath|cstdalign|cstdbool)(>|")$' + Priority: 3 + - Regex: '^(<|").*(>|")$' + Priority: 1 +IndentWidth: 4 +InsertNewlineAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: true +QualifierAlignment: Left +CommentPragmas: '^.*A2Lfactory:' +--- +# Make sure language specific settings are below the generic settings to be compatible to all languages. +Language: Cpp +Standard: c++17 diff --git a/.gitignore b/.gitignore index d68c542c..0691dfdb 100644 --- a/.gitignore +++ b/.gitignore @@ -69,7 +69,6 @@ tests/**/*.html tests/**/*.xml # IDE Code files -.vscode* *.orig .venv_docs MODULE.lock.bazel diff --git a/.vscode/settings.json b/.vscode/settings.json index c2e7f411..cb47140a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,20 @@ "rust-analyzer.check.command": "clippy", "rust-analyzer.rustfmt.overrideCommand": [ "${workspaceFolder}/.vscode/rustfmt.sh" - ] + ], + "clangd.arguments": [ + "--header-insertion=never", + "--compile-commands-dir=${workspaceFolder}/", + "--query-driver=**", + "--clang-tidy", + "--fallback-style=None" + ], + "[cpp]": { + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", + "editor.formatOnSave": true + }, + "[c]": { + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", + "editor.formatOnSave": true + }, } diff --git a/MODULE.bazel b/MODULE.bazel index b2c05ae4..6a2de25d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,13 +24,13 @@ bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "flatbuffers", version = "25.9.23") bazel_dep(name = "download_utils", version = "1.0.1") +bazel_dep(name = "googletest", version = "1.17.0.bcr.1") # S-CORE process rules bazel_dep(name = "score_bazel_platforms", version = "0.0.3") -bazel_dep(name = "score_docs_as_code", version = "2.2.0") +bazel_dep(name = "score_docs_as_code", version = "2.3.0") bazel_dep(name = "score_tooling", version = "1.0.5") bazel_dep(name = "score_rust_policies", version = "0.0.3") -bazel_dep(name = "score_baselibs", version = "0.2.2") bazel_dep(name = "score_process", version = "1.4.0", dev_dependency = True) bazel_dep(name = "score_platform", version = "0.5.1", dev_dependency = True) @@ -41,8 +41,6 @@ bazel_dep(name = "score_toolchains_qnx", version = "0.0.6", dev_dependency = Tru bazel_dep(name = "rust_qnx8_toolchain", version = "1.2.0", dev_dependency = True) bazel_dep(name = "score_toolchains_rust", version = "0.1.1", dev_dependency = True) -bazel_dep(name = "googletest", version = "1.17.0.bcr.1") - # S-CORE crates bazel_dep(name = "score_crates", version = "0.0.6") @@ -106,3 +104,20 @@ use_repo(toolchains_qnx, "toolchains_qnx_qcc") use_repo(toolchains_qnx, "toolchains_qnx_ifs") bazel_dep(name = "score_baselibs_rust", version = "0.0.3") +bazel_dep(name = "score_baselibs", version = "0.2.2") +# git_override( +# module_name = "score_baselibs", +# commit = "9925dba1fd2ca7f2d33300cd2c01e6af022024cd", +# remote = "https://github.com/eclipse-score/baselibs.git", +# ) + +# Hedron's Compile Commands Extractor for Bazel +# https://github.com/hedronvision/bazel-compile-commands-extractor +bazel_dep(name = "hedron_compile_commands", dev_dependency = True) +git_override( + module_name = "hedron_compile_commands", + commit = "0e990032f3c5a866e72615cf67e5ce22186dcb97", + remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git", + # Replace the commit hash (above) with the latest (https://github.com/hedronvision/bazel-compile-commands-extractor/commits/main). + # Even better, set up Renovate and let it do the work for you (see "Suggestion: Updates" in the README). +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 716ee69b..6babb4e0 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -752,7 +752,8 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.0.2/MODULE.bazel": "eda1324a672604fedee96e29ca3324d585085df54a2e49e30527db410c98ff03", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.0.3/MODULE.bazel": "9b945514727190d4c381d8965b972884ba04ce105260ffd2b3c9df51f206ebfe", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.2.0/MODULE.bazel": "467d9b7f70f3c4f9ba84b5e9718da0272dcf8e30a737173bf79a48f017927744", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.2.0/source.json": "176af08bcfa30f5857cc6bcdd7fc2d737857d3cd8bc718c09544dba2cbba7d56", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.3.0/MODULE.bazel": "e215b29e2a737316af099d2f1372e303641b0720a67a104a286efeb66c92ff14", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.3.0/source.json": "3dfe1c6593e9acab12c6d7ca62bbaf9abbfe7d1c4a5e722dd8bc72f2b5e8fa4f", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_format_checker/0.1.1/MODULE.bazel": "1acc254faa90e9f97b79ac69af25b6c21c561f8d6079914f6352b9b20d26bd37", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.0/MODULE.bazel": "cc9eae86e76f2a930510ed6e50ec991bb5661687e24881685b39c322087adf6f", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.1/MODULE.bazel": "eb086ba99f9319371fbbd0a9252dfd27b0817039b88bd4d691602974b1ada005", @@ -6618,6 +6619,53 @@ ] ] } + }, + "@@score_toolchains_qnx+//:extensions.bzl%toolchains_qnx": { + "general": { + "bzlTransitiveDigest": "F6y2fAJJUfV3b6FNSGJSyi+Pa7AqT9NG+AhWCIySUwA=", + "usagesDigest": "57vskEtnheV4LMUX6fJDRfsaRXNH2xz9eQ0xsOMu6y8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "toolchains_qnx_sdp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://www.qnx.com/download/download/79858/installation.tgz" + ], + "build_file": "@@score_toolchains_qnx+//toolchains:sdp.BUILD", + "sha256": "f2e0cb21c6baddbcb65f6a70610ce498e7685de8ea2e0f1648f01b327f6bac63", + "strip_prefix": "installation" + } + }, + "toolchains_qnx_qcc": { + "repoRuleId": "@@score_toolchains_qnx+//toolchains:rules.bzl%qcc_toolchain", + "attributes": { + "sdp_repo": "toolchains_qnx_sdp", + "qcc_version": "12.2.0", + "sdp_version": "8.0.0", + "qnx_version_constraint": "@@score_bazel_platforms+//:qnx8_0", + "default_license_server": "" + } + }, + "toolchains_qnx_ifs": { + "repoRuleId": "@@score_toolchains_qnx+//toolchains:rules.bzl%ifs_toolchain", + "attributes": { + "sdp_repo": "toolchains_qnx_sdp", + "qnx_version_constraint": "@@score_bazel_platforms+//:qnx8_0", + "default_license_server": "" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "score_toolchains_qnx+", + "bazel_tools", + "bazel_tools" + ] + ] + } } } } diff --git a/README.md b/README.md index 73677732..f0fbc37f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,15 @@ bazel build --cxxopt=-DLC_LOG_SCORE_MW_LOG //... You can also use the config `--config=x86_64-linux` to build for linux. +## IDE support + +### C++ +Use Visual Studio Code with `clangd`. Make sure you don't have the MS C++ IntelliSense extension installed as this is likely to clash with `clangd`. +Then you need to call `./scripts/generate_cpp_compile_commands.sh` to generate compilation DB for clangd and restart it in VS Code. Indexing shall work. + +### Rust + + ### QNX #### Envionment Setup diff --git a/scripts/generate_cpp_compile_commands.sh b/scripts/generate_cpp_compile_commands.sh new file mode 100755 index 00000000..38959767 --- /dev/null +++ b/scripts/generate_cpp_compile_commands.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +bazel run @hedron_compile_commands//:refresh_all \ No newline at end of file diff --git a/src/health_monitoring_lib/BUILD b/src/health_monitoring_lib/BUILD index 0cc8957c..82909df8 100644 --- a/src/health_monitoring_lib/BUILD +++ b/src/health_monitoring_lib/BUILD @@ -11,7 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_static_library", "rust_test") +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_gtest_unit_test", "cc_unit_test_suites_for_host_and_qnx") rust_library( name = "health_monitoring_lib", @@ -26,6 +27,19 @@ rust_library( ], ) +rust_static_library( + name = "health_monitoring_lib_ffi", + srcs = glob(["src/**/*.rs"]), + crate_name = "health_monitoring_lib", + proc_macro_deps = [ + "@score_baselibs_rust//src/testing_macros:score_testing_macros", + ], + visibility = ["//visibility:public"], + deps = [ + "@score_baselibs_rust//src/log/score_log", + ], +) + rust_test( name = "tests", crate = ":health_monitoring_lib", @@ -33,3 +47,41 @@ rust_test( "@score_baselibs_rust//src/log/stdout_logger", ], ) + +cc_library( + name = "health_monitoring_lib_cc", + srcs = [ + "cpp/common.cpp", + "cpp/deadline/deadline_monitor.cpp", + "cpp/ffi_helpers.h", + "cpp/health_monitor.cpp", + ], + hdrs = [ + "cpp/include/score/hm/common.h", + "cpp/include/score/hm/deadline/deadline_monitor.h", + "cpp/include/score/hm/health_monitor.h", + ], + copts = [ + "-Isrc/health_monitoring_lib/cpp", # private include path + ], + strip_include_prefix = "cpp/include", + visibility = ["//visibility:public"], + deps = [ + ":health_monitoring_lib_ffi", + "@score_baselibs//score/result", + ], +) + +cc_gtest_unit_test( + name = "cpp_tests", + srcs = [ + "cpp/tests/health_monitor_test.cpp", + ], + linkopts = select({ + "@platforms//os:qnx": ["-lsocket"], + "//conditions:default": [], + }), + deps = [ + ":health_monitoring_lib_cc", + ], +) diff --git a/src/health_monitoring_lib/cpp/common.cpp b/src/health_monitoring_lib/cpp/common.cpp new file mode 100644 index 00000000..d85cfbec --- /dev/null +++ b/src/health_monitoring_lib/cpp/common.cpp @@ -0,0 +1,81 @@ +/******************************************************************************** + * 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 + +namespace score::hm::internal +{ + +DroppableFFIHandle::DroppableFFIHandle(FFIHandle handle, DropFn drop_fn) : handle_(handle), drop_fn_(drop_fn) {} + +DroppableFFIHandle::DroppableFFIHandle(DroppableFFIHandle&& other) noexcept + : handle_(other.handle_), drop_fn_(other.drop_fn_) +{ + other.handle_ = nullptr; + other.drop_fn_ = nullptr; +} + +DroppableFFIHandle& DroppableFFIHandle::operator=(DroppableFFIHandle&& other) noexcept +{ + if (this != &other) + { + // Clean up existing resources + if (drop_fn_) + { + drop_fn_(handle_); + } + + // Move resources from other + handle_ = other.handle_; + drop_fn_ = other.drop_fn_; + + // Nullify other's resources + other.handle_ = nullptr; + other.drop_fn_ = nullptr; + } + return *this; +} + +::score::cpp::optional DroppableFFIHandle::as_rust_handle() const +{ + if (handle_ == nullptr) + { + return ::score::cpp::nullopt; + } + + return handle_; +} + +::score::cpp::optional DroppableFFIHandle::drop_by_rust() +{ + if (handle_ == nullptr) + { + return ::score::cpp::nullopt; + } + + FFIHandle temp = handle_; + handle_ = nullptr; + drop_fn_ = nullptr; + + return temp; +} + +DroppableFFIHandle::~DroppableFFIHandle() +{ + // Clean up resources associated with the FFI handle + if (drop_fn_) + { + drop_fn_(handle_); + } +} + +} // namespace score::hm::internal diff --git a/src/health_monitoring_lib/cpp/deadline/deadline_monitor.cpp b/src/health_monitoring_lib/cpp/deadline/deadline_monitor.cpp new file mode 100644 index 00000000..b126ca5b --- /dev/null +++ b/src/health_monitoring_lib/cpp/deadline/deadline_monitor.cpp @@ -0,0 +1,130 @@ +/******************************************************************************** + * 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/deadline/deadline_monitor.h" +#include "ffi_helpers.h" + +extern "C" { +using namespace score::hm; +using namespace score::hm::internal; +using namespace score::hm::deadline; + +internal::FFIHandle deadline_monitor_builder_create(); +void deadline_monitor_builder_destroy(internal::FFIHandle handle); +void deadline_monitor_builder_add_deadline(internal::FFIHandle handler, + const IdentTag* tag, + uint32_t min, + uint32_t max); +int deadline_monitor_cpp_get_deadline(FFIHandle handler, const IdentTag* tag, FFIHandle* out); +void deadline_monitor_cpp_destroy(FFIHandle handler); +void deadline_destroy(FFIHandle deadline_handle); +int deadline_start(FFIHandle deadline_handle); +void deadline_stop(FFIHandle deadline_handle); +} + +namespace score::hm::deadline +{ +DeadlineMonitorBuilder::DeadlineMonitorBuilder() + : monitor_builder_handler_(deadline_monitor_builder_create(), &deadline_monitor_builder_destroy) +{ +} + +DeadlineMonitorBuilder DeadlineMonitorBuilder::add_deadline(const IdentTag& tag, const TimeRange& range) && +{ + auto handle = monitor_builder_handler_.as_rust_handle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + deadline_monitor_builder_add_deadline(handle.value(), &tag, range.min_as_u32(), range.max_as_u32()); + + return std::move(*this); +} + +DeadlineMonitor::DeadlineMonitor(FFIHandle handle) : monitor_handle_(handle, &deadline_monitor_cpp_destroy) {} + +score::cpp::expected DeadlineMonitor::get_deadline(const IdentTag& tag) +{ + auto handle = monitor_handle_.as_rust_handle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + internal::FFIHandle ret = nullptr; + auto result = deadline_monitor_cpp_get_deadline(handle.value(), &tag, &ret); + + if (result != kSuccess) + { + return score::cpp::unexpected(::score::hm::ffi::fromRustError(result)); + } + + return score::cpp::expected(Deadline{ret}); +} + +Deadline::Deadline(internal::FFIHandle handle) : deadline_handle_(handle, &deadline_destroy), has_handle_(false) {} + +Deadline::~Deadline() +{ + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(!has_handle_); +} + +score::cpp::expected Deadline::start() +{ + if (has_handle_) + { + return score::cpp::unexpected(::score::hm::Error::WrongState); + } + + auto handle = deadline_handle_.as_rust_handle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + auto result = deadline_start(handle.value()); + if (result != kSuccess) + { + return score::cpp::unexpected(::score::hm::ffi::fromRustError(result)); + } + + has_handle_ = true; + return score::cpp::expected(DeadlineHandle{*this}); +} + +DeadlineHandle::DeadlineHandle(Deadline& deadline) : was_stopped_(false), deadline_(deadline) {} + +void DeadlineHandle::stop() +{ + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(deadline_.has_value()); + + if (!was_stopped_) + { + was_stopped_ = true; + auto handle = deadline_.value().get().deadline_handle_.as_rust_handle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + deadline_stop(handle.value()); + } +} + +DeadlineHandle::DeadlineHandle(DeadlineHandle&& other) + : was_stopped_(other.was_stopped_), deadline_(std::move(other.deadline_)) +{ + other.was_stopped_ = true; + other.deadline_ = ::score::cpp::optional>{}; // None +} + +DeadlineHandle::~DeadlineHandle() +{ + if (!deadline_.has_value()) + { + return; + } + + stop(); + deadline_.value().get().has_handle_ = false; +} + +} // namespace score::hm::deadline diff --git a/src/health_monitoring_lib/cpp/ffi_helpers.h b/src/health_monitoring_lib/cpp/ffi_helpers.h new file mode 100644 index 00000000..96bf54c4 --- /dev/null +++ b/src/health_monitoring_lib/cpp/ffi_helpers.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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_FFI_HELPERS_HPP +#define SCORE_HM_FFI_HELPERS_HPP + +namespace score::hm::ffi +{ + +inline Error fromRustError(int ffi_error_code) +{ + switch (ffi_error_code) + { + case 1: + return Error::NotFound; + case 2: + return Error::AlreadyExists; + case 3: + return Error::InvalidArgument; + case 4: + return Error::WrongState; + case 5: + return Error::Failed; + default: + assert(false && "Unknown FFI error code"); + return Error::InvalidArgument; // Fallback + } +} + +} // namespace score::hm::ffi + +#endif // SCORE_HM_FFI_HELPERS_HPP diff --git a/src/health_monitoring_lib/cpp/health_monitor.cpp b/src/health_monitoring_lib/cpp/health_monitor.cpp new file mode 100644 index 00000000..abefd17c --- /dev/null +++ b/src/health_monitoring_lib/cpp/health_monitor.cpp @@ -0,0 +1,99 @@ +/******************************************************************************** + * 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/health_monitor.h" + +extern "C" { +using namespace score::hm; + +internal::FFIHandle health_monitor_builder_create(); +void health_monitor_builder_destroy(internal::FFIHandle handler); + +internal::FFIHandle health_monitor_builder_build(internal::FFIHandle health_monitor_builder_handle); +void health_monitor_builder_add_deadline_monitor(internal::FFIHandle handle, + const IdentTag* tag, + internal::FFIHandle monitor_handle); + +internal::FFIHandle health_monitor_get_deadline_monitor(internal::FFIHandle health_monitor_handle, const IdentTag* tag); + +void health_monitor_destroy(internal::FFIHandle handler); +} + +namespace score::hm +{ + +HealthMonitorBuilder::HealthMonitorBuilder() + : health_monitor_builder_handle_{health_monitor_builder_create(), &health_monitor_builder_destroy} +{ +} + +HealthMonitorBuilder HealthMonitorBuilder::add_deadline_monitor(const IdentTag& tag, + deadline::DeadlineMonitorBuilder&& 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()); + + health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle_.as_rust_handle().value(), &tag, monitor_handle.value()); + return std::move(*this); +} + +HealthMonitor HealthMonitorBuilder::build() && +{ + auto handle = health_monitor_builder_handle_.drop_by_rust(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + return HealthMonitor(health_monitor_builder_build(handle.value())); +} + +HealthMonitor::HealthMonitor(internal::FFIHandle handle) : health_monitor_(handle) +{ + // Initialize health monitor +} + +score::cpp::expected HealthMonitor::get_deadline_monitor(const IdentTag& tag) +{ + auto maybe_monitor = health_monitor_get_deadline_monitor(health_monitor_, &tag); + + if (maybe_monitor != nullptr) + { + + return score::cpp::expected(deadline::DeadlineMonitor{maybe_monitor}); + } + + return score::cpp::unexpected(Error::NotFound); +} + +HealthMonitor::~HealthMonitor() +{ + if (health_monitor_ != nullptr) + { + health_monitor_destroy(health_monitor_); + } +} + +HealthMonitor& HealthMonitor::operator=(HealthMonitor&& other) +{ + if (this != &other) + { + if (health_monitor_ != nullptr) + { + health_monitor_destroy(health_monitor_); + } + health_monitor_ = std::move(other.health_monitor_); + other.health_monitor_ = nullptr; + } + return *this; +} + +} // namespace score::hm diff --git a/src/health_monitoring_lib/cpp/include/score/hm/common.h b/src/health_monitoring_lib/cpp/include/score/hm/common.h new file mode 100644 index 00000000..a45d5e42 --- /dev/null +++ b/src/health_monitoring_lib/cpp/include/score/hm/common.h @@ -0,0 +1,129 @@ +/******************************************************************************** + * 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_COMMON_H +#define SCORE_HM_COMMON_H + +#include +#include +#include +#include +#include +#include +namespace score::hm +{ + +constexpr int kSuccess = 0; + +enum class Error +{ + NotFound = kSuccess + 1, + AlreadyExists = kSuccess + 2, + InvalidArgument = kSuccess + 3, + WrongState = kSuccess + 4, + Failed = kSuccess + 5 +}; + +/// +/// Identifier tag used to uniquely identify entities within the health monitoring system. +/// +class IdentTag +{ + public: + /// Create a new IdentTag from a C-style string. + template + explicit IdentTag(const char (&tag)[N]) : tag_(tag), len_(N - 1) + { + } + + private: + /// SAFETY: This has to be FFI compatible with the Rust side representation. + const char* const tag_; + size_t len_; +}; + +/// +/// Time range representation with minimum and maximum durations in milliseconds. +/// +class TimeRange +{ + public: + TimeRange(std::chrono::milliseconds min_ms, std::chrono::milliseconds max_ms) : min_ms(min_ms), max_ms(max_ms) {} + + const uint32_t min_as_u32() const + { + return min_ms.count(); + } + + const uint32_t max_as_u32() const + { + return max_ms.count(); + } + + private: + const std::chrono::milliseconds min_ms; + const std::chrono::milliseconds max_ms; +}; + +/// FFI internal helpers +namespace internal +{ + +/// Opaque handle type for Rust managed object +using FFIHandle = void*; + +/// Droppable wrapper that denotes that the object can be dropped by Rust side +template +class RustDroppable +{ + public: + ~RustDroppable() = default; + + /// Marks object as no longer managed by C++ side, releasing handle to be passed to Rust side for dropping + ::score::cpp::optional drop_by_rust() + { + return static_cast(this)->__drop_by_rust_impl(); + } +}; + +/// Wrapper for FFIHandle that ensures proper dropping via provided drop function +class DroppableFFIHandle +{ + public: + using DropFn = void (*)(FFIHandle); + + DroppableFFIHandle(FFIHandle handle, DropFn drop_fn); + + DroppableFFIHandle(const DroppableFFIHandle&) = delete; + DroppableFFIHandle& operator=(const DroppableFFIHandle&) = delete; + + DroppableFFIHandle(DroppableFFIHandle&& other) noexcept; + DroppableFFIHandle& operator=(DroppableFFIHandle&& other) noexcept; + + /// Get the underlying FFI handle if it was not dropped before + ::score::cpp::optional as_rust_handle() const; + + /// Marks object as no longer managed by C++ side, releasing handle to be passed to Rust side for dropping + ::score::cpp::optional drop_by_rust(); + + ~DroppableFFIHandle(); + + private: + FFIHandle handle_; + DropFn drop_fn_; +}; + +} // namespace internal + +} // namespace score::hm + +#endif // SCORE_HM_COMMON_H diff --git a/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h b/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h new file mode 100644 index 00000000..220d6956 --- /dev/null +++ b/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h @@ -0,0 +1,128 @@ +/******************************************************************************** + * 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_DEADLINE_DEADLINE_MONITOR_H +#define SCORE_HM_DEADLINE_DEADLINE_MONITOR_H + +#include +#include + +namespace score::hm +{ +// Forward declaration +class HealthMonitor; +} // namespace score::hm + +namespace score::hm::deadline +{ + +// Forward declaration +class DeadlineMonitor; +class DeadlineHandle; +class Deadline; + +/// DeadlineMonitorBuilder for constructing DeadlineMonitor instance +class DeadlineMonitorBuilder : public internal::RustDroppable +{ + public: + /// Creates a new DeadlineMonitorBuilder + DeadlineMonitorBuilder(); + + DeadlineMonitorBuilder(const DeadlineMonitorBuilder&) = delete; + DeadlineMonitorBuilder& operator=(const DeadlineMonitorBuilder&) = delete; + + DeadlineMonitorBuilder(DeadlineMonitorBuilder&&) = default; + DeadlineMonitorBuilder& operator=(DeadlineMonitorBuilder&&) = delete; + + /// Adds a deadline with the given tag and duration range to the monitor. + DeadlineMonitorBuilder add_deadline(const IdentTag& tag, const TimeRange& range) &&; + + ::score::cpp::optional __drop_by_rust_impl() + { + return monitor_builder_handler_.drop_by_rust(); + } + + private: + internal::DroppableFFIHandle monitor_builder_handler_; +}; + +class DeadlineMonitor +{ + public: + // Delete copy, allow move + DeadlineMonitor(const DeadlineMonitor&) = delete; + DeadlineMonitor& operator=(const DeadlineMonitor&) = delete; + + DeadlineMonitor(DeadlineMonitor&& other) noexcept = default; + DeadlineMonitor& operator=(DeadlineMonitor&& other) noexcept = default; + + ::score::cpp::expected get_deadline(const IdentTag& tag); + + private: + explicit DeadlineMonitor(internal::FFIHandle handle); + + friend class score::hm::HealthMonitor; + internal::DroppableFFIHandle monitor_handle_; +}; + +/// Deadline instance representing a specific deadline to be monitored. +class Deadline +{ + public: + ~Deadline(); + + Deadline(const Deadline&) = delete; + Deadline& operator=(const Deadline&) = delete; + + Deadline(Deadline&& other) noexcept = default; + Deadline& operator=(Deadline&& other) noexcept = delete; + + /// Starts the deadline monitoring. Returns a DeadlineHandle to manage the deadline. + // After this call the Deadline instance cannot be used until connected DeadlineHandle is destroyed + ::score::cpp::expected start(); + + private: + explicit Deadline(internal::FFIHandle handle); + + friend class DeadlineMonitor; + friend class DeadlineHandle; + internal::DroppableFFIHandle deadline_handle_; + bool has_handle_; +}; + +/// Deadline guard to manage the lifetime of a started deadline. +class DeadlineHandle +{ + public: + /// Stops the deadline monitoring. + void stop(); + + /// Destructor that ensures the deadline is stopped if not already done. + ~DeadlineHandle(); + + DeadlineHandle(const DeadlineHandle&) = delete; + DeadlineHandle& operator=(const DeadlineHandle&) = delete; + + DeadlineHandle(DeadlineHandle&& other); + DeadlineHandle& operator=(DeadlineHandle&& other) = delete; + + private: + DeadlineHandle(Deadline& deadline); + + friend class Deadline; + bool was_stopped_; + ::score::cpp::optional> deadline_; +}; + +} // namespace score::hm::deadline + +#endif // SCORE_HM_DEADLINE_DEADLINE_MONITOR_H 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 new file mode 100644 index 00000000..abe2e227 --- /dev/null +++ b/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h @@ -0,0 +1,77 @@ +/******************************************************************************** + * 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_HEALTH_MONITOR_H +#define SCORE_HM_HEALTH_MONITOR_H + +#include +#include + +namespace score::hm +{ + +class HealthMonitor; + +/// +/// Builder for HealthMonitor instances. +/// +class HealthMonitorBuilder +{ + public: + /// Creates a new HealthMonitorBuilder + HealthMonitorBuilder(); + + ~HealthMonitorBuilder() = default; + HealthMonitorBuilder(const HealthMonitorBuilder&) = delete; + HealthMonitorBuilder& operator=(const HealthMonitorBuilder&) = delete; + + HealthMonitorBuilder(HealthMonitorBuilder&&) = default; + HealthMonitorBuilder& operator=(HealthMonitorBuilder&&) = delete; + + /// Adds a deadline monitor to the builder to construct DeadlineMonitor instances during HealthMonitor build. + HealthMonitorBuilder add_deadline_monitor(const IdentTag& tag, deadline::DeadlineMonitorBuilder&& monitor) &&; + + /// Builds and returns the HealthMonitor instance. + HealthMonitor build() &&; + + private: + internal::DroppableFFIHandle health_monitor_builder_handle_; +}; + +class HealthMonitor +{ + public: + HealthMonitor(const HealthMonitor&) = delete; + HealthMonitor& operator=(const HealthMonitor&) = delete; + HealthMonitor(HealthMonitor&& other) + { + health_monitor_ = std::move(other.health_monitor_); + other.health_monitor_ = nullptr; + } + + HealthMonitor& operator=(HealthMonitor&&); + + ~HealthMonitor(); + + score::cpp::expected get_deadline_monitor(const IdentTag& tag); + + private: + friend class HealthMonitorBuilder; + + HealthMonitor(internal::FFIHandle handle); + + internal::FFIHandle health_monitor_; +}; + +} // namespace score::hm + +#endif // SCORE_HM_HEALTH_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 new file mode 100644 index 00000000..4699eafa --- /dev/null +++ b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp @@ -0,0 +1,63 @@ +/******************************************************************************** + * 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/health_monitor.h" +#include "score/hm/common.h" + +#include +#include + +using namespace score::hm; +using ::testing::_; + +class HealthMonitorTest : public ::testing::Test +{ +}; + +// For first review round, only single test case to show up API +TEST_F(HealthMonitorTest, TestName) +{ + ::testing::GTEST_FLAG(catch_exceptions) = false; + ::testing::GTEST_FLAG(print_time) = true; + + auto builder_mon = deadline::DeadlineMonitorBuilder() + .add_deadline(IdentTag("deadline_1"), + TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))) + .add_deadline(IdentTag("deadline_2"), + TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))); + + IdentTag ident("monitor"); + + auto hm = HealthMonitorBuilder().add_deadline_monitor(ident, std::move(builder_mon)).build(); + + auto deadline_monitor_res = hm.get_deadline_monitor(ident); + EXPECT_TRUE(deadline_monitor_res.has_value()); + + { + // Try again to get the same monitor + auto deadline_monitor_res = hm.get_deadline_monitor(ident); + EXPECT_FALSE(deadline_monitor_res.has_value()); + } + + auto deadline_mon = std::move(*deadline_monitor_res); + + // std::cout << "Getting deadline" << std::endl; + + auto deadline_res = deadline_mon.get_deadline(IdentTag("deadline_1")); + + { + auto deadline_guard = deadline_res.value().start().value(); + + EXPECT_EQ(deadline_res.value().start().error(), ::score::hm::Error::WrongState); + deadline_guard.stop(); + } +} diff --git a/src/health_monitoring_lib/src/common.rs b/src/health_monitoring_lib/src/common.rs index eef86104..882bfea2 100644 --- a/src/health_monitoring_lib/src/common.rs +++ b/src/health_monitoring_lib/src/common.rs @@ -11,22 +11,60 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use crate::log::*; +use core::hash::Hash; use core::time::Duration; /// Unique identifier for deadlines. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, ScoreDebug)] -pub struct IdentTag(&'static str); // Internal representation as a leaked string slice for now. It can be also an str to u64 conversion. Since this is internal only, we can change it later if needed. +#[derive(Clone, Copy, Debug, Eq)] +#[repr(C)] +pub struct IdentTag { + data: *const u8, + len: usize, +} // Internal representation as a leaked string slice for now. It can be also an str to u64 conversion. Since this is internal only, we can change it later if needed. + +// Safety below for `from_raw_parts` -> Data was constructed from valid str +impl Hash for IdentTag { + fn hash(&self, state: &mut H) { + let bytes = unsafe { core::slice::from_raw_parts(self.data, self.len) }; + bytes.hash(state); + } +} +impl PartialEq for IdentTag { + fn eq(&self, other: &Self) -> bool { + let self_bytes = unsafe { core::slice::from_raw_parts(self.data, self.len) }; + let other_bytes = unsafe { core::slice::from_raw_parts(other.data, other.len) }; + self_bytes == other_bytes + } +} + +impl crate::log::ScoreDebug for IdentTag { + fn fmt(&self, f: crate::log::Writer, spec: &crate::log::FormatSpec) -> Result<(), crate::log::Error> { + let bytes = unsafe { core::slice::from_raw_parts(self.data, self.len) }; + crate::log::DebugStruct::new(f, spec, "IdentTag") + .field("data", unsafe { &core::str::from_utf8_unchecked(bytes) }) // Safety: The underlying data was created out of valid str + .finish() + } +} impl From for IdentTag { fn from(value: String) -> Self { - Self(value.leak()) + let leaked = value.leak(); + + Self { + data: leaked.as_ptr(), + len: leaked.len(), + } } } impl From<&str> for IdentTag { fn from(value: &str) -> Self { - Self(value.to_string().leak()) + let leaked = value.to_string().leak(); + + Self { + data: leaked.as_ptr(), + len: leaked.len(), + } } } @@ -42,3 +80,44 @@ impl TimeRange { Self { min, max } } } + +pub(crate) mod ffi { + use core::mem::ManuallyDrop; + use core::ops::{Deref, DerefMut}; + + pub(crate) type FFIHandle = *mut core::ffi::c_void; + + pub(crate) const HM_OK: i32 = 0; + pub(crate) const HM_NOT_FOUND: i32 = HM_OK + 1; + pub(crate) const HM_ALREADY_EXISTS: i32 = HM_OK + 2; + pub(crate) const _HM_INVALID_ARGS: i32 = HM_OK + 3; + pub(crate) const _HM_WRONG_STATE: i32 = HM_OK + 4; + pub(crate) const HM_FAILED: i32 = HM_OK + 5; + + /// A wrapper to represent borrowed data over FFI boundary without taking ownership. + pub(crate) struct FFIBorrowed { + data: ManuallyDrop, + } + + impl FFIBorrowed { + pub(crate) fn new(data: T) -> Self { + Self { + data: ManuallyDrop::new(data), + } + } + } + + impl Deref for FFIBorrowed { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } + } + + impl DerefMut for FFIBorrowed { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } + } +} diff --git a/src/health_monitoring_lib/src/deadline/deadline_monitor.rs b/src/health_monitoring_lib/src/deadline/deadline_monitor.rs index cada5aff..9d4d2beb 100644 --- a/src/health_monitoring_lib/src/deadline/deadline_monitor.rs +++ b/src/health_monitoring_lib/src/deadline/deadline_monitor.rs @@ -61,14 +61,18 @@ impl DeadlineMonitorBuilder { /// Adds a deadline with the given tag and duration range to the monitor. pub fn add_deadline(mut self, tag: &IdentTag, range: TimeRange) -> Self { - self.deadlines.insert(*tag, range); + self._add_deadline_internal(tag, range); self } /// Builds the DeadlineMonitor with the configured deadlines. - fn build(self, _allocator: &ProtectedMemoryAllocator) -> DeadlineMonitor { + pub(crate) fn build(self, _allocator: &ProtectedMemoryAllocator) -> DeadlineMonitor { DeadlineMonitor::new(self.deadlines) } + + pub(crate) fn _add_deadline_internal(&mut self, tag: &IdentTag, range: TimeRange) { + self.deadlines.insert(*tag, range); + } } pub struct DeadlineMonitor { @@ -83,7 +87,6 @@ pub(crate) enum DeadlineEvaluationError { impl DeadlineMonitor { fn new(deadlines: HashMap) -> Self { - // let active_deadlines: Arc<[DeadlineState]> = (0..deadlines.len()).map(|_| DeadlineState::new()).collect::>().into(); let mut active_deadlines = vec![]; let deadlines = deadlines @@ -96,6 +99,7 @@ impl DeadlineMonitor { .collect(); Self { + #[allow(clippy::arc_with_non_send_sync)] // This will be fixed once we add background thread inner: Arc::new(DeadlineMonitorInner { deadlines, active_deadlines: active_deadlines.into(), @@ -167,6 +171,18 @@ impl Deadline { /// - Err(DeadlineError::DeadlineAlreadyFailed) - if the deadline was already missed before /// pub fn start(&mut self) -> Result, DeadlineError> { + // Safety: We ensure that the caller upholds the safety contract for FFI usage by using &'a mut self lifetime in DeadlineHandle + unsafe { self.start_internal().map(|_| DeadlineHandle(self)) } + } + + /// Starts the deadline - it will be monitored by health monitoring system. + /// This function is provides for FFI usage only! + /// + /// # Safety + /// - Caller must ensure that + /// - Deadline is not used (no call to any api) until it's stopped. basically this means that after this call You shall assure that there + /// is only single owner of the Deadline instance and it does not try to call start before stopping. + pub(super) unsafe fn start_internal(&mut self) -> Result<(), DeadlineError> { let now = self.monitor.now(); let max_time = now + self.range.max.as_millis() as u32; @@ -187,11 +203,11 @@ impl Deadline { warn!("Trying to start deadline {:?} that already failed", self.tag); Err(DeadlineError::DeadlineAlreadyFailed) } else { - Ok(DeadlineHandle(self)) + Ok(()) } } - fn stop_internal(&mut self) { + pub(super) fn stop_internal(&mut self) { let now = self.monitor.now(); let max = self.range.max.as_millis() as u32; let min = self.range.min.as_millis() as u32; diff --git a/src/health_monitoring_lib/src/deadline/ffi.rs b/src/health_monitoring_lib/src/deadline/ffi.rs new file mode 100644 index 00000000..f0e151a3 --- /dev/null +++ b/src/health_monitoring_lib/src/deadline/ffi.rs @@ -0,0 +1,155 @@ +// ******************************************************************************* +// 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::ffi::*; +use crate::deadline::deadline_monitor::Deadline; +use crate::deadline::*; +use crate::*; +use core::time::Duration; +use std::os::raw::c_int; + +pub(crate) struct DeadlineMonitorCpp { + monitor: DeadlineMonitor, + // TODO: Here we will keep allocation storage for Deadlines once we implement memory pool + // For now, Deadlines are kept allocated on heap individually +} + +impl DeadlineMonitorCpp { + pub(crate) fn new(monitor: DeadlineMonitor) -> Self { + Self { monitor } + } + + pub(crate) fn get_deadline(&self, tag: IdentTag) -> Result { + match self.monitor.get_deadline(&tag) { + Ok(deadline) => { + // Now we allocate at runtime. As next step we will add a memory pool for deadlines into self and this way we will not need allocate anymore + let handle = Box::into_raw(Box::new(deadline)); + Ok(handle as FFIHandle) + }, + Err(DeadlineMonitorError::DeadlineInUse) => Err(HM_ALREADY_EXISTS), + Err(DeadlineMonitorError::DeadlineNotFound) => Err(HM_NOT_FOUND), + } + } +} + +#[no_mangle] +pub extern "C" fn deadline_monitor_builder_create() -> FFIHandle { + let builder = DeadlineMonitorBuilder::new(); + let handle = Box::into_raw(Box::new(builder)); + handle as FFIHandle +} + +#[no_mangle] +pub extern "C" fn deadline_monitor_builder_destroy(handle: FFIHandle) { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` + // and this must be assured on other side of FFI. + unsafe { + let _ = Box::from_raw(handle as *mut DeadlineMonitorBuilder); + } +} + +#[no_mangle] +pub extern "C" fn deadline_monitor_builder_add_deadline(handle: FFIHandle, tag: *const IdentTag, min: u32, max: u32) { + assert!(!handle.is_null()); + assert!(!tag.is_null()); + + // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust + let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` + // and this must be assured on other side of FFI. + let mut monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut DeadlineMonitorBuilder) }); + + monitor._add_deadline_internal( + &tag, + TimeRange::new(Duration::from_millis(min as u64), Duration::from_millis(max as u64)), + ); +} + +#[no_mangle] +pub extern "C" fn deadline_monitor_cpp_destroy(handle: FFIHandle) { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` + // and this must be assured on other side of FFI. + unsafe { + let _ = Box::from_raw(handle as *mut DeadlineMonitorCpp); + } +} + +#[no_mangle] +pub extern "C" fn deadline_monitor_cpp_get_deadline( + handle: FFIHandle, + tag: *const IdentTag, + out: *mut FFIHandle, +) -> c_int { + assert!(!handle.is_null()); + assert!(!tag.is_null()); + assert!(!out.is_null()); + + // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust + let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` + // and this must be assured on other side of FFI. + let monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut DeadlineMonitorCpp) }); + let deadline_handle = monitor.get_deadline(tag); + + deadline_handle.map_or_else( + |err_code| err_code, + |handle| { + unsafe { + *out = handle; + } + HM_OK + }, + ) +} + +#[no_mangle] +pub extern "C" fn deadline_start(handle: FFIHandle) -> c_int { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_cpp_get_deadline` + // and this must be assured on other side of FFI. + let mut deadline = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut Deadline) }); + + // Safety: We ensure at CPP side that a Deadline has move only semantic to not end up in multiple owners of same deadline. + // We also check during start call that previous start/stop sequence was done correctly. + match unsafe { deadline.start_internal() } { + Ok(()) => HM_OK, + Err(_err) => HM_FAILED, + } +} + +#[no_mangle] +pub extern "C" fn deadline_stop(handle: FFIHandle) { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_cpp_get_deadline` + // and this must be assured on other side of FFI. + let mut deadline = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut Deadline) }); + deadline.stop_internal(); +} + +#[no_mangle] +pub extern "C" fn deadline_destroy(handle: FFIHandle) { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_cpp_get_deadline` + // and this must be assured on other side of FFI. + unsafe { + let _ = Box::from_raw(handle as *mut Deadline); + } +} diff --git a/src/health_monitoring_lib/src/deadline/mod.rs b/src/health_monitoring_lib/src/deadline/mod.rs index 9517ed60..d903c412 100644 --- a/src/health_monitoring_lib/src/deadline/mod.rs +++ b/src/health_monitoring_lib/src/deadline/mod.rs @@ -18,3 +18,6 @@ mod deadline_state; pub use deadline_monitor::{ DeadlineError, DeadlineHandle, DeadlineMonitor, DeadlineMonitorBuilder, DeadlineMonitorError, }; + +// FFI bindings +pub(super) mod ffi; diff --git a/src/health_monitoring_lib/src/ffi.rs b/src/health_monitoring_lib/src/ffi.rs new file mode 100644 index 00000000..36c90828 --- /dev/null +++ b/src/health_monitoring_lib/src/ffi.rs @@ -0,0 +1,97 @@ +// ******************************************************************************* +// 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::ffi::*; +use crate::deadline::ffi::DeadlineMonitorCpp; +use crate::*; + +#[no_mangle] +extern "C" fn health_monitor_builder_create() -> FFIHandle { + let builder = HealthMonitorBuilder::new(); + let handle = Box::into_raw(Box::new(builder)); + handle as FFIHandle +} + +#[no_mangle] +extern "C" fn health_monitor_builder_destroy(handle: FFIHandle) { + assert!(!handle.is_null()); + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` + // and this must be assured on other side of FFI. + unsafe { + let _ = Box::from_raw(handle as *mut HealthMonitorBuilder); + } +} + +#[no_mangle] +extern "C" fn health_monitor_builder_add_deadline_monitor(handle: FFIHandle, tag: *const IdentTag, monitor: FFIHandle) { + assert!(!handle.is_null()); + assert!(!tag.is_null()); + assert!(!monitor.is_null()); + + // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust + let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` + let monitor = unsafe { Box::from_raw(monitor as *mut deadline::DeadlineMonitorBuilder) }; + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` + // and this must be assured on other side of FFI. + let mut health_monitor_builder = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut HealthMonitorBuilder) }); + + health_monitor_builder.add_deadline_monitor(tag, *monitor); +} + +#[no_mangle] +extern "C" fn health_monitor_builder_build(handle: FFIHandle) -> FFIHandle { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` + // and this must be assured on other side of FFI. + let health_monitor_builder: Box = + unsafe { Box::from_raw(handle as *mut HealthMonitorBuilder) }; + + let health_monitor = health_monitor_builder.build(); + let health_monitor_handle = Box::into_raw(Box::new(health_monitor)); + health_monitor_handle as FFIHandle +} + +#[no_mangle] +extern "C" fn health_monitor_get_deadline_monitor(handle: FFIHandle, tag: *const IdentTag) -> FFIHandle { + assert!(!handle.is_null()); + assert!(!tag.is_null()); + + // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust + let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` + // and this must be assured on other side of FFI. + let mut health_monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut HealthMonitor) }); + + if let Some(deadline_monitor) = health_monitor.get_deadline_monitor(&tag) { + let deadline_monitor_handle = Box::into_raw(Box::new(DeadlineMonitorCpp::new(deadline_monitor))); + + deadline_monitor_handle as FFIHandle + } else { + core::ptr::null_mut() + } +} + +#[no_mangle] +extern "C" fn health_monitor_destroy(handle: FFIHandle) { + assert!(!handle.is_null()); + + // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_build` + // and this must be assured on other side of FFI. + unsafe { + let _ = Box::from_raw(handle as *mut HealthMonitor); + } +} diff --git a/src/health_monitoring_lib/src/lib.rs b/src/health_monitoring_lib/src/lib.rs index 792d3cd8..af1bded2 100644 --- a/src/health_monitoring_lib/src/lib.rs +++ b/src/health_monitoring_lib/src/lib.rs @@ -10,11 +10,52 @@ // // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* - mod common; mod log; mod protected_memory; pub mod deadline; +use std::collections::HashMap; + pub use common::{IdentTag, TimeRange}; + +#[derive(Default)] +pub struct HealthMonitorBuilder { + deadlines: HashMap, +} + +impl HealthMonitorBuilder { + pub fn new() -> Self { + Self { + deadlines: HashMap::new(), + } + } + + pub fn add_deadline_monitor(&mut self, tag: IdentTag, monitor: deadline::DeadlineMonitorBuilder) { + self.deadlines.insert(tag, monitor); + } + + pub fn build(self) -> HealthMonitor { + let allocator = protected_memory::ProtectedMemoryAllocator {}; + let mut monitors = HashMap::new(); + for (tag, builder) in self.deadlines { + monitors.insert(tag, builder.build(&allocator)); + } + HealthMonitor { + deadline_monitors: monitors, + } + } +} + +pub struct HealthMonitor { + deadline_monitors: HashMap, +} + +impl HealthMonitor { + pub fn get_deadline_monitor(&mut self, tag: &IdentTag) -> Option { + self.deadline_monitors.remove(tag) + } +} + +mod ffi;