1use std::{
3 io,
4 path::Path,
5 sync::{
6 Arc, Mutex,
7 atomic::{AtomicU64, Ordering},
8 },
9 time::Duration,
10};
11use tokio::{sync::watch, time::Instant};
12
13pub fn allocate(
16 _lock: &crate::ProcessLock,
17 directory: &Path,
18 counter: &Path,
19 selected: u64,
20) -> io::Result<u64> {
21 let prior = match std::fs::read_to_string(counter) {
22 Ok(text) => text.trim().parse::<u64>().map_err(io::Error::other)?,
23 Err(e) if e.kind() == io::ErrorKind::NotFound => 0,
24 Err(e) => return Err(e),
25 };
26 let mut maximum = prior.max(selected);
27 for entry in std::fs::read_dir(directory)? {
28 if let Some(value) = entry?
29 .path()
30 .file_stem()
31 .and_then(|name| name.to_str())
32 .and_then(|name| name.parse::<u64>().ok())
33 {
34 maximum = maximum.max(value);
35 }
36 }
37 let next = maximum
38 .checked_add(1)
39 .ok_or_else(|| io::Error::other("generation exhausted"))?;
40 crate::publish_record(counter, next.to_string().as_bytes())?;
41 Ok(next)
42}
43
44#[derive(Default)]
45struct Sessions {
46 active: usize,
47 legacy: usize,
48 retired: bool,
49 superseded_since: Option<Instant>,
50}
51
52pub struct Generation {
54 own: u64,
55 selected: AtomicU64,
56 sessions: Mutex<Sessions>,
57 changes: watch::Sender<()>,
58 started: Instant,
59 staged: bool,
60 drain_limit: Option<Duration>,
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum Retirement {
67 Idle,
69 DrainExpired {
72 active: usize,
74 legacy: usize,
76 },
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81#[non_exhaustive]
82pub struct Snapshot {
83 pub own: u64,
85 pub selected: u64,
87 pub active: usize,
89 pub legacy: usize,
91 pub retired: bool,
93 pub superseded_for: Option<Duration>,
95}
96impl Generation {
97 #[expect(
105 clippy::disallowed_methods,
106 reason = "Discovery has no change notification; session changes wake the same select immediately."
107 )]
108 pub async fn run_until_retired<F, Fut>(
109 &self,
110 interval: Duration,
111 staging_lifetime: Duration,
112 mut refresh: F,
113 ) -> Retirement
114 where
115 F: FnMut() -> Fut,
116 Fut: std::future::Future<Output = ()>,
117 {
118 let mut changes = self.subscribe();
119 loop {
120 changes.borrow_and_update();
121 if let Some(retirement) = self.try_retire(staging_lifetime, false, true) {
122 return retirement;
123 }
124 refresh().await;
125 if let Some(retirement) = self.retire(staging_lifetime) {
126 return retirement;
127 }
128 let wait = self
130 .drain_remaining()
131 .map_or(interval, |remaining| remaining.min(interval));
132 tokio::select! {
133 _ = tokio::time::sleep(wait) => {},
134 _ = changes.changed() => {},
135 }
136 }
137 }
138 pub fn new(own: u64, selected: u64, staged: bool) -> Self {
140 let started = Instant::now();
141 Self {
142 own,
143 selected: AtomicU64::new(selected),
144 sessions: Mutex::new(Sessions {
145 superseded_since: (selected > own).then_some(started),
146 ..Sessions::default()
147 }),
148 changes: watch::channel(()).0,
149 started,
150 staged,
151 drain_limit: None,
152 }
153 }
154 #[must_use]
161 pub fn with_drain_limit(mut self, limit: Duration) -> Self {
162 self.drain_limit = Some(limit);
163 self
164 }
165 pub fn selected(&self) -> u64 {
167 self.selected.load(Ordering::Acquire)
168 }
169 pub fn select(&self, selected: u64) -> io::Result<()> {
171 let mut state = self.sessions.lock().unwrap_or_else(|e| e.into_inner());
174 let previous = self
175 .selected
176 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |old| {
177 (selected >= old).then_some(selected)
178 })
179 .map_err(|_| io::Error::other("generation selection moved backwards"))?;
180 if previous != selected {
181 if selected > self.own {
182 state.superseded_since.get_or_insert_with(Instant::now);
183 }
184 drop(state);
185 self.changes.send_replace(());
186 }
187 Ok(())
188 }
189 pub fn admit(self: &Arc<Self>) -> Option<SessionLease> {
191 let mut state = self.sessions.lock().unwrap_or_else(|e| e.into_inner());
192 if state.retired {
193 return None;
194 }
195 state.active += 1;
196 Some(SessionLease {
197 generation: Arc::clone(self),
198 legacy: false,
199 })
200 }
201 pub fn retire_if_idle(&self, staging_lifetime: Duration) -> bool {
205 self.try_retire(staging_lifetime, true, false).is_some()
206 }
207 pub fn retire(&self, staging_lifetime: Duration) -> Option<Retirement> {
210 self.try_retire(staging_lifetime, true, true)
211 }
212 fn try_retire(
215 &self,
216 staging_lifetime: Duration,
217 idle: bool,
218 drain: bool,
219 ) -> Option<Retirement> {
220 let mut state = self.sessions.lock().unwrap_or_else(|e| e.into_inner());
221 let selected = self.selected();
222 if selected <= self.own
223 && !(self.staged && selected < self.own && self.started.elapsed() > staging_lifetime)
224 {
225 return None;
226 }
227 let expired = drain
229 && self
230 .drain_limit
231 .zip(state.superseded_since)
232 .is_some_and(|(limit, since)| since.elapsed() >= limit);
233 let idle = idle && state.active == 0;
234 if !(expired || idle) {
235 return None;
236 }
237 state.retired = true;
238 Some(if state.active == 0 {
239 Retirement::Idle
240 } else {
241 Retirement::DrainExpired {
242 active: state.active,
243 legacy: state.legacy,
244 }
245 })
246 }
247 fn drain_remaining(&self) -> Option<Duration> {
249 let state = self.sessions.lock().unwrap_or_else(|e| e.into_inner());
250 self.drain_limit
251 .zip(state.superseded_since)
252 .map(|(limit, since)| limit.saturating_sub(since.elapsed()))
253 }
254 pub fn snapshot(&self) -> Snapshot {
256 let state = self.sessions.lock().unwrap_or_else(|e| e.into_inner());
257 Snapshot {
258 own: self.own,
259 selected: self.selected(),
260 active: state.active,
261 legacy: state.legacy,
262 retired: state.retired,
263 superseded_for: state.superseded_since.map(|since| since.elapsed()),
264 }
265 }
266 pub fn subscribe(&self) -> watch::Receiver<()> {
273 self.changes.subscribe()
274 }
275}
276
277pub struct SessionLease {
279 generation: Arc<Generation>,
280 legacy: bool,
281}
282impl SessionLease {
283 pub fn mark_legacy(&mut self) {
285 if !self.legacy {
286 self.legacy = true;
287 self.generation
288 .sessions
289 .lock()
290 .unwrap_or_else(|e| e.into_inner())
291 .legacy += 1;
292 }
293 }
294}
295impl Drop for SessionLease {
296 fn drop(&mut self) {
297 let mut state = self
298 .generation
299 .sessions
300 .lock()
301 .unwrap_or_else(|e| e.into_inner());
302 state.active -= 1;
303 if self.legacy {
304 state.legacy -= 1;
305 }
306 self.generation.changes.send_replace(());
307 }
308}