distributed_cache/health.rs
1//
2// Copyright 2018-2026 Accenture Technology
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17//! The distributed cache's Redis health check — Rust port of the Java
18//! `CacheRedisHealthCheck`: the thin binding of the shared
19//! [`RedisHealthProbe`] to the reserved route `redis.health` and the plain
20//! `redis.*` config namespace. The route name was reserved for exactly this
21//! module when sync-over-async took `soa.redis.health`, so the two coexist
22//! without collision, each reporting on its own server.
23//!
24//! Gated by the same `redis.cache.enabled` switch as the cache function, so it
25//! registers only when the cache is enabled. Add `redis.health` to
26//! `mandatory.health.dependencies` (or `optional.health.dependencies`) and
27//! `/health` will include the cache Redis status. The probe's configuration is
28//! resolved lazily — never at registration — so a credential a bootstrap
29//! publishes after start-up is picked up (see the foundation's probe).
30//! `redis.health.timeout` (default `5s`) bounds the probe;
31//! `redis.health.startup.grace` (default `30s`) is the start-up placeholder
32//! window.
33
34use std::collections::HashMap;
35use std::sync::OnceLock;
36
37use async_trait::async_trait;
38use platform_core::{preload, AppError, ComposableFunction, EventEnvelope};
39use redis_connection::{resolve_duration, RedisConfig, RedisHealthProbe, BASE_PREFIX};
40
41/// The cache's health-check route — the plain-named counterpart of
42/// sync-over-async's `soa.redis.health`.
43pub const HEALTH_ROUTE: &str = "redis.health";
44
45const TIMEOUT_KEY: &str = "redis.health.timeout";
46const GRACE_KEY: &str = "redis.health.startup.grace";
47const DEFAULT_TIMEOUT: &str = "5s";
48const DEFAULT_GRACE: &str = "30s";
49
50/// `redis.health` — registered by the preload inventory alongside the cache
51/// function when `redis.cache.enabled=true`.
52#[preload(route = "redis.health", instances = 5)]
53#[optional_service("redis.cache.enabled")]
54pub struct CacheRedisHealthCheck;
55
56impl CacheRedisHealthCheck {
57 /// The one probe behind every worker of this route, built on the first
58 /// event: settings re-resolved from `redis.*` on every rebuild, timeout
59 /// and grace from `redis.health.*`.
60 fn probe() -> &'static RedisHealthProbe {
61 static PROBE: OnceLock<RedisHealthProbe> = OnceLock::new();
62 PROBE.get_or_init(|| {
63 RedisHealthProbe::new(
64 || RedisConfig::from_prefix(BASE_PREFIX),
65 resolve_duration(TIMEOUT_KEY, TIMEOUT_KEY, DEFAULT_TIMEOUT),
66 resolve_duration(GRACE_KEY, GRACE_KEY, DEFAULT_GRACE),
67 )
68 })
69 }
70}
71
72#[async_trait]
73impl ComposableFunction for CacheRedisHealthCheck {
74 async fn handle_event(
75 &self,
76 headers: HashMap<String, String>,
77 input: EventEnvelope,
78 instance: usize,
79 ) -> Result<EventEnvelope, AppError> {
80 Self::probe().handle_event(headers, input, instance).await
81 }
82}