1use crate::error::{ProviderError, Result};
4use std::collections::HashMap;
5use std::panic::{catch_unwind, AssertUnwindSafe};
6use std::sync::{Arc, Condvar, Mutex};
7use std::time::{Duration, Instant};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct LoadKey {
12 pub kind: &'static str,
13 pub id: String,
14 pub path: String,
15}
16
17impl LoadKey {
18 pub fn stt(id: impl Into<String>, path: impl Into<String>) -> Self {
19 Self {
20 kind: "stt",
21 id: id.into(),
22 path: path.into(),
23 }
24 }
25
26 pub fn tts(id: impl Into<String>, path: impl Into<String>) -> Self {
27 Self {
28 kind: "tts",
29 id: id.into(),
30 path: path.into(),
31 }
32 }
33}
34
35enum SlotState<T> {
36 Loading { waiters: usize },
37 Ready(Arc<T>),
38 Failed { message: String, at: Instant },
39}
40
41pub struct Singleflight<T> {
43 inner: Mutex<HashMap<LoadKey, SlotState<T>>>,
44 cv: Condvar,
45 fail_ttl: Duration,
47}
48
49impl<T> Singleflight<T> {
50 pub fn new(fail_ttl: Duration) -> Self {
51 Self {
52 inner: Mutex::new(HashMap::new()),
53 cv: Condvar::new(),
54 fail_ttl,
55 }
56 }
57
58 pub fn get_ready(&self, key: &LoadKey) -> Option<Arc<T>> {
59 let guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
60 match guard.get(key) {
61 Some(SlotState::Ready(v)) => Some(Arc::clone(v)),
62 _ => None,
63 }
64 }
65
66 pub fn contains_ready(&self, key: &LoadKey) -> bool {
67 self.get_ready(key).is_some()
68 }
69
70 pub fn invalidate(&self, key: &LoadKey) {
71 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
72 guard.remove(key);
73 self.cv.notify_all();
74 }
75
76 pub fn clear(&self) {
77 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
78 guard.clear();
79 self.cv.notify_all();
80 }
81
82 pub fn ready_count(&self) -> usize {
83 let guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
84 guard
85 .values()
86 .filter(|s| matches!(s, SlotState::Ready(_)))
87 .count()
88 }
89
90 pub fn finish_load_published(&self, key: &LoadKey) {
95 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
96 guard.remove(key);
97 self.cv.notify_all();
98 }
99
100 pub fn finish_load_failed(&self, key: &LoadKey, message: String) {
102 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
103 guard.insert(
104 key.clone(),
105 SlotState::Failed {
106 message,
107 at: Instant::now(),
108 },
109 );
110 self.cv.notify_all();
111 }
112
113 pub fn begin_or_wait(&self, key: &LoadKey) -> BeginLoad {
120 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
121 loop {
122 if let Some(SlotState::Failed { at, .. }) = guard.get(key) {
123 if at.elapsed() > self.fail_ttl {
124 guard.remove(key);
125 }
126 }
127 match guard.get_mut(key) {
128 Some(SlotState::Ready(_)) => {
129 guard.remove(key);
132 self.cv.notify_all();
133 return BeginLoad::WaitDone;
134 }
135 Some(SlotState::Failed { message, .. }) => {
136 return BeginLoad::Failed(message.clone());
137 }
138 Some(SlotState::Loading { waiters }) => {
139 *waiters += 1;
140 guard = self.cv.wait(guard).unwrap_or_else(|e| e.into_inner());
141 if !matches!(guard.get(key), Some(SlotState::Loading { .. })) {
144 if let Some(SlotState::Failed { message, .. }) = guard.get(key) {
145 return BeginLoad::Failed(message.clone());
146 }
147 return BeginLoad::WaitDone;
148 }
149 }
150 None => {
151 guard.insert(key.clone(), SlotState::Loading { waiters: 0 });
152 return BeginLoad::Leader;
153 }
154 }
155 }
156 }
157
158 pub fn begin_or_wait_guard(
163 &self,
164 key: LoadKey,
165 ) -> std::result::Result<Option<LeaderGuard<'_, T>>, String> {
166 match self.begin_or_wait(&key) {
167 BeginLoad::Leader => Ok(Some(LeaderGuard {
168 flight: self,
169 key,
170 finished: false,
171 })),
172 BeginLoad::WaitDone => Ok(None),
173 BeginLoad::Failed(m) => Err(m),
174 }
175 }
176}
177
178#[derive(Debug)]
180pub enum BeginLoad {
181 Leader,
183 WaitDone,
185 Failed(String),
187}
188
189pub struct LeaderGuard<'a, T> {
194 flight: &'a Singleflight<T>,
195 key: LoadKey,
196 finished: bool,
197}
198
199impl<'a, T> LeaderGuard<'a, T> {
200 pub fn key(&self) -> &LoadKey {
201 &self.key
202 }
203
204 pub fn success(mut self) {
205 self.flight.finish_load_published(&self.key);
206 self.finished = true;
207 }
208
209 pub fn fail(mut self, message: impl Into<String>) {
210 self.flight.finish_load_failed(&self.key, message.into());
211 self.finished = true;
212 }
213}
214
215impl<T> Drop for LeaderGuard<'_, T> {
216 fn drop(&mut self) {
217 if !self.finished {
218 self.flight.finish_load_failed(
219 &self.key,
220 "loader panicked or abandoned before publish".into(),
221 );
222 }
223 }
224}
225
226impl<T> Default for Singleflight<T> {
227 fn default() -> Self {
228 Self::new(Duration::from_secs(2))
229 }
230}
231
232impl<T: Send + Sync + 'static> Singleflight<T> {
233 pub fn get_or_load<F>(&self, key: LoadKey, loader: F) -> Result<Arc<T>>
238 where
239 F: FnOnce() -> Result<T>,
240 {
241 let mut leader = false;
243 let early: Option<Result<Arc<T>>> = {
244 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
245 loop {
246 if let Some(SlotState::Failed { at, .. }) = guard.get(&key) {
248 if at.elapsed() > self.fail_ttl {
249 guard.remove(&key);
250 }
251 }
252
253 match guard.get_mut(&key) {
254 Some(SlotState::Ready(v)) => {
255 break Some(Ok(Arc::clone(v)));
256 }
257 Some(SlotState::Failed { message, .. }) => {
258 break Some(Err(ProviderError::ModelLoad {
259 model: key.id.clone(),
260 reason: message.clone(),
261 }
262 .into()));
263 }
264 Some(SlotState::Loading { waiters }) => {
265 *waiters += 1;
266 guard = self.cv.wait(guard).unwrap_or_else(|e| e.into_inner());
267 }
269 None => {
270 guard.insert(key.clone(), SlotState::Loading { waiters: 0 });
271 leader = true;
272 break None;
273 }
274 }
275 }
276 };
277
278 if let Some(r) = early {
279 return r;
280 }
281 debug_assert!(leader);
282
283 let result = match catch_unwind(AssertUnwindSafe(loader)) {
285 Ok(r) => r,
286 Err(_) => Err(ProviderError::ModelLoad {
287 model: key.id.clone(),
288 reason: "loader panicked".into(),
289 }
290 .into()),
291 };
292 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
293 match result {
294 Ok(value) => {
295 let arc = Arc::new(value);
296 guard.insert(key, SlotState::Ready(Arc::clone(&arc)));
297 self.cv.notify_all();
298 Ok(arc)
299 }
300 Err(e) => {
301 let message = e.to_string();
302 guard.insert(
303 key,
304 SlotState::Failed {
305 message,
306 at: Instant::now(),
307 },
308 );
309 self.cv.notify_all();
310 Err(e)
311 }
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use std::sync::atomic::{AtomicUsize, Ordering};
320 use std::thread;
321
322 #[test]
323 fn single_loader_for_many_waiters() {
324 let sf = Arc::new(Singleflight::<u32>::default());
325 let loads = Arc::new(AtomicUsize::new(0));
326 let key = LoadKey::stt("m1", "/tmp/m1");
327 let mut handles = vec![];
328 for _ in 0..16 {
329 let sf = Arc::clone(&sf);
330 let loads = Arc::clone(&loads);
331 let key = key.clone();
332 handles.push(thread::spawn(move || {
333 sf.get_or_load(key, || {
334 loads.fetch_add(1, Ordering::SeqCst);
335 thread::sleep(Duration::from_millis(30));
336 Ok(42)
337 })
338 .unwrap()
339 }));
340 }
341 for h in handles {
342 assert_eq!(*h.join().unwrap(), 42);
343 }
344 assert_eq!(loads.load(Ordering::SeqCst), 1);
345 }
346
347 #[test]
348 fn failure_delivered_to_waiters() {
349 let sf = Arc::new(Singleflight::<u32>::new(Duration::from_secs(10)));
350 let key = LoadKey::stt("bad", "/tmp/bad");
351 let sf2 = Arc::clone(&sf);
352 let key2 = key.clone();
353 let leader = thread::spawn(move || {
354 sf2.get_or_load(key2, || {
355 thread::sleep(Duration::from_millis(20));
356 Err(ProviderError::ModelLoad {
357 model: "bad".into(),
358 reason: "boom".into(),
359 }
360 .into())
361 })
362 });
363 thread::sleep(Duration::from_millis(5));
364 let waiter = sf.get_or_load(key, || Ok(1));
365 assert!(leader.join().unwrap().is_err());
366 assert!(waiter.is_err());
367 }
368
369 #[test]
370 fn panic_does_not_stick_loading() {
371 let sf = Singleflight::<u32>::new(Duration::from_millis(50));
372 let key = LoadKey::stt("panic", "/tmp/p");
373 let err = sf.get_or_load(key.clone(), || panic!("boom"));
374 assert!(err.is_err());
375 thread::sleep(Duration::from_millis(60));
377 let v = sf.get_or_load(key, || Ok(7)).unwrap();
378 assert_eq!(*v, 7);
379 }
380
381 #[test]
382 fn leader_guard_drop_unblocks_waiters() {
383 let sf = Arc::new(Singleflight::<u32>::new(Duration::from_millis(500)));
385 let key = LoadKey::stt("abandon", "/tmp/a");
386 let held = Arc::new(std::sync::Barrier::new(2));
387 let sf2 = Arc::clone(&sf);
388 let key2 = key.clone();
389 let held2 = Arc::clone(&held);
390 let leader = thread::spawn(move || {
391 let g = sf2.begin_or_wait_guard(key2).unwrap().expect("leader");
392 held2.wait();
395 drop(g);
397 });
398 held.wait();
399 let waiter = thread::spawn({
400 let sf = Arc::clone(&sf);
401 let key = key.clone();
402 move || sf.begin_or_wait(&key)
403 });
404 thread::sleep(Duration::from_millis(30));
406 leader.join().unwrap();
407 match waiter.join().unwrap() {
408 BeginLoad::Failed(m) => assert!(m.contains("abandon") || m.contains("panic")),
409 other => panic!("expected Failed after abandon, got {other:?}"),
410 }
411 thread::sleep(Duration::from_millis(520));
413 let g = sf
414 .begin_or_wait_guard(key.clone())
415 .unwrap()
416 .expect("leader2");
417 g.success();
418 match sf.begin_or_wait(&key) {
421 BeginLoad::Leader | BeginLoad::WaitDone => {}
422 BeginLoad::Failed(m) => panic!("unexpected Failed: {m}"),
423 }
424 }
425}