use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{LazyLock, Mutex};
use std::time::SystemTime;
type CurrentTimeFn = Box<dyn Fn() -> Option<SystemTime> + Send + Sync>;
type EventTimeFn = Box<dyn Fn(i32) -> Option<SystemTime> + Send + Sync>;
const EPICS_EPOCH_UNIX_SECS: u64 = 631_152_000;
fn epics_epoch() -> SystemTime {
SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(EPICS_EPOCH_UNIX_SECS)
}
struct CurrentTimeProvider {
name: String,
priority: i32,
get_time: CurrentTimeFn,
interrupt_safe: bool,
is_os_default: bool,
}
struct EventTimeProvider {
name: String,
priority: i32,
get_event: EventTimeFn,
interrupt_safe: bool,
}
struct GeneralTimeInner {
current_providers: Vec<CurrentTimeProvider>,
event_providers: Vec<EventTimeProvider>,
last_provided_time: SystemTime,
event_times: [SystemTime; 256],
last_best_time: SystemTime,
last_current_name: Option<String>,
last_event_name: Option<String>,
use_osd_get_current: bool,
sync_hooks: Vec<Box<dyn Fn(SystemTime) + Send + Sync>>,
}
impl GeneralTimeInner {
fn new() -> Self {
let mut inner = Self {
current_providers: Vec::new(),
event_providers: Vec::new(),
last_provided_time: epics_epoch(),
event_times: [epics_epoch(); 256],
last_best_time: epics_epoch(),
last_current_name: None,
last_event_name: None,
use_osd_get_current: true,
sync_hooks: Vec::new(),
};
inner.current_providers.push(CurrentTimeProvider {
name: "OS Clock".to_string(),
priority: 999,
get_time: Box::new(|| Some(SystemTime::now())),
interrupt_safe: true,
is_os_default: true,
});
inner
}
}
static GENERAL_TIME: LazyLock<Mutex<GeneralTimeInner>> =
LazyLock::new(|| Mutex::new(GeneralTimeInner::new()));
static ERROR_COUNTS: AtomicU64 = AtomicU64::new(0);
pub fn register_current_provider(
name: impl Into<String>,
priority: i32,
get_time: impl Fn() -> Option<SystemTime> + Send + Sync + 'static,
) {
register_current_provider_impl(name.into(), priority, Box::new(get_time), false);
}
pub fn register_int_current_provider(
name: impl Into<String>,
priority: i32,
get_time: impl Fn() -> Option<SystemTime> + Send + Sync + 'static,
) {
register_current_provider_impl(name.into(), priority, Box::new(get_time), true);
}
fn register_current_provider_impl(
name: String,
priority: i32,
get_time: CurrentTimeFn,
interrupt_safe: bool,
) {
let mut inner = GENERAL_TIME.lock().unwrap();
let provider = CurrentTimeProvider {
name,
priority,
get_time,
interrupt_safe,
is_os_default: false,
};
let pos = inner
.current_providers
.iter()
.position(|p| p.priority > priority)
.unwrap_or(inner.current_providers.len());
inner.current_providers.insert(pos, provider);
inner.use_osd_get_current = false;
}
pub fn register_event_provider(
name: impl Into<String>,
priority: i32,
get_event: impl Fn(i32) -> Option<SystemTime> + Send + Sync + 'static,
) {
register_event_provider_impl(name.into(), priority, Box::new(get_event), false);
}
pub fn register_int_event_provider(
name: impl Into<String>,
priority: i32,
get_event: impl Fn(i32) -> Option<SystemTime> + Send + Sync + 'static,
) {
register_event_provider_impl(name.into(), priority, Box::new(get_event), true);
}
fn register_event_provider_impl(
name: String,
priority: i32,
get_event: EventTimeFn,
interrupt_safe: bool,
) {
let mut inner = GENERAL_TIME.lock().unwrap();
let provider = EventTimeProvider {
name,
priority,
get_event,
interrupt_safe,
};
let pos = inner
.event_providers
.iter()
.position(|p| p.priority > priority)
.unwrap_or(inner.event_providers.len());
inner.event_providers.insert(pos, provider);
}
pub fn register_clock_sync_hook<F>(hook: F)
where
F: Fn(SystemTime) + Send + Sync + 'static,
{
let mut inner = GENERAL_TIME.lock().unwrap();
inner.sync_hooks.push(Box::new(hook));
}
pub fn notify_clock_sync(t_synced: SystemTime) {
let inner = GENERAL_TIME.lock().unwrap();
for hook in &inner.sync_hooks {
hook(t_synced);
}
}
pub fn get_current() -> SystemTime {
let mut inner = GENERAL_TIME.lock().unwrap();
if inner.use_osd_get_current {
if let Some(idx) = inner.current_providers.iter().position(|p| p.is_os_default) {
if let Some(t) = (inner.current_providers[idx].get_time)() {
let name = inner.current_providers[idx].name.clone();
inner.last_provided_time = t;
inner.last_current_name = Some(name);
return t;
}
}
}
for i in 0..inner.current_providers.len() {
if let Some(t) = (inner.current_providers[i].get_time)() {
let name = inner.current_providers[i].name.clone();
if t >= inner.last_provided_time {
inner.last_provided_time = t;
inner.last_current_name = Some(name);
return t;
} else {
ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
inner.last_current_name = Some(name);
return inner.last_provided_time;
}
}
}
ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
inner.last_provided_time
}
pub fn get_current_except_priority(ignore_priority: i32) -> Option<(SystemTime, i32)> {
let inner = GENERAL_TIME.lock().unwrap();
for p in &inner.current_providers {
if (ignore_priority > 0 && p.priority == ignore_priority)
|| (ignore_priority < 0 && p.priority != -ignore_priority)
{
continue;
}
if let Some(t) = (p.get_time)() {
return Some((t, p.priority));
}
}
None
}
pub fn get_current_int() -> Option<SystemTime> {
let inner = GENERAL_TIME.lock().unwrap();
for p in &inner.current_providers {
if !p.interrupt_safe {
continue;
}
if let Some(t) = (p.get_time)() {
return Some(t);
}
}
None
}
pub fn get_event_int(event: i32) -> Option<SystemTime> {
let inner = GENERAL_TIME.lock().unwrap();
for p in &inner.event_providers {
if !p.interrupt_safe {
continue;
}
if let Some(t) = (p.get_event)(event) {
return Some(t);
}
}
None
}
pub fn highest_current_name() -> Option<String> {
GENERAL_TIME
.lock()
.unwrap()
.current_providers
.first()
.map(|p| p.name.clone())
}
pub fn get_event(event: i32) -> SystemTime {
if event == 0 {
return get_current();
}
let mut inner = GENERAL_TIME.lock().unwrap();
if event == -1 {
for i in 0..inner.current_providers.len() {
if let Some(t) = (inner.current_providers[i].get_time)() {
let name = inner.current_providers[i].name.clone();
if t >= inner.last_best_time {
inner.last_best_time = t;
inner.last_event_name = Some(name);
return t;
} else {
ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
inner.last_event_name = Some(name);
return inner.last_best_time;
}
}
}
ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
return inner.last_best_time;
}
for i in 0..inner.event_providers.len() {
if let Some(t) = (inner.event_providers[i].get_event)(event) {
let name = inner.event_providers[i].name.clone();
inner.last_event_name = Some(name);
if (1..=255).contains(&event) {
let slot = event as usize;
if t >= inner.event_times[slot] {
inner.event_times[slot] = t;
return t;
} else {
ERROR_COUNTS.fetch_add(1, Ordering::Relaxed);
return inner.event_times[slot];
}
}
return t;
}
}
drop(inner);
get_current()
}
pub fn install_last_resort_event_provider() {
register_event_provider("OS Clock", 999, |_| Some(SystemTime::now()));
}
pub fn error_counts() -> u64 {
ERROR_COUNTS.load(Ordering::Relaxed)
}
pub fn reset_error_counts() {
ERROR_COUNTS.store(0, Ordering::Relaxed);
}
pub fn current_provider_name() -> Option<String> {
GENERAL_TIME.lock().unwrap().last_current_name.clone()
}
pub fn event_provider_name() -> Option<String> {
GENERAL_TIME.lock().unwrap().last_event_name.clone()
}
fn format_time_sample(t: SystemTime) -> String {
let dt: chrono::DateTime<chrono::Utc> = t.into();
dt.format("%Y-%m-%d %H:%M:%S.%6f").to_string()
}
pub fn report(level: i32) -> String {
let inner = GENERAL_TIME.lock().unwrap();
let mut out = String::new();
out.push_str(&format!(
"Backwards time errors prevented {} times.\n\n",
error_counts()
));
out.push_str("Current Time Providers:\n");
if inner.current_providers.is_empty() {
out.push_str("\tNo Providers registered.\n");
} else {
for p in &inner.current_providers {
out.push_str(&format!(" \"{}\", priority = {}\n", p.name, p.priority));
if level != 0 {
match (p.get_time)() {
Some(t) => {
out.push_str(&format!("\tCurrent Time is {}.\n", format_time_sample(t)))
}
None => out.push_str("\tCurrent Time not available\n"),
}
}
}
out.push('\n');
}
out.push_str("Event Time Providers:\n");
if inner.event_providers.is_empty() {
out.push_str("\tNo Providers registered.\n");
} else {
for p in &inner.event_providers {
out.push_str(&format!(" \"{}\", priority = {}\n", p.name, p.priority));
}
out.push('\n');
}
out
}
#[cfg(test)]
fn _reset_for_testing() {
let mut inner = GENERAL_TIME.lock().unwrap();
*inner = GeneralTimeInner::new();
ERROR_COUNTS.store(0, Ordering::Relaxed);
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn os_clock_default_returns_reasonable_time() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t = get_current();
let secs = t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
assert!(secs > 1_577_836_800, "time should be after 2020");
}
#[test]
fn custom_provider_overrides_os_clock() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
register_current_provider("Test Clock", 10, move || Some(fixed));
let t = get_current();
assert_eq!(t, fixed);
assert_eq!(current_provider_name().as_deref(), Some("Test Clock"));
}
#[test]
fn provider_returning_none_falls_through() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
register_current_provider("Broken", 1, || None);
register_current_provider("Fallback", 50, move || Some(fixed));
let t = get_current();
assert_eq!(t, fixed);
assert_eq!(current_provider_name().as_deref(), Some("Fallback"));
}
#[test]
fn monotonic_enforcement() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let call_c = call.clone();
register_current_provider("Stepper", 10, move || {
let n = call_c.fetch_add(1, Ordering::Relaxed);
match n {
0 => Some(t1),
_ => Some(t2),
}
});
reset_error_counts();
let first = get_current();
assert_eq!(first, t1);
assert_eq!(error_counts(), 0);
let second = get_current();
assert_eq!(second, t1);
assert_eq!(error_counts(), 1);
}
#[test]
fn event_zero_delegates_to_get_current() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let fixed = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
register_current_provider("Fixed", 10, move || Some(fixed));
let t = get_event(0);
assert_eq!(t, fixed);
}
#[test]
fn event_per_slot_ratcheting() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000); let t3 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_001_000);
let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let call_c = call.clone();
register_event_provider("EventSrc", 10, move |_ev| {
let n = call_c.fetch_add(1, Ordering::Relaxed);
match n {
0 => Some(t1),
1 => Some(t2),
_ => Some(t3),
}
});
reset_error_counts();
let first = get_event(42);
assert_eq!(first, t1);
let second = get_event(42);
assert_eq!(second, t1);
assert_eq!(error_counts(), 1);
let third = get_event(42);
assert_eq!(third, t3);
}
#[test]
fn event_best_time_ratcheting() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t1 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
let t2 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let call_c = call.clone();
register_current_provider("BestSrc", 10, move || {
let n = call_c.fetch_add(1, Ordering::Relaxed);
match n {
0 => Some(t1),
_ => Some(t2),
}
});
reset_error_counts();
let first = get_event(-1);
assert_eq!(first, t1);
let second = get_event(-1);
assert_eq!(second, t1); assert_eq!(error_counts(), 1);
}
#[test]
fn error_counts_reset() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t_back = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
register_current_provider("AlwaysBack", 10, move || Some(t_back));
reset_error_counts();
assert_eq!(error_counts(), 0);
let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(3_000_000_000);
{
let mut inner = GENERAL_TIME.lock().unwrap();
inner.last_best_time = t_high;
}
let _ = get_event(-1);
assert!(error_counts() > 0);
reset_error_counts();
assert_eq!(error_counts(), 0);
}
#[test]
fn sync_hooks_fire_in_registration_order() {
use std::sync::{Arc, Mutex};
let captured: Arc<Mutex<Vec<(usize, SystemTime)>>> = Arc::new(Mutex::new(Vec::new()));
let cap1 = captured.clone();
register_clock_sync_hook(move |t| {
cap1.lock().unwrap().push((1, t));
});
let cap2 = captured.clone();
register_clock_sync_hook(move |t| {
cap2.lock().unwrap().push((2, t));
});
let synced = SystemTime::UNIX_EPOCH + Duration::from_secs(5_000_000_000);
notify_clock_sync(synced);
let log = captured.lock().unwrap();
let ours: Vec<_> = log.iter().filter(|(id, _)| *id == 1 || *id == 2).collect();
assert!(
ours.len() >= 2,
"both hooks must have fired at least once: {ours:?}"
);
let last1_idx = ours
.iter()
.rposition(|(id, t)| *id == 1 && *t == synced)
.expect("hook 1 fired with our synced timestamp");
let last2_idx = ours
.iter()
.rposition(|(id, t)| *id == 2 && *t == synced)
.expect("hook 2 fired with our synced timestamp");
assert!(
last1_idx < last2_idx,
"hook 1 must fire before hook 2 (registration order)"
);
}
#[test]
fn os_clock_only_bypasses_ratchet() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
let t_low = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let call_c = call.clone();
{
let mut inner = GENERAL_TIME.lock().unwrap();
inner.current_providers.clear();
inner.current_providers.push(CurrentTimeProvider {
name: "OS Clock".to_string(),
priority: 999,
get_time: Box::new(move || {
let n = call_c.fetch_add(1, Ordering::Relaxed);
Some(if n == 0 { t_high } else { t_low })
}),
interrupt_safe: true,
is_os_default: true,
});
inner.use_osd_get_current = true;
}
reset_error_counts();
let first = get_current();
assert_eq!(first, t_high);
let second = get_current();
assert_eq!(
second, t_low,
"OS-clock-only path must follow a backward step (C useOsdGetCurrent)"
);
assert_eq!(
error_counts(),
0,
"OS-clock-only backward step must not count an error"
);
}
#[test]
fn registering_provider_enables_ratchet() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t_high = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
let t_low = SystemTime::UNIX_EPOCH + Duration::from_secs(1_999_999_000);
let call = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let call_c = call.clone();
register_current_provider("Stepper", 10, move || {
let n = call_c.fetch_add(1, Ordering::Relaxed);
Some(if n == 0 { t_high } else { t_low })
});
reset_error_counts();
assert_eq!(get_current(), t_high);
assert_eq!(get_current(), t_high);
assert_eq!(error_counts(), 1);
}
#[test]
fn except_priority_skips_named_provider() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t10 = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
let t20 = SystemTime::UNIX_EPOCH + Duration::from_secs(1_900_000_000);
register_current_provider("P10", 10, move || Some(t10));
register_current_provider("P20", 20, move || Some(t20));
let (t, prio) = get_current_except_priority(10).expect("P20 answers");
assert_eq!(t, t20);
assert_eq!(prio, 20);
let (t, prio) = get_current_except_priority(-10).expect("P10 answers");
assert_eq!(t, t10);
assert_eq!(prio, 10);
}
#[test]
fn int_providers_only_seen_by_int_queries() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let t_int = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_000);
register_int_current_provider("IntClock", 5, move || Some(t_int));
assert_eq!(get_current_int(), Some(t_int));
let t_evt = SystemTime::UNIX_EPOCH + Duration::from_secs(2_000_000_500);
register_int_event_provider("IntEvent", 5, move |_| Some(t_evt));
assert_eq!(get_event_int(7), Some(t_evt));
}
#[test]
fn int_event_query_none_without_int_provider() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
register_event_provider("Plain", 10, |_| {
Some(SystemTime::UNIX_EPOCH + Duration::from_secs(1))
});
assert_eq!(get_event_int(3), None);
}
#[test]
fn highest_current_name_is_top_priority() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
assert_eq!(highest_current_name().as_deref(), Some("OS Clock"));
register_current_provider("Primary", 1, || None);
assert_eq!(highest_current_name().as_deref(), Some("Primary"));
}
#[test]
fn report_format_matches_general_time_report() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let r = report(0);
assert!(
r.starts_with("Backwards time errors prevented 0 times.\n\n"),
"report must lead with the backwards-error line: {r:?}"
);
assert!(r.contains("Current Time Providers:\n"));
assert!(
r.contains(" \"OS Clock\", priority = 999\n"),
"provider line must use C `\"name\", priority = N` form: {r:?}"
);
assert!(
r.contains("Event Time Providers:\n\tNo Providers registered.\n"),
"empty event list must print the C placeholder: {r:?}"
);
assert!(!r.contains("\" priority "));
assert!(!r.contains("(none)"));
}
#[test]
fn report_level_one_prints_time_sample() {
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let r = report(1);
assert!(
r.contains("\tCurrent Time is "),
"level>0 report must print a per-provider time sample: {r:?}"
);
}
#[test]
fn ratchet_seeds_at_epics_epoch() {
let secs = epics_epoch()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(secs, EPICS_EPOCH_UNIX_SECS);
assert_eq!(secs, 631_152_000);
let _g = TEST_LOCK.lock().unwrap();
_reset_for_testing();
let inner = GENERAL_TIME.lock().unwrap();
assert_eq!(inner.last_provided_time, epics_epoch());
assert_eq!(inner.last_best_time, epics_epoch());
assert!(inner.event_times.iter().all(|t| *t == epics_epoch()));
}
}