hyperdir 0.7.0

A container for inodes implemented as a Hyperfile
Documentation
//! A coarse shared clock derived from S3 response `Date` headers, so lock and
//! lease expiry stay consistent across mounts even when local wall clocks are
//! skewed relative to each other.
//!
//! All mounts of a tree talk to the same S3 region, so S3's clock is a single
//! reference. [`S3ClockInterceptor`] (attached once to the S3 client) records
//! the `(S3 - local)` skew from the `Date` header of *every* response, adding no
//! extra request. [`now_ms`] returns the local clock when the skew is small
//! (local is sub-millisecond and current, while `Date` is only second-precise
//! and sampled one RTT ago) and the S3-corrected time when the local clock is
//! badly off, so independent mounts agree on whether a lease has expired.

use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

/// Below this `|skew|` the local clock is trusted; above it, S3 time is used.
/// 2s absorbs the 1s quantization of the HTTP `Date` header plus request RTT.
pub const CLOCK_TRUST_LOCAL_MS: i64 = 2_000;

/// Most recent `(S3_unix_ms - local_unix_ms)` observation.
static SKEW_MS: AtomicI64 = AtomicI64::new(0);
static SKEW_SEEN: AtomicBool = AtomicBool::new(false);

/// Raw local Unix time in milliseconds (saturating).
pub(crate) fn local_unix_ms() -> i64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| i64::try_from(d.as_millis()).unwrap_or(i64::MAX))
        .unwrap_or(0)
}

/// Record one observation of the S3 server clock (Unix ms) against the local clock.
fn record_skew(s3_unix_ms: i64) {
    SKEW_MS.store(s3_unix_ms - local_unix_ms(), Ordering::Relaxed);
    SKEW_SEEN.store(true, Ordering::Relaxed);
}

/// Last observed `(S3 - local)` skew in ms, or `None` if no S3 response has been
/// seen yet. Use it at mount to flag or refuse a badly-skewed host.
pub fn s3_clock_skew_ms() -> Option<i64> {
    SKEW_SEEN.load(Ordering::Relaxed).then(|| SKEW_MS.load(Ordering::Relaxed))
}

/// Current Unix time in milliseconds, corrected toward the S3 server clock when
/// the local clock is off by more than [`CLOCK_TRUST_LOCAL_MS`]. Saturating.
pub(crate) fn now_ms() -> i64 {
    let local = local_unix_ms();
    match s3_clock_skew_ms() {
        Some(skew) if skew.abs() > CLOCK_TRUST_LOCAL_MS => local.saturating_add(skew),
        _ => local,
    }
}

/// S3 client interceptor that records the server clock from each response's
/// `Date` header (RFC 2822). Attach it once on the client; it issues no requests
/// of its own and simply piggybacks on the responses of existing operations.
#[derive(Clone, Default)]
pub struct S3ClockInterceptor;

impl fmt::Debug for S3ClockInterceptor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "S3ClockInterceptor")
    }
}

impl aws_sdk_s3::config::Intercept for S3ClockInterceptor {
    fn name(&self) -> &'static str {
        "S3ClockInterceptor"
    }

    fn read_after_execution(
        &self,
        ctx: &aws_sdk_s3::config::interceptors::FinalizerInterceptorContextRef<'_>,
        _: &aws_sdk_s3::config::RuntimeComponents,
        _: &mut aws_sdk_s3::config::ConfigBag,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        if let Some(resp) = ctx.response() {
            if let Some(date) = resp.headers().get("date") {
                if let Ok(dt) = chrono::DateTime::parse_from_rfc2822(date) {
                    record_skew(dt.timestamp_millis());
                }
            }
        }
        Ok(())
    }
}