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}