stackless_core/state/
lease.rs1use std::time::Duration;
5
6use super::error::StateError;
7use super::row::Row;
8use super::store::Store;
9
10#[derive(Debug, Clone, Copy)]
11pub struct Lease {
12 pub duration: Duration,
13 pub expires_at: i64,
14}
15
16impl Lease {
17 pub fn remaining(&self, now: i64) -> Duration {
18 Duration::from_secs(self.expires_at.saturating_sub(now).max(0) as u64)
19 }
20}
21
22impl TryFrom<&Row> for Lease {
23 type Error = StateError;
24
25 fn try_from(row: &Row) -> Result<Self, Self::Error> {
26 Ok(Self {
27 duration: Duration::from_secs(row.get_i64(0)?.max(0) as u64),
28 expires_at: row.get_i64(1)?,
29 })
30 }
31}
32
33impl Store {
34 pub fn renew_lease(&self, instance: &str, duration: Duration) -> Result<Lease, StateError> {
36 let now = Self::now();
37 let expires_at = now + duration.as_secs() as i64;
38 self.execute(
39 "INSERT INTO leases (instance, duration_secs, expires_at) VALUES (?1, ?2, ?3)
40 ON CONFLICT(instance) DO UPDATE SET
41 duration_secs = excluded.duration_secs,
42 expires_at = excluded.expires_at",
43 &[
44 instance.into(),
45 (duration.as_secs() as i64).into(),
46 expires_at.into(),
47 ],
48 )?;
49 Ok(Lease {
50 duration,
51 expires_at,
52 })
53 }
54
55 pub fn renew_lease_at_recorded_duration(&self, instance: &str) -> Result<(), StateError> {
58 self.execute(
59 "UPDATE leases SET expires_at = ?2 + duration_secs WHERE instance = ?1",
60 &[instance.into(), Self::now().into()],
61 )?;
62 Ok(())
63 }
64
65 pub fn lease(&self, instance: &str) -> Result<Option<Lease>, StateError> {
66 self.query_row(
67 "SELECT duration_secs, expires_at FROM leases WHERE instance = ?1",
68 &[instance.into()],
69 |row| Lease::try_from(row),
70 )
71 }
72
73 pub fn delete_lease(&self, instance: &str) -> Result<(), StateError> {
75 self.execute("DELETE FROM leases WHERE instance = ?1", &[instance.into()])?;
76 Ok(())
77 }
78
79 pub fn expired_instances(&self) -> Result<Vec<String>, StateError> {
81 self.query_map(
82 "SELECT i.name FROM instances i JOIN leases l ON l.instance = i.name
83 WHERE i.status = 'active' AND l.expires_at <= ?1 ORDER BY i.name",
84 &[Self::now().into()],
85 |row: &Row| row.get_string(0),
86 )
87 }
88}