pgdo/coordinate/
resource.rs

1//! Manage a resource that can be started, stopped, and destroyed – i.e. a
2//! [`Subject`] – and which has different facets depending on whether it is
3//! locked exclusively, shared between multiple users, or unlocked/free.
4//!
5//! For example, a resource representing a PostgreSQL cluster would allow start,
6//! stop, and destroy actions only when it is exclusively locked. The _type_ of
7//! an unlocked cluster resource or a shared cluster resource would not even
8//! have functions available to start, stop, or destroy the cluster.
9//!
10//! The intent is to codify safe behaviours into Rust's type system so that we
11//! make it hard or impossible to mishandle a resource – and conversely, easier
12//! to correctly handle a resource.
13
14use super::{lock, CoordinateError, Subject};
15use either::{Either, Left, Right};
16
17// ----------------------------------------------------------------------------
18
19pub trait FacetFree {
20    type FacetFree<'a>
21    where
22        Self: 'a;
23
24    fn facet_free(&self) -> Self::FacetFree<'_>;
25}
26
27pub trait FacetShared {
28    type FacetShared<'a>
29    where
30        Self: 'a;
31
32    fn facet_shared(&self) -> Self::FacetShared<'_>;
33}
34
35pub trait FacetExclusive {
36    type FacetExclusive<'a>
37    where
38        Self: 'a;
39
40    fn facet_exclusive(&self) -> Self::FacetExclusive<'_>;
41}
42
43// ----------------------------------------------------------------------------
44
45/// An unlocked/free resource.
46pub struct ResourceFree<S: Subject> {
47    lock: lock::UnlockedFile,
48    subject: S,
49}
50
51impl<S: Subject> ResourceFree<S> {
52    pub fn new(lock: lock::UnlockedFile, inner: S) -> Self {
53        Self { lock, subject: inner }
54    }
55
56    /// Attempt to obtain a shared lock on the resource.
57    pub fn try_shared(self) -> Result<Either<Self, ResourceShared<S>>, CoordinateError<S::Error>> {
58        Ok(match self.lock.try_lock_shared()? {
59            Left(lock) => Left(Self { subject: self.subject, lock }),
60            Right(lock) => Right(ResourceShared { subject: self.subject, lock }),
61        })
62    }
63
64    /// Obtain a shared lock on the resource. Can block indefinitely.
65    pub fn shared(self) -> Result<ResourceShared<S>, CoordinateError<S::Error>> {
66        let lock = self.lock.lock_shared()?;
67        Ok(ResourceShared { subject: self.subject, lock })
68    }
69
70    /// Attempt to obtain an exclusive lock on the resource.
71    pub fn try_exclusive(
72        self,
73    ) -> Result<Either<Self, ResourceExclusive<S>>, CoordinateError<S::Error>> {
74        Ok(match self.lock.try_lock_exclusive()? {
75            Left(lock) => Left(Self { subject: self.subject, lock }),
76            Right(lock) => Right(ResourceExclusive { subject: self.subject, lock }),
77        })
78    }
79
80    /// Obtain an exclusive lock on the resource. Can block indefinitely.
81    pub fn exclusive(self) -> Result<ResourceExclusive<S>, CoordinateError<S::Error>> {
82        let lock = self.lock.lock_exclusive()?;
83        Ok(ResourceExclusive { subject: self.subject, lock })
84    }
85
86    /// Disassembles this resource into the lock and the inner, managed, value.
87    /// This can only be done from an unlocked/free resource.
88    pub fn into_parts(self) -> (lock::UnlockedFile, S) {
89        (self.lock, self.subject)
90    }
91}
92
93impl<S: Subject + FacetFree> ResourceFree<S> {
94    /// Return the [`FacetFree::FacetFree`] of the wrapped resource.
95    pub fn facet(&self) -> S::FacetFree<'_> {
96        self.subject.facet_free()
97    }
98}
99
100// ----------------------------------------------------------------------------
101
102/// A shared resource.
103pub struct ResourceShared<S: Subject> {
104    lock: lock::LockedFileShared,
105    subject: S,
106}
107
108impl<S: Subject> ResourceShared<S> {
109    pub fn new(lock: lock::LockedFileShared, inner: S) -> Self {
110        Self { lock, subject: inner }
111    }
112
113    /// Attempt to obtain an exclusive lock on the resource.
114    pub fn try_exclusive(
115        self,
116    ) -> Result<Either<Self, ResourceExclusive<S>>, CoordinateError<S::Error>> {
117        Ok(match self.lock.try_lock_exclusive()? {
118            Left(lock) => Left(Self { subject: self.subject, lock }),
119            Right(lock) => Right(ResourceExclusive { subject: self.subject, lock }),
120        })
121    }
122
123    /// Obtain an exclusive lock on the resource. Can block indefinitely.
124    pub fn exclusive(self) -> Result<ResourceExclusive<S>, CoordinateError<S::Error>> {
125        let lock = self.lock.lock_exclusive()?;
126        Ok(ResourceExclusive { subject: self.subject, lock })
127    }
128
129    /// Attempt to release this resource.
130    pub fn try_release(self) -> Result<Either<Self, ResourceFree<S>>, CoordinateError<S::Error>> {
131        Ok(match self.lock.try_unlock()? {
132            Left(lock) => Left(Self { subject: self.subject, lock }),
133            Right(lock) => Right(ResourceFree { subject: self.subject, lock }),
134        })
135    }
136
137    /// Release this resource. Can block indefinitely.
138    pub fn release(self) -> Result<ResourceFree<S>, CoordinateError<S::Error>> {
139        let lock = self.lock.unlock()?;
140        Ok(ResourceFree { subject: self.subject, lock })
141    }
142}
143
144impl<S: Subject + FacetShared> ResourceShared<S> {
145    /// Return the [`FacetShared::FacetShared`] of the wrapped resource.
146    pub fn facet(&self) -> S::FacetShared<'_> {
147        self.subject.facet_shared()
148    }
149}
150
151// ----------------------------------------------------------------------------
152
153/// A resource held exclusively.
154pub struct ResourceExclusive<S: Subject> {
155    lock: lock::LockedFileExclusive,
156    subject: S,
157}
158
159impl<S: Subject> ResourceExclusive<S> {
160    pub fn new(lock: lock::LockedFileExclusive, inner: S) -> Self {
161        Self { lock, subject: inner }
162    }
163
164    /// Attempt to obtain a shared lock on the resource.
165    pub fn try_shared(self) -> Result<Either<Self, ResourceShared<S>>, CoordinateError<S::Error>> {
166        Ok(match self.lock.try_lock_shared()? {
167            Left(lock) => Left(Self { subject: self.subject, lock }),
168            Right(lock) => Right(ResourceShared { subject: self.subject, lock }),
169        })
170    }
171
172    /// Obtain a shared lock on the resource. Can block indefinitely.
173    pub fn shared(self) -> Result<ResourceShared<S>, CoordinateError<S::Error>> {
174        let lock = self.lock.lock_shared()?;
175        Ok(ResourceShared { subject: self.subject, lock })
176    }
177
178    /// Attempt to release this resource.
179    pub fn try_release(self) -> Result<Either<Self, ResourceFree<S>>, CoordinateError<S::Error>> {
180        Ok(match self.lock.try_unlock()? {
181            Left(lock) => Left(Self { subject: self.subject, lock }),
182            Right(lock) => Right(ResourceFree { subject: self.subject, lock }),
183        })
184    }
185
186    /// Release this resource. Can block indefinitely.
187    pub fn release(self) -> Result<ResourceFree<S>, CoordinateError<S::Error>> {
188        let lock = self.lock.unlock()?;
189        Ok(ResourceFree { subject: self.subject, lock })
190    }
191}
192
193impl<S: Subject + FacetExclusive> ResourceExclusive<S> {
194    /// Return the [`FacetExclusive::FacetExclusive`] of the wrapped resource.
195    pub fn facet(&self) -> S::FacetExclusive<'_> {
196        self.subject.facet_exclusive()
197    }
198}