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);
131 self.cv.notify_all();
132 return BeginLoad::WaitDone;
133 }
134 Some(SlotState::Failed { message, .. }) => {
135 return BeginLoad::Failed(message.clone());
136 }
137 Some(SlotState::Loading { waiters }) => {
138 *waiters += 1;
139 guard = self.cv.wait(guard).unwrap_or_else(|e| e.into_inner());
140 if !matches!(guard.get(key), Some(SlotState::Loading { .. })) {
143 if let Some(SlotState::Failed { message, .. }) = guard.get(key) {
144 return BeginLoad::Failed(message.clone());
145 }
146 return BeginLoad::WaitDone;
147 }
148 }
149 None => {
150 guard.insert(key.clone(), SlotState::Loading { waiters: 0 });
151 return BeginLoad::Leader;
152 }
153 }
154 }
155 }
156
157 pub fn begin_or_wait_guard(
162 &self,
163 key: LoadKey,
164 ) -> std::result::Result<Option<LeaderGuard<'_, T>>, String> {
165 match self.begin_or_wait(&key) {
166 BeginLoad::Leader => Ok(Some(LeaderGuard {
167 flight: self,
168 key,
169 finished: false,
170 })),
171 BeginLoad::WaitDone => Ok(None),
172 BeginLoad::Failed(m) => Err(m),
173 }
174 }
175}
176
177#[derive(Debug)]
179pub enum BeginLoad {
180 Leader,
182 WaitDone,
184 Failed(String),
186}
187
188pub struct LeaderGuard<'a, T> {
193 flight: &'a Singleflight<T>,
194 key: LoadKey,
195 finished: bool,
196}
197
198impl<'a, T> LeaderGuard<'a, T> {
199 pub fn key(&self) -> &LoadKey {
200 &self.key
201 }
202
203 pub fn success(mut self) {
204 self.flight.finish_load_published(&self.key);
205 self.finished = true;
206 }
207
208 pub fn fail(mut self, message: impl Into<String>) {
209 self.flight.finish_load_failed(&self.key, message.into());
210 self.finished = true;
211 }
212}
213
214impl<T> Drop for LeaderGuard<'_, T> {
215 fn drop(&mut self) {
216 if !self.finished {
217 self.flight.finish_load_failed(
218 &self.key,
219 "loader panicked or abandoned before publish".into(),
220 );
221 }
222 }
223}
224
225impl<T> Default for Singleflight<T> {
226 fn default() -> Self {
227 Self::new(Duration::from_secs(2))
228 }
229}
230
231impl<T: Send + Sync + 'static> Singleflight<T> {
232 pub fn get_or_load<F>(&self, key: LoadKey, loader: F) -> Result<Arc<T>>
237 where
238 F: FnOnce() -> Result<T>,
239 {
240 let mut leader = false;
242 let early: Option<Result<Arc<T>>> = {
243 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
244 loop {
245 if let Some(SlotState::Failed { at, .. }) = guard.get(&key) {
247 if at.elapsed() > self.fail_ttl {
248 guard.remove(&key);
249 }
250 }
251
252 match guard.get_mut(&key) {
253 Some(SlotState::Ready(v)) => {
254 break Some(Ok(Arc::clone(v)));
255 }
256 Some(SlotState::Failed { message, .. }) => {
257 break Some(Err(ProviderError::ModelLoad {
258 model: key.id.clone(),
259 reason: message.clone(),
260 }
261 .into()));
262 }
263 Some(SlotState::Loading { waiters }) => {
264 *waiters += 1;
265 guard = self.cv.wait(guard).unwrap_or_else(|e| e.into_inner());
266 }
268 None => {
269 guard.insert(key.clone(), SlotState::Loading { waiters: 0 });
270 leader = true;
271 break None;
272 }
273 }
274 }
275 };
276
277 if let Some(r) = early {
278 return r;
279 }
280 debug_assert!(leader);
281
282 let result = match catch_unwind(AssertUnwindSafe(loader)) {
284 Ok(r) => r,
285 Err(_) => Err(ProviderError::ModelLoad {
286 model: key.id.clone(),
287 reason: "loader panicked".into(),
288 }
289 .into()),
290 };
291 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
292 match result {
293 Ok(value) => {
294 let arc = Arc::new(value);
295 guard.insert(key, SlotState::Ready(Arc::clone(&arc)));
296 self.cv.notify_all();
297 Ok(arc)
298 }
299 Err(e) => {
300 let message = e.to_string();
301 guard.insert(
302 key,
303 SlotState::Failed {
304 message,
305 at: Instant::now(),
306 },
307 );
308 self.cv.notify_all();
309 Err(e)
310 }
311 }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use std::sync::atomic::{AtomicUsize, Ordering};
319 use std::thread;
320
321 #[test]
322 fn single_loader_for_many_waiters() {
323 let sf = Arc::new(Singleflight::<u32>::default());
324 let loads = Arc::new(AtomicUsize::new(0));
325 let key = LoadKey::stt("m1", "/tmp/m1");
326 let mut handles = vec![];
327 for _ in 0..16 {
328 let sf = Arc::clone(&sf);
329 let loads = Arc::clone(&loads);
330 let key = key.clone();
331 handles.push(thread::spawn(move || {
332 sf.get_or_load(key, || {
333 loads.fetch_add(1, Ordering::SeqCst);
334 thread::sleep(Duration::from_millis(30));
335 Ok(42)
336 })
337 .unwrap()
338 }));
339 }
340 for h in handles {
341 assert_eq!(*h.join().unwrap(), 42);
342 }
343 assert_eq!(loads.load(Ordering::SeqCst), 1);
344 }
345
346 #[test]
347 fn failure_delivered_to_waiters() {
348 let sf = Arc::new(Singleflight::<u32>::new(Duration::from_secs(10)));
349 let key = LoadKey::stt("bad", "/tmp/bad");
350 let sf2 = Arc::clone(&sf);
351 let key2 = key.clone();
352 let leader = thread::spawn(move || {
353 sf2.get_or_load(key2, || {
354 thread::sleep(Duration::from_millis(20));
355 Err(ProviderError::ModelLoad {
356 model: "bad".into(),
357 reason: "boom".into(),
358 }
359 .into())
360 })
361 });
362 thread::sleep(Duration::from_millis(5));
363 let waiter = sf.get_or_load(key, || Ok(1));
364 assert!(leader.join().unwrap().is_err());
365 assert!(waiter.is_err());
366 }
367
368 #[test]
369 fn panic_does_not_stick_loading() {
370 let sf = Singleflight::<u32>::new(Duration::from_millis(50));
371 let key = LoadKey::stt("panic", "/tmp/p");
372 let err = sf.get_or_load(key.clone(), || panic!("boom"));
373 assert!(err.is_err());
374 thread::sleep(Duration::from_millis(60));
376 let v = sf.get_or_load(key, || Ok(7)).unwrap();
377 assert_eq!(*v, 7);
378 }
379
380 #[test]
381 fn leader_guard_drop_unblocks_waiters() {
382 let sf = Arc::new(Singleflight::<u32>::new(Duration::from_millis(500)));
384 let key = LoadKey::stt("abandon", "/tmp/a");
385 let held = Arc::new(std::sync::Barrier::new(2));
386 let sf2 = Arc::clone(&sf);
387 let key2 = key.clone();
388 let held2 = Arc::clone(&held);
389 let leader = thread::spawn(move || {
390 let g = sf2.begin_or_wait_guard(key2).unwrap().expect("leader");
391 held2.wait();
394 drop(g);
396 });
397 held.wait();
398 let waiter = thread::spawn({
399 let sf = Arc::clone(&sf);
400 let key = key.clone();
401 move || sf.begin_or_wait(&key)
402 });
403 thread::sleep(Duration::from_millis(30));
405 leader.join().unwrap();
406 match waiter.join().unwrap() {
407 BeginLoad::Failed(m) => assert!(m.contains("abandon") || m.contains("panic")),
408 other => panic!("expected Failed after abandon, got {other:?}"),
409 }
410 thread::sleep(Duration::from_millis(520));
412 let g = sf
413 .begin_or_wait_guard(key.clone())
414 .unwrap()
415 .expect("leader2");
416 g.success();
417 match sf.begin_or_wait(&key) {
420 BeginLoad::Leader | BeginLoad::WaitDone => {}
421 BeginLoad::Failed(m) => panic!("unexpected Failed: {m}"),
422 }
423 }
424}