saluki_core/runtime/state/resources/
sublease.rs

1//! Resource subleasing.
2
3use std::sync::{Arc, Weak};
4
5use tokio::sync::watch;
6
7/// The ledger a resource's subleases are recorded in.
8///
9/// One per registry entry, outliving the individual leases taken out on it.
10pub(super) struct SubleaseLedger {
11    outstanding: watch::Sender<usize>,
12}
13
14impl SubleaseLedger {
15    /// Creates an empty ledger.
16    pub(super) fn new() -> Arc<Self> {
17        Arc::new(Self {
18            outstanding: watch::Sender::new(0),
19        })
20    }
21
22    /// Returns the number of subleases currently outstanding.
23    pub(super) fn outstanding(&self) -> usize {
24        *self.outstanding.borrow()
25    }
26
27    /// Waits until every sublease has been returned.
28    ///
29    /// Returns immediately when none are outstanding, which is the case for any resource that never subdivides
30    /// itself, and for one whose holder finished with its subresources before releasing the head lease.
31    pub(super) async fn settled(&self) {
32        // The borrow is released before awaiting, and `wait_for` checks the current value before it waits, so a
33        // sublease returned in between is accounted for rather than missed.
34        let mut receiver = self.outstanding.subscribe();
35        let _ = receiver.wait_for(|outstanding| *outstanding == 0).await;
36    }
37
38    /// Records a new sublease.
39    fn acquire(&self) {
40        self.outstanding.send_modify(|outstanding| *outstanding += 1);
41    }
42
43    /// Records the return of a sublease.
44    fn release(&self) {
45        self.outstanding.send_modify(|outstanding| {
46            *outstanding = outstanding.saturating_sub(1);
47        });
48    }
49}
50
51/// Subleases on a leased resource.
52///
53/// In some scenarios, a leased resource may actually represent a _collection_ of "subresources" that can be handed out
54/// piecemeal, at arbitrary points in time. These subresources are often wrapped in their own types to ensure proper
55/// behavior (RAII style), but ultimately must roll back up to the original leased resource to ensure proper behavior
56/// within the resource registry. This requires knowing, deterministically, when a leased resource has returned
57/// completely.
58///
59/// Subleases provide an RAII guard mechanism that allows an owned guard to be paired with a subresource such that when
60/// the subresource is dropped, the sublease is dropped with it. Subleases are tracked by a parent leased resource, and
61/// only when all subleases have been dropped is the parent leased resource able to be reacquired by another caller.
62///
63/// [`Subleases`] is not itself a sublease. It deliberately doesn't keep the resource's lease alive: a handle kept in
64/// order to issue subleases would otherwise hold the resource's lease open for as long as the resource existed, and
65/// no acquirer would ever be handed it again.
66#[derive(Clone)]
67pub struct Subleases {
68    ledger: Weak<SubleaseLedger>,
69}
70
71impl Subleases {
72    pub(super) fn from_ledger(ledger: &Arc<SubleaseLedger>) -> Self {
73        Self {
74            ledger: Arc::downgrade(ledger),
75        }
76    }
77
78    /// Issues a sublease, held until the returned value is dropped.
79    ///
80    /// Returns `None` once the resource's registry entry is gone, leaving nothing to record a sublease against. That
81    /// only happens after the resource itself has been dropped -- a
82    /// [`discard`][super::ResourceLease::discard]ed resource keeps its entry until its subleases are returned -- so a
83    /// resource still in use always gets its sublease.
84    ///
85    /// # Subleasing after the head lease is returned
86    ///
87    /// Nothing here prevents a sublease being issued after the head lease has gone back to the registry, which under
88    /// the property-law reading of the name shouldn't be possible. In practice a resource is only reachable through
89    /// its head lease, so a holder has nothing to issue against once it has released it. Genuinely preventing it
90    /// would mean fixing the number of subleases up front, or issuing them all eagerly and handing them out, which
91    /// costs more than the case is worth.
92    pub fn issue(&self) -> Option<Sublease> {
93        let ledger = self.ledger.upgrade()?;
94        ledger.acquire();
95
96        Some(Sublease { ledger })
97    }
98}
99
100impl std::fmt::Debug for Subleases {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.debug_struct("Subleases")
103            .field("outstanding", &self.ledger.upgrade().map(|ledger| ledger.outstanding()))
104            .finish()
105    }
106}
107
108/// A sublease on part of a leased resource.
109///
110/// Held by a subresource for as long as it is in use. Outstanding subleases keep the resource's lease alive: the
111/// registry won't hand the resource to another acquirer while one is held, even once the head
112/// [`ResourceLease`][super::ResourceLease] has been dropped -- nor build a replacement for one that was
113/// [`discard`][super::ResourceLease::discard]ed, since that replacement would exist alongside whatever this sublease
114/// is still holding open.
115pub struct Sublease {
116    ledger: Arc<SubleaseLedger>,
117}
118
119impl Drop for Sublease {
120    fn drop(&mut self) {
121        self.ledger.release();
122    }
123}
124
125impl std::fmt::Debug for Sublease {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        f.debug_struct("Sublease").finish_non_exhaustive()
128    }
129}