Skip to content

Conversation

@arkjedrz
Copy link
Contributor

@arkjedrz arkjedrz commented Feb 9, 2026

Add heartbeat monitor HMON.

Resolves #68

@arkjedrz arkjedrz requested a review from pawelrutkaq February 9, 2026 15:43
@arkjedrz arkjedrz requested a review from Copilot February 9, 2026 15:43
@github-actions
Copy link

github-actions bot commented Feb 9, 2026

License Check Results

🚀 The license check job ran with the Bazel command:

bazel run //:license-check

Status: ⚠️ Needs Review

Click to expand output
[License Check Output]
Extracting Bazel installation...
Starting local Bazel server (8.4.2) and connecting to it...
INFO: Invocation ID: e8886f84-3d61-43ce-807e-9c308b38543b
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
DEBUG: Rule 'rust_qnx8_toolchain+' indicated that a canonical reproducible form can be obtained by modifying arguments integrity = "sha256-eQOopREOYCL5vtTb6c1cwZrql4GVrJ1FqgxarQRe1xs="
DEBUG: Repository rust_qnx8_toolchain+ instantiated at:
  <builtin>: in <toplevel>
Repository rule http_archive defined at:
  /home/runner/.bazel/external/bazel_tools/tools/build_defs/repo/http.bzl:431:31: in <toplevel>
Computing main repo mapping: 
Computing main repo mapping: 
WARNING: For repository 'score_bazel_platforms', the root module requires module version score_bazel_platforms@0.0.3, but got score_bazel_platforms@0.0.4 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: For repository 'score_tooling', the root module requires module version score_tooling@1.0.5, but got score_tooling@1.1.0 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Loading: 
Loading: 2 packages loaded
Loading: 2 packages loaded
    currently loading: 
Loading: 2 packages loaded
    currently loading: 
Analyzing: target //:license-check (3 packages loaded)
Analyzing: target //:license-check (3 packages loaded, 0 targets configured)
Analyzing: target //:license-check (3 packages loaded, 0 targets configured)

Analyzing: target //:license-check (34 packages loaded, 10 targets configured)

Analyzing: target //:license-check (46 packages loaded, 10 targets configured)

Analyzing: target //:license-check (88 packages loaded, 10 targets configured)

Analyzing: target //:license-check (131 packages loaded, 437 targets configured)

Analyzing: target //:license-check (147 packages loaded, 4002 targets configured)

Analyzing: target //:license-check (148 packages loaded, 7382 targets configured)

Analyzing: target //:license-check (150 packages loaded, 7393 targets configured)

Analyzing: target //:license-check (150 packages loaded, 7393 targets configured)

Analyzing: target //:license-check (150 packages loaded, 7393 targets configured)

Analyzing: target //:license-check (162 packages loaded, 13676 targets configured)

INFO: Analyzed target //:license-check (162 packages loaded, 13697 targets configured).
[9 / 13] JavaToolchainCompileClasses external/rules_java+/toolchains/platformclasspath_classes; 0s disk-cache, processwrapper-sandbox ... (2 actions, 1 running)
[11 / 13] JavaToolchainCompileBootClasspath external/rules_java+/toolchains/platformclasspath.jar; 0s disk-cache, processwrapper-sandbox
[12 / 13] Building license.check.license_check.jar (); 0s disk-cache, multiplex-worker
INFO: Found 1 target...
Target //:license.check.license_check up-to-date:
  bazel-bin/license.check.license_check
  bazel-bin/license.check.license_check.jar
INFO: Elapsed time: 32.923s, Critical Path: 2.73s
INFO: 13 processes: 9 internal, 3 processwrapper-sandbox, 1 worker.
INFO: Build completed successfully, 13 total actions
INFO: Running command line: bazel-bin/license.check.license_check ./formatted.txt <args omitted>
usage: org.eclipse.dash.licenses.cli.Main [-batch <int>] [-cd <url>]
       [-confidence <int>] [-ef <url>] [-excludeSources <sources>] [-help] [-lic
       <url>] [-project <shortname>] [-repo <url>] [-review] [-summary <file>]
       [-timeout <seconds>] [-token <token>]

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

The created documentation from the pull request is available at: docu-html

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new heartbeat monitor (HMON) to the Rust health monitoring library and integrates it into the existing monitoring worker/supervisor notification flow.

Changes:

  • Introduces heartbeat module (monitor + atomic state) and integrates heartbeat monitors into HealthMonitorBuilder/HealthMonitor.
  • Updates the monitor evaluation interface to accept a shared hmon_starting_point and wires it through the monitoring worker thread.
  • Refactors SupervisorAPIClient into a dedicated module with selectable implementations via Cargo features.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/health_monitoring_lib/rust/worker.rs Passes a shared HMON start instant into monitor evaluations; moves supervisor client trait out.
src/health_monitoring_lib/rust/supervisor_api_client/mod.rs Adds feature-selected SupervisorAPIClient + implementation alias.
src/health_monitoring_lib/rust/supervisor_api_client/stub_supervisor_api_client.rs New stub client implementation.
src/health_monitoring_lib/rust/supervisor_api_client/score_supervisor_api_client.rs New SCORE client implementation.
src/health_monitoring_lib/rust/lib.rs Adds heartbeat monitors to builder + start flow; uses new supervisor client impl selector.
src/health_monitoring_lib/rust/heartbeat/mod.rs Exposes heartbeat monitor API.
src/health_monitoring_lib/rust/heartbeat/heartbeat_state.rs Adds atomic packed heartbeat state and tests.
src/health_monitoring_lib/rust/heartbeat/heartbeat_monitor.rs Implements heartbeat monitor logic + tests (incl. loom).
src/health_monitoring_lib/rust/deadline/deadline_monitor.rs Adapts deadline evaluation to new evaluator signature and shared start instant.
src/health_monitoring_lib/rust/common.rs Extends evaluation error types; adds duration_to_u32; updates evaluator trait signature.
src/health_monitoring_lib/Cargo.toml Adds optional monitor_rs, loom target dep, and feature defaults.
src/health_monitoring_lib/BUILD Enables score_supervisor_api_client feature in Bazel builds.
Cargo.toml Updates workspace defaults and adds cfg(loom) lint configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@arkjedrz arkjedrz force-pushed the arkjedrz_heartbeat-monitor branch from f07c0db to e65f6a6 Compare February 10, 2026 11:43
@arkjedrz arkjedrz temporarily deployed to workflow-approval February 10, 2026 11:43 — with GitHub Actions Inactive
@arkjedrz arkjedrz requested a review from Copilot February 10, 2026 11:44
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@arkjedrz arkjedrz force-pushed the arkjedrz_heartbeat-monitor branch from e65f6a6 to bd438c3 Compare February 10, 2026 12:57
@arkjedrz arkjedrz temporarily deployed to workflow-approval February 10, 2026 12:57 — with GitHub Actions Inactive
@arkjedrz arkjedrz force-pushed the arkjedrz_heartbeat-monitor branch from bd438c3 to c5f4b84 Compare February 10, 2026 13:14
@arkjedrz arkjedrz requested a review from Copilot February 10, 2026 13:15
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +222 to +223
let hmon_starting_point = Instant::now();
let monitor_starting_point = Instant::now();
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_hmon_time_offset_wrong_order can be flaky because consecutive Instant::now() calls may return the same value on some platforms, in which case checked_duration_since won’t panic and the test will intermittently fail. Make the ordering deterministic by constructing monitor_starting_point first and then explicitly creating an earlier hmon_starting_point (e.g., monitor_starting_point - Duration::from_nanos(1)), or otherwise guaranteeing hmon_starting_point < monitor_starting_point.

Suggested change
let hmon_starting_point = Instant::now();
let monitor_starting_point = Instant::now();
let monitor_starting_point = Instant::now();
let hmon_starting_point = monitor_starting_point
.checked_sub(Duration::from_nanos(1))
.unwrap();

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +24
supervisor_link: monitor_rs::Monitor<Checks>,
}

unsafe impl Send for ScoreSupervisorAPIClient {} // Just assuming it's safe to send across threads, this is a temporary solution

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unsafe impl Send is unsound unless you can prove monitor_rs::Monitor<Checks> is thread-safe to move across threads; if it is not, this can introduce undefined behavior. Prefer removing the unsafe impl and restructuring so the SCORE client is created inside the monitoring thread (so it does not cross thread boundaries), or wrap the underlying non-Send type behind a thread-safe mechanism (only if the wrapped type supports it) and document the safety justification if unsafe remains necessary.

Suggested change
supervisor_link: monitor_rs::Monitor<Checks>,
}
unsafe impl Send for ScoreSupervisorAPIClient {} // Just assuming it's safe to send across threads, this is a temporary solution
// NOTE: We do not implement `Send` for `ScoreSupervisorAPIClient` because
// `monitor_rs::Monitor<Checks>` is not proven to be safe to move across
// threads. If cross-thread use is required, refactor the code so this
// client is only used on a single thread or introduce a safe
// communication mechanism instead of asserting `Send` unsafely.
supervisor_link: monitor_rs::Monitor<Checks>,
}

Copilot uses AI. Check for mistakes.
Add heartbeat monitor HMON.
@arkjedrz arkjedrz force-pushed the arkjedrz_heartbeat-monitor branch from c5f4b84 to b012f37 Compare February 10, 2026 13:19
@arkjedrz arkjedrz temporarily deployed to workflow-approval February 10, 2026 13:19 — with GitHub Actions Inactive
@arkjedrz arkjedrz requested a review from Copilot February 10, 2026 13:19
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +38

#[allow(dead_code)]
pub struct ScoreSupervisorAPIClient {
supervisor_link: monitor_rs::Monitor<Checks>,
}

unsafe impl Send for ScoreSupervisorAPIClient {} // Just assuming it's safe to send across threads, this is a temporary solution

#[allow(dead_code)]
impl ScoreSupervisorAPIClient {
pub fn new() -> Self {
let value = std::env::var("IDENTIFIER").expect("IDENTIFIER env not set");
debug!("ScoreSupervisorAPIClient: Creating with IDENTIFIER={}", value);
// This is only temporary usage so unwrap is fine here.
let supervisor_link = monitor_rs::Monitor::<Checks>::new(&value).expect("Failed to create supervisor_link");
Self { supervisor_link }
}
}

impl SupervisorAPIClient for ScoreSupervisorAPIClient {
fn notify_alive(&self) {
self.supervisor_link.report_checkpoint(Checks::WorkerCheckpoint);
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unsafe impl Send is unsound without a proof that monitor_rs::Monitor<Checks> is thread-safe to move across threads. Instead of forcing Send, refactor so the SCORE client is constructed inside the monitoring thread (so it isn’t sent across threads), or add a thread-safe wrapper/indirection (e.g., a channel to a dedicated reporter thread) that avoids moving non-Send state into std::thread::spawn.

Suggested change
#[allow(dead_code)]
pub struct ScoreSupervisorAPIClient {
supervisor_link: monitor_rs::Monitor<Checks>,
}
unsafe impl Send for ScoreSupervisorAPIClient {} // Just assuming it's safe to send across threads, this is a temporary solution
#[allow(dead_code)]
impl ScoreSupervisorAPIClient {
pub fn new() -> Self {
let value = std::env::var("IDENTIFIER").expect("IDENTIFIER env not set");
debug!("ScoreSupervisorAPIClient: Creating with IDENTIFIER={}", value);
// This is only temporary usage so unwrap is fine here.
let supervisor_link = monitor_rs::Monitor::<Checks>::new(&value).expect("Failed to create supervisor_link");
Self { supervisor_link }
}
}
impl SupervisorAPIClient for ScoreSupervisorAPIClient {
fn notify_alive(&self) {
self.supervisor_link.report_checkpoint(Checks::WorkerCheckpoint);
use std::sync::mpsc;
#[allow(dead_code)]
pub struct ScoreSupervisorAPIClient {
sender: mpsc::Sender<()>,
}
#[allow(dead_code)]
impl ScoreSupervisorAPIClient {
pub fn new() -> Self {
let value = std::env::var("IDENTIFIER").expect("IDENTIFIER env not set");
debug!("ScoreSupervisorAPIClient: Creating with IDENTIFIER={}", value);
let (sender, receiver) = mpsc::channel::<()>();
// Spawn a dedicated thread that owns the non-Send monitor and reports checkpoints.
std::thread::spawn(move || {
// This is only temporary usage so unwrap is fine here.
let supervisor_link = monitor_rs::Monitor::<Checks>::new(&value)
.expect("Failed to create supervisor_link");
// Process all incoming "alive" notifications.
while let Ok(()) = receiver.recv() {
supervisor_link.report_checkpoint(Checks::WorkerCheckpoint);
}
});
Self { sender }
}
}
impl SupervisorAPIClient for ScoreSupervisorAPIClient {
fn notify_alive(&self) {
// Ignore send errors (e.g., if the background thread has exited).
if let Err(err) = self.sender.send(()) {
debug!("ScoreSupervisorAPIClient: failed to send alive notification: {}", err);
}

Copilot uses AI. Check for mistakes.
internal_processing_cycle: Duration,
_allocator: &ProtectedMemoryAllocator,
) -> HeartbeatMonitor {
assert!(self.range.min * 2 > internal_processing_cycle);
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion has no message, which makes failures harder to diagnose (especially when triggered via config/FFI). Consider adding an assertion message that includes the constraint and the actual values (e.g., min, internal_processing_cycle) to guide users toward fixing their configuration.

Suggested change
assert!(self.range.min * 2 > internal_processing_cycle);
assert!(
self.range.min * 2 > internal_processing_cycle,
"internal_processing_cycle ({:?}) must be shorter than doubled minimum time range (2 * min = {:?})",
internal_processing_cycle,
self.range.min
);

Copilot uses AI. Check for mistakes.
with:
command: clippy
args: --all-features --all-targets --workspace -- -D warnings
args: --features stub_supervisor_api_client --all-targets -- -D warnings
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to the previous workflow, these clippy invocations no longer pass --workspace, which can reduce lint coverage to only the default package(s) and miss issues in other workspace crates (e.g., examples). If feature conflicts were the reason for dropping --workspace, consider (1) keeping a separate cargo clippy --workspace step without feature overrides, and (2) running feature-specific clippy scoped to -p health_monitoring_lib.

Copilot uses AI. Check for mistakes.
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features score_supervisor_api_client --no-default-features --all-targets -- -D warnings
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to the previous workflow, these clippy invocations no longer pass --workspace, which can reduce lint coverage to only the default package(s) and miss issues in other workspace crates (e.g., examples). If feature conflicts were the reason for dropping --workspace, consider (1) keeping a separate cargo clippy --workspace step without feature overrides, and (2) running feature-specific clippy scoped to -p health_monitoring_lib.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[HmLib] Rust Heartbeat Monitor API

1 participant