grafos_std/
lease.rs

1//! Shared lease lifecycle primitives used by resource modules.
2//!
3//! This module centralizes lease metadata and lifecycle behavior
4//! (active/expired/revoked, renewal, and explicit free).
5
6extern crate alloc;
7
8use alloc::rc::Rc;
9use core::cell::RefCell;
10use core::sync::atomic::{AtomicU32, Ordering};
11
12use crate::error::{FabricError, Result};
13use crate::host;
14
15/// Public lease status for all resource lease types.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LeaseStatus {
18    /// Lease is active and operations are allowed.
19    Active,
20    /// Lease TTL has elapsed; operations fail with `LeaseExpired`.
21    Expired,
22    /// Lease was explicitly freed/revoked.
23    Revoked,
24}
25
26/// Snapshot of lease metadata for diagnostics/observability.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct LeaseInfo {
29    pub lease_id: u128,
30    pub created_at_unix_secs: u64,
31    pub expires_at_unix_secs: u64,
32    pub status: LeaseStatus,
33}
34
35#[derive(Debug)]
36pub(crate) struct LeaseState {
37    pub lease_id: u128,
38    pub created_at_unix_secs: u64,
39    pub expires_at_unix_secs: u64,
40    pub status: LeaseStatus,
41    pub resource_tag: u8,
42}
43
44pub(crate) type SharedLeaseState = Rc<RefCell<LeaseState>>;
45
46static LEASE_SEQ: AtomicU32 = AtomicU32::new(1);
47
48fn next_lease_id(tag: u8, now_unix_secs: u64) -> u128 {
49    let seq = LEASE_SEQ.fetch_add(1, Ordering::Relaxed) as u128;
50    ((now_unix_secs as u128) << 64) | ((tag as u128) << 56) | seq
51}
52
53pub(crate) fn new_shared_lease(tag: u8, lease_secs: u64) -> SharedLeaseState {
54    let created_at_unix_secs = host::unix_time_secs();
55    let ttl = lease_secs.max(1);
56    Rc::new(RefCell::new(LeaseState {
57        lease_id: next_lease_id(tag, created_at_unix_secs),
58        created_at_unix_secs,
59        expires_at_unix_secs: created_at_unix_secs.saturating_add(ttl),
60        status: LeaseStatus::Active,
61        resource_tag: tag,
62    }))
63}
64
65pub(crate) fn new_shared_lease_from_parts(
66    lease_id: u128,
67    created_at_unix_secs: u64,
68    expires_at_unix_secs: u64,
69    status: LeaseStatus,
70) -> SharedLeaseState {
71    // Extract resource_tag from lease_id encoding: bits [56..64) of the lower 64 bits.
72    let resource_tag = ((lease_id >> 56) & 0xFF) as u8;
73    Rc::new(RefCell::new(LeaseState {
74        lease_id,
75        created_at_unix_secs,
76        expires_at_unix_secs,
77        status,
78        resource_tag,
79    }))
80}
81
82fn refresh_status(state: &mut LeaseState, now_unix_secs: u64) {
83    if state.status == LeaseStatus::Active && now_unix_secs >= state.expires_at_unix_secs {
84        state.status = LeaseStatus::Expired;
85        #[cfg(feature = "observe")]
86        crate::observe_hooks::on_lease_expired(state.resource_tag, state.lease_id, "local");
87    }
88}
89
90pub(crate) fn status(state: &SharedLeaseState) -> LeaseStatus {
91    let mut s = state.borrow_mut();
92    refresh_status(&mut s, host::unix_time_secs());
93    s.status
94}
95
96pub(crate) fn ensure_active(state: &SharedLeaseState) -> Result<()> {
97    match status(state) {
98        LeaseStatus::Active => Ok(()),
99        LeaseStatus::Revoked => Err(FabricError::Revoked),
100        LeaseStatus::Expired => Err(FabricError::LeaseExpired),
101    }
102}
103
104pub(crate) fn lease_id(state: &SharedLeaseState) -> u128 {
105    state.borrow().lease_id
106}
107
108pub(crate) fn created_at_unix_secs(state: &SharedLeaseState) -> u64 {
109    state.borrow().created_at_unix_secs
110}
111
112pub(crate) fn expires_at_unix_secs(state: &SharedLeaseState) -> u64 {
113    let mut s = state.borrow_mut();
114    refresh_status(&mut s, host::unix_time_secs());
115    s.expires_at_unix_secs
116}
117
118pub(crate) fn info(state: &SharedLeaseState) -> LeaseInfo {
119    let mut s = state.borrow_mut();
120    refresh_status(&mut s, host::unix_time_secs());
121    LeaseInfo {
122        lease_id: s.lease_id,
123        created_at_unix_secs: s.created_at_unix_secs,
124        expires_at_unix_secs: s.expires_at_unix_secs,
125        status: s.status,
126    }
127}
128
129pub(crate) fn renew(state: &SharedLeaseState, duration_secs: u64) -> Result<()> {
130    if duration_secs == 0 {
131        return Err(FabricError::IoError(-1));
132    }
133
134    let now_unix_secs = host::unix_time_secs();
135    let mut s = state.borrow_mut();
136    refresh_status(&mut s, now_unix_secs);
137    if s.status != LeaseStatus::Active {
138        return Err(FabricError::LeaseExpired);
139    }
140
141    let requested_exp = now_unix_secs.saturating_add(duration_secs);
142    if requested_exp > s.expires_at_unix_secs {
143        s.expires_at_unix_secs = requested_exp;
144    }
145    Ok(())
146}
147
148pub(crate) fn free(state: &SharedLeaseState) {
149    let mut s = state.borrow_mut();
150    s.status = LeaseStatus::Revoked;
151}
152
153pub(crate) fn set_status(state: &SharedLeaseState, new_status: LeaseStatus) {
154    let mut s = state.borrow_mut();
155    #[cfg(feature = "observe")]
156    let old_status = s.status;
157    s.status = new_status;
158    #[cfg(feature = "observe")]
159    {
160        if old_status == LeaseStatus::Active && new_status == LeaseStatus::Expired {
161            crate::observe_hooks::on_lease_expired(s.resource_tag, s.lease_id, "local");
162        } else if old_status == LeaseStatus::Active && new_status == LeaseStatus::Revoked {
163            crate::observe_hooks::on_lease_revoked(s.resource_tag, s.lease_id, "local");
164        }
165    }
166}
167
168pub(crate) fn set_expires_at_unix_secs(state: &SharedLeaseState, expires_at_unix_secs: u64) {
169    let mut s = state.borrow_mut();
170    s.expires_at_unix_secs = expires_at_unix_secs;
171}