1#![allow(dead_code)]
2use std::any::TypeId;
7use std::cell::RefCell;
8use std::collections::{HashMap, VecDeque};
9use std::fmt::Debug;
10use std::mem::size_of;
11use std::num::NonZeroUsize;
12use std::sync::{Arc, Condvar, Mutex, MutexGuard};
13
14use lru::LruCache;
15
16use super::cache_owner::{FrozenCacheOwner, FrozenCacheOwnerKind};
17use super::{
18 CacheInFlightBehavior, CacheOwnerFailure, CacheStats, PreparationKeySummary, PrepareError,
19 RuntimeCacheError, RuntimeStateError, SpecializationRequirements,
20};
21
22const PREPARED_CACHE_LOCK: &str = "prepared-cache.state";
23const DEFAULT_MAX_PREPARED_ENTRIES: usize = 128;
24const DEFAULT_MAX_RETAINED_BYTES: usize = 64 * 1024 * 1024;
25const DEFAULT_MAX_IN_FLIGHT_ENTRIES: usize = 16;
26const DEFAULT_MAX_QUEUED_DISTINCT_KEYS: usize = 64;
27
28thread_local! {
29 static PREPARATION_STACK: RefCell<Vec<StackFrame>> = const { RefCell::new(Vec::new()) };
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct PreparedPlanCacheLimits {
43 pub max_entries: NonZeroUsize,
45 pub max_retained_bytes: NonZeroUsize,
47 pub max_in_flight_entries: NonZeroUsize,
49 pub max_queued_distinct_keys: NonZeroUsize,
51}
52
53impl Default for PreparedPlanCacheLimits {
54 fn default() -> Self {
55 Self {
56 max_entries: NonZeroUsize::new(DEFAULT_MAX_PREPARED_ENTRIES)
57 .expect("default entry limit is nonzero"),
58 max_retained_bytes: NonZeroUsize::new(DEFAULT_MAX_RETAINED_BYTES)
59 .expect("default byte limit is nonzero"),
60 max_in_flight_entries: NonZeroUsize::new(DEFAULT_MAX_IN_FLIGHT_ENTRIES)
61 .expect("default in-flight limit is nonzero"),
62 max_queued_distinct_keys: NonZeroUsize::new(DEFAULT_MAX_QUEUED_DISTINCT_KEYS)
63 .expect("default queue limit is nonzero"),
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
78pub struct PreparedPlanCacheStats {
79 pub entries: usize,
81 pub retained_bytes: usize,
83 pub hits: u64,
85 pub misses: u64,
87 pub waits: u64,
89 pub negative_hits: u64,
91 pub preparations: u64,
93 pub evictions: u64,
95 pub redirects: u64,
97 pub in_flight: usize,
99 pub peak_in_flight: usize,
101 pub queued_distinct_keys: usize,
103 pub capacity_refusals: u64,
105}
106
107#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
117pub struct RuntimeCacheStats {
118 pub prepared_plans: PreparedPlanCacheStats,
120 pub engines: CacheStats,
122 pub extensions: CacheStats,
124}
125
126pub(crate) trait PreparedCacheKey: Clone + Debug + Send + Sync + 'static {
127 type Shared: Debug + Send + Sync + 'static;
128
129 fn compact_digest(&self) -> u128;
130 fn exact_eq(&self, other: &Self) -> bool;
131 fn retained_bytes(&self) -> Option<usize>;
132 fn summary(&self) -> PreparationKeySummary;
133 fn shared_retention(&self) -> Option<SharedRetention<Self::Shared>>;
134}
135
136pub(crate) trait PreparedValue: Debug + Send + Sync + 'static {
137 fn retained_bytes(&self) -> Option<usize>;
138}
139
140#[derive(Debug)]
141pub(crate) struct SharedRetention<S: Debug + Send + Sync + 'static> {
142 pub(crate) value: Arc<S>,
143 pub(crate) retained_bytes: Option<usize>,
144}
145
146impl<S: Debug + Send + Sync + 'static> Clone for SharedRetention<S> {
147 fn clone(&self) -> Self {
148 Self {
149 value: Arc::clone(&self.value),
150 retained_bytes: self.retained_bytes,
151 }
152 }
153}
154
155#[derive(Debug)]
156pub(crate) enum CacheProduced<K: PreparedCacheKey, V: PreparedValue> {
157 Ready {
158 value: Arc<V>,
159 shared: Option<SharedRetention<K::Shared>>,
160 },
161 Redirect {
162 requirements: SpecializationRequirements,
163 shared: Option<SharedRetention<K::Shared>>,
164 },
165 FailedDeterministic {
166 error: Arc<PrepareError>,
167 shared: Option<SharedRetention<K::Shared>>,
168 },
169 FailedTransient(Arc<PrepareError>),
170}
171
172#[derive(Debug)]
173pub(crate) enum CacheLookup<K: PreparedCacheKey, V: PreparedValue> {
174 Ready(Arc<V>),
175 Redirect {
176 requirements: SpecializationRequirements,
177 shared: Option<Arc<K::Shared>>,
178 },
179 FailedDeterministic(Arc<PrepareError>),
180 FailedTransient(Arc<PrepareError>),
181}
182
183#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
184struct AttemptId(u128);
185
186#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
187struct EntryId(u128);
188
189#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
190struct QueueId(u128);
191
192#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
193struct SharedRetentionId(NonZeroUsize);
194
195impl SharedRetentionId {
196 fn of<S: Debug + Send + Sync + 'static>(value: &Arc<S>) -> Self {
197 Self(NonZeroUsize::new(Arc::as_ptr(value) as usize).expect("Arc data pointer is non-null"))
198 }
199}
200
201#[derive(Debug)]
202struct CacheGenerationToken;
203
204#[derive(Debug)]
205struct CacheRevisionToken;
206
207#[derive(Debug, thiserror::Error)]
208enum CacheInternalError {
209 #[error("prepared-cache retained-byte accounting overflow")]
210 AccountingOverflow,
211 #[error("prepared-cache shared retention pointer collision")]
212 SharedPointerCollision,
213}
214
215#[derive(Debug)]
216enum CacheTerminal<K: PreparedCacheKey, V: PreparedValue> {
217 Ready(Arc<V>),
218 Redirect {
219 requirements: SpecializationRequirements,
220 shared: Option<Arc<K::Shared>>,
221 },
222 FailedDeterministic(Arc<PrepareError>),
223 FailedTransient(Arc<PrepareError>),
224}
225
226impl<K: PreparedCacheKey, V: PreparedValue> Clone for CacheTerminal<K, V> {
227 fn clone(&self) -> Self {
228 match self {
229 Self::Ready(value) => Self::Ready(Arc::clone(value)),
230 Self::Redirect {
231 requirements,
232 shared,
233 } => Self::Redirect {
234 requirements: requirements.clone(),
235 shared: shared.as_ref().map(Arc::clone),
236 },
237 Self::FailedDeterministic(error) => Self::FailedDeterministic(Arc::clone(error)),
238 Self::FailedTransient(error) => Self::FailedTransient(Arc::clone(error)),
239 }
240 }
241}
242
243#[derive(Clone, Copy, Debug)]
244enum RetainedTerminalKind {
245 Ready,
246 Redirect,
247 FailedDeterministic,
248 FailedTransient,
249}
250
251impl RetainedTerminalKind {
252 fn from_terminal<K: PreparedCacheKey, V: PreparedValue>(
253 terminal: &CacheTerminal<K, V>,
254 ) -> Self {
255 match terminal {
256 CacheTerminal::Ready(_) => Self::Ready,
257 CacheTerminal::Redirect { .. } => Self::Redirect,
258 CacheTerminal::FailedDeterministic(_) => Self::FailedDeterministic,
259 CacheTerminal::FailedTransient(_) => Self::FailedTransient,
260 }
261 }
262}
263
264#[derive(Debug)]
265enum PreparedEntryState<K: PreparedCacheKey, V: PreparedValue> {
266 Preparing {
267 attempt_id: AttemptId,
268 waiting_readers: usize,
269 },
270 Retained(CacheTerminal<K, V>),
271 Ephemeral {
272 terminal: CacheTerminal<K, V>,
273 remaining_readers: usize,
274 },
275}
276
277#[derive(Debug)]
278struct EntryRecord<K: PreparedCacheKey, V: PreparedValue> {
279 id: EntryId,
280 digest: u128,
281 key: Arc<K>,
282 state: PreparedEntryState<K, V>,
283 generation: Arc<CacheGenerationToken>,
284 retained_bytes: usize,
285 shared_id: Option<SharedRetentionId>,
286}
287
288#[derive(Debug)]
289struct ActiveAttempt<K: PreparedCacheKey> {
290 id: AttemptId,
291 entry_id: Option<EntryId>,
292 digest: u128,
293 key: Arc<K>,
294 generation: Arc<CacheGenerationToken>,
295 key_bytes: usize,
296 signature_bytes: usize,
297 attempt_metadata_bytes: usize,
298 visible_entry_bytes: usize,
299}
300
301impl<K: PreparedCacheKey> ActiveAttempt<K> {
302 fn retained_bytes_without_visible_entry(&self) -> usize {
303 self.key_bytes
304 .saturating_add(self.signature_bytes)
305 .saturating_add(self.attempt_metadata_bytes)
306 }
307}
308
309#[derive(Debug)]
310struct QueueRecord<K: PreparedCacheKey> {
311 id: QueueId,
312 digest: u128,
313 key: Arc<K>,
314 generation: Arc<CacheGenerationToken>,
315 key_bytes: usize,
316 signature_bytes: usize,
317 metadata_bytes: usize,
318 tickets: usize,
319}
320
321impl<K: PreparedCacheKey> QueueRecord<K> {
322 fn retained_bytes(&self) -> usize {
323 self.key_bytes
324 .saturating_add(self.signature_bytes)
325 .saturating_add(self.metadata_bytes)
326 }
327}
328
329#[derive(Debug)]
330struct SharedCharge<S: Debug + Send + Sync + 'static> {
331 value: Arc<S>,
332 retained_bytes: usize,
333 references: usize,
334}
335
336#[derive(Debug)]
337struct CacheState<K: PreparedCacheKey, V: PreparedValue> {
338 entry_buckets: HashMap<u128, Vec<EntryId>>,
339 entries: HashMap<EntryId, EntryRecord<K, V>>,
340 lru: LruCache<EntryId, ()>,
341 queue_buckets: HashMap<u128, Vec<QueueId>>,
342 queue_order: VecDeque<QueueId>,
343 queue_records: HashMap<QueueId, QueueRecord<K>>,
344 attempts: HashMap<AttemptId, ActiveAttempt<K>>,
345 shared: HashMap<SharedRetentionId, SharedCharge<K::Shared>>,
346 generation: Arc<CacheGenerationToken>,
347 revision: Arc<CacheRevisionToken>,
348 next_attempt: u128,
349 next_entry: u128,
350 next_queue: u128,
351 limits: PreparedPlanCacheLimits,
352 retained_bytes: usize,
353 in_flight: usize,
354 stats: PreparedPlanCacheStats,
355}
356
357impl<K: PreparedCacheKey, V: PreparedValue> CacheState<K, V> {
358 fn new(limits: PreparedPlanCacheLimits) -> Self {
359 Self {
360 entry_buckets: HashMap::new(),
361 entries: HashMap::new(),
362 lru: LruCache::unbounded(),
363 queue_buckets: HashMap::new(),
364 queue_order: VecDeque::new(),
365 queue_records: HashMap::new(),
366 attempts: HashMap::new(),
367 shared: HashMap::new(),
368 generation: Arc::new(CacheGenerationToken),
369 revision: Arc::new(CacheRevisionToken),
370 next_attempt: 0,
371 next_entry: 0,
372 next_queue: 0,
373 limits,
374 retained_bytes: 0,
375 in_flight: 0,
376 stats: PreparedPlanCacheStats::default(),
377 }
378 }
379
380 fn snapshot_stats(&self) -> PreparedPlanCacheStats {
381 let mut stats = self.stats;
382 stats.entries = self.lru.len();
383 stats.retained_bytes = self.retained_bytes;
384 stats.in_flight = self.in_flight;
385 stats.queued_distinct_keys = self.queue_records.len();
386 stats
387 }
388
389 fn bump_revision(&mut self) {
390 self.revision = Arc::new(CacheRevisionToken);
391 }
392}
393
394pub(crate) struct PreparedPlanCache<K: PreparedCacheKey, V: PreparedValue> {
395 state: Mutex<CacheState<K, V>>,
396 changed: Condvar,
397}
398
399impl<K: PreparedCacheKey, V: PreparedValue> PreparedPlanCache<K, V> {
400 pub(crate) fn new(limits: PreparedPlanCacheLimits) -> Self {
401 Self {
402 state: Mutex::new(CacheState::new(limits)),
403 changed: Condvar::new(),
404 }
405 }
406
407 pub(crate) fn get_or_prepare(
408 &self,
409 key: K,
410 behavior: CacheInFlightBehavior,
411 signature_bytes: usize,
412 producer: impl FnOnce() -> CacheProduced<K, V>,
413 ) -> Result<CacheLookup<K, V>, Arc<PrepareError>> {
414 self.get_or_prepare_inner(key, behavior, signature_bytes, producer, || {}, || {})
415 }
416
417 #[cfg(test)]
418 pub(crate) fn get_or_prepare_with_entry_wait_hooks_for_test(
419 &self,
420 key: K,
421 behavior: CacheInFlightBehavior,
422 signature_bytes: usize,
423 producer: impl FnOnce() -> CacheProduced<K, V>,
424 before_entry_wait: impl FnMut(),
425 before_condvar_wait: impl FnMut(),
426 ) -> Result<CacheLookup<K, V>, Arc<PrepareError>> {
427 self.get_or_prepare_inner(
428 key,
429 behavior,
430 signature_bytes,
431 producer,
432 before_entry_wait,
433 before_condvar_wait,
434 )
435 }
436
437 fn get_or_prepare_inner(
438 &self,
439 key: K,
440 behavior: CacheInFlightBehavior,
441 signature_bytes: usize,
442 producer: impl FnOnce() -> CacheProduced<K, V>,
443 mut before_entry_wait: impl FnMut(),
444 mut before_condvar_wait: impl FnMut(),
445 ) -> Result<CacheLookup<K, V>, Arc<PrepareError>> {
446 let summary = key.summary();
447 check_preparation_stack::<K>(summary)?;
448 let digest = key.compact_digest();
449 let mut producer = Some(producer);
450 loop {
451 match self.probe(&key, digest)? {
452 ProbeResult::Entry(entry_id) => match self.handle_entry(entry_id)? {
453 EntryAction::Return(lookup) => return Ok(lookup),
454 EntryAction::Wait(waiter) => {
455 before_entry_wait();
456 match waiter.wait_once(&mut before_condvar_wait)? {
457 WaitOutcome::RetryProbe => continue,
458 WaitOutcome::Return(lookup) => return Ok(lookup),
459 }
460 }
461 EntryAction::ReturnRetry => continue,
462 },
463 ProbeResult::Queue(queue_id) => {
464 let ticket = self.add_queue_ticket(queue_id)?;
465 match ticket.wait_for_turn()? {
466 QueueOutcome::RetryProbe => continue,
467 QueueOutcome::Produce(guard) => {
468 return self.run_producer(
469 summary,
470 guard,
471 producer
472 .take()
473 .expect("producer is consumed by only one cache miss"),
474 );
475 }
476 }
477 }
478 ProbeResult::NoMatch {
479 revision,
480 generation,
481 } => {
482 match self.reserve_miss(
483 key.clone(),
484 digest,
485 behavior,
486 signature_bytes,
487 revision,
488 generation,
489 )? {
490 MissReservation::Produce(guard) => {
491 return self.run_producer(
492 summary,
493 guard,
494 producer
495 .take()
496 .expect("producer is consumed by only one cache miss"),
497 );
498 }
499 MissReservation::Wait(ticket) => match ticket.wait_for_turn()? {
500 QueueOutcome::RetryProbe => continue,
501 QueueOutcome::Produce(guard) => {
502 return self.run_producer(
503 summary,
504 guard,
505 producer
506 .take()
507 .expect("producer is consumed by only one cache miss"),
508 );
509 }
510 },
511 }
512 }
513 }
514 }
515 }
516
517 pub(crate) fn limits(&self) -> Result<PreparedPlanCacheLimits, RuntimeStateError> {
518 self.state
519 .lock()
520 .map(|state| state.limits)
521 .map_err(|_| poisoned_state_error())
522 }
523
524 pub(crate) fn set_limits(
525 &self,
526 limits: PreparedPlanCacheLimits,
527 ) -> Result<(), RuntimeStateError> {
528 let mut state = self.state.lock().map_err(|_| poisoned_state_error())?;
529 state.limits = limits;
530 evict_to_limits(&mut state);
531 state.bump_revision();
532 drop(state);
533 self.changed.notify_all();
534 Ok(())
535 }
536
537 pub(crate) fn stats(&self) -> Result<PreparedPlanCacheStats, RuntimeStateError> {
538 self.state
539 .lock()
540 .map(|state| state.snapshot_stats())
541 .map_err(|_| poisoned_state_error())
542 }
543
544 pub(crate) fn clear(&self) -> Result<(), RuntimeStateError> {
545 let mut state = self.state.lock().map_err(|_| poisoned_state_error())?;
546 clear_state(&mut state);
547 drop(state);
548 self.changed.notify_all();
549 Ok(())
550 }
551
552 #[cfg(test)]
553 pub(crate) fn ready_charge_breakdown_for_test(
554 &self,
555 key: &K,
556 value: &V,
557 has_shared: bool,
558 ) -> Option<RetainedChargeBreakdown> {
559 let key_payload = key.retained_bytes()?;
560 let value_payload = value.retained_bytes()?;
561 let entry_record = size_of::<EntryRecord<K, V>>();
562 let compact_bucket_record = size_of::<EntryId>();
563 let lru_record = size_of::<EntryId>().checked_add(size_of::<()>())?;
564 let shared_record = if has_shared {
565 size_of::<SharedRetentionId>().checked_add(size_of::<SharedCharge<K::Shared>>())?
566 } else {
567 0
568 };
569 let total = checked_sum([
570 key_payload,
571 value_payload,
572 entry_record,
573 compact_bucket_record,
574 lru_record,
575 shared_record,
576 ])?;
577 Some(RetainedChargeBreakdown {
578 key_payload,
579 value_payload,
580 entry_record,
581 compact_bucket_record,
582 lru_record,
583 shared_record,
584 total,
585 })
586 }
587
588 fn run_producer(
589 &self,
590 summary: PreparationKeySummary,
591 guard: ProducerGuard<'_, K, V>,
592 producer: impl FnOnce() -> CacheProduced<K, V>,
593 ) -> Result<CacheLookup<K, V>, Arc<PrepareError>> {
594 let stack_guard = StackGuard::push::<K>(summary);
595 let produced = producer();
596 drop(stack_guard);
597 guard.publish(produced)
598 }
599
600 fn probe(&self, key: &K, digest: u128) -> Result<ProbeResult, Arc<PrepareError>> {
601 loop {
602 let (revision, generation, entry_ids, queue_ids) = {
603 let state = self.lock_state()?;
604 (
605 Arc::clone(&state.revision),
606 Arc::clone(&state.generation),
607 state
608 .entry_buckets
609 .get(&digest)
610 .cloned()
611 .unwrap_or_default(),
612 state
613 .queue_buckets
614 .get(&digest)
615 .cloned()
616 .unwrap_or_default(),
617 )
618 };
619
620 for entry_id in entry_ids {
621 let Some(candidate_key) =
622 self.entry_key_if_generation_current(entry_id, digest, &revision, &generation)?
623 else {
624 continue;
625 };
626 if key.exact_eq(candidate_key.as_ref()) {
627 let state = self.lock_state()?;
628 if !Arc::ptr_eq(&revision, &state.revision)
629 || !Arc::ptr_eq(&generation, &state.generation)
630 {
631 continue;
632 }
633 if state
634 .entries
635 .get(&entry_id)
636 .is_some_and(|entry| entry.digest == digest)
637 {
638 return Ok(ProbeResult::Entry(entry_id));
639 }
640 continue;
641 }
642 }
643
644 for queue_id in queue_ids {
645 let Some(candidate_key) =
646 self.queue_key_if_generation_current(queue_id, digest, &revision, &generation)?
647 else {
648 continue;
649 };
650 if key.exact_eq(candidate_key.as_ref()) {
651 let state = self.lock_state()?;
652 if !Arc::ptr_eq(&revision, &state.revision)
653 || !Arc::ptr_eq(&generation, &state.generation)
654 {
655 continue;
656 }
657 if state
658 .queue_records
659 .get(&queue_id)
660 .is_some_and(|record| record.digest == digest)
661 {
662 return Ok(ProbeResult::Queue(queue_id));
663 }
664 continue;
665 }
666 }
667
668 let state = self.lock_state()?;
669 if !Arc::ptr_eq(&revision, &state.revision)
670 || !Arc::ptr_eq(&generation, &state.generation)
671 {
672 continue;
673 }
674 return Ok(ProbeResult::NoMatch {
675 revision: Arc::clone(&state.revision),
676 generation: Arc::clone(&state.generation),
677 });
678 }
679 }
680
681 fn entry_key_if_generation_current(
682 &self,
683 entry_id: EntryId,
684 digest: u128,
685 revision: &Arc<CacheRevisionToken>,
686 generation: &Arc<CacheGenerationToken>,
687 ) -> Result<Option<Arc<K>>, Arc<PrepareError>> {
688 let state = self.lock_state()?;
689 if !Arc::ptr_eq(revision, &state.revision) || !Arc::ptr_eq(generation, &state.generation) {
690 return Ok(None);
691 }
692 Ok(state
693 .entries
694 .get(&entry_id)
695 .filter(|entry| entry.digest == digest)
696 .map(|entry| Arc::clone(&entry.key)))
697 }
698
699 fn queue_key_if_generation_current(
700 &self,
701 queue_id: QueueId,
702 digest: u128,
703 revision: &Arc<CacheRevisionToken>,
704 generation: &Arc<CacheGenerationToken>,
705 ) -> Result<Option<Arc<K>>, Arc<PrepareError>> {
706 let state = self.lock_state()?;
707 if !Arc::ptr_eq(revision, &state.revision) || !Arc::ptr_eq(generation, &state.generation) {
708 return Ok(None);
709 }
710 Ok(state
711 .queue_records
712 .get(&queue_id)
713 .filter(|record| record.digest == digest)
714 .map(|record| Arc::clone(&record.key)))
715 }
716
717 fn handle_entry(&self, entry_id: EntryId) -> Result<EntryAction<'_, K, V>, Arc<PrepareError>> {
718 let mut state = self.lock_state()?;
719 if let Some((lookup, terminal_kind)) = state.entries.get(&entry_id).and_then(|entry| {
720 if let PreparedEntryState::Retained(terminal) = &entry.state {
721 let lookup = lookup_from_terminal(terminal, entry.shared_id, &state.shared);
722 Some((lookup, RetainedTerminalKind::from_terminal(terminal)))
723 } else {
724 None
725 }
726 }) {
727 match terminal_kind {
728 RetainedTerminalKind::FailedDeterministic => {
729 state.stats.negative_hits = state.stats.negative_hits.saturating_add(1);
730 }
731 RetainedTerminalKind::Redirect => {
732 state.stats.hits = state.stats.hits.saturating_add(1);
733 state.stats.redirects = state.stats.redirects.saturating_add(1);
734 }
735 RetainedTerminalKind::Ready => {
736 state.stats.hits = state.stats.hits.saturating_add(1);
737 }
738 RetainedTerminalKind::FailedTransient => {}
739 }
740 let _ = state.lru.get(&entry_id);
741 return Ok(EntryAction::Return(lookup));
742 }
743
744 let Some(entry) = state.entries.get_mut(&entry_id) else {
745 return Ok(EntryAction::ReturnRetry);
746 };
747 match &mut entry.state {
748 PreparedEntryState::Retained(_) => Ok(EntryAction::ReturnRetry),
749 PreparedEntryState::Preparing {
750 attempt_id,
751 waiting_readers,
752 } => {
753 let attempt_id = *attempt_id;
754 *waiting_readers = waiting_readers
755 .checked_add(1)
756 .ok_or_else(accounting_prepare_error)?;
757 let _ = entry;
758 state.stats.waits = state.stats.waits.saturating_add(1);
759 Ok(EntryAction::Wait(EntryWaitGuard {
760 cache: self,
761 entry_id,
762 attempt_id,
763 armed: true,
764 }))
765 }
766 PreparedEntryState::Ephemeral { .. } => Ok(EntryAction::ReturnRetry),
767 }
768 }
769
770 fn add_queue_ticket(
771 &self,
772 queue_id: QueueId,
773 ) -> Result<QueueTicketGuard<'_, K, V>, Arc<PrepareError>> {
774 let mut state = self.lock_state()?;
775 let Some(record) = state.queue_records.get_mut(&queue_id) else {
776 return Ok(QueueTicketGuard {
777 cache: self,
778 queue_id,
779 armed: false,
780 });
781 };
782 record.tickets = record
783 .tickets
784 .checked_add(1)
785 .ok_or_else(accounting_prepare_error)?;
786 state.stats.waits = state.stats.waits.saturating_add(1);
787 Ok(QueueTicketGuard {
788 cache: self,
789 queue_id,
790 armed: true,
791 })
792 }
793
794 fn reserve_miss(
795 &self,
796 key: K,
797 digest: u128,
798 behavior: CacheInFlightBehavior,
799 signature_bytes: usize,
800 revision: Arc<CacheRevisionToken>,
801 generation: Arc<CacheGenerationToken>,
802 ) -> Result<MissReservation<'_, K, V>, Arc<PrepareError>> {
803 let mut state = self.lock_state()?;
804 if !Arc::ptr_eq(&revision, &state.revision) || !Arc::ptr_eq(&generation, &state.generation)
805 {
806 return Ok(MissReservation::Wait(QueueTicketGuard {
807 cache: self,
808 queue_id: QueueId(0),
809 armed: false,
810 }));
811 }
812 if state.in_flight < state.limits.max_in_flight_entries.get()
813 && state.queue_order.is_empty()
814 {
815 let guard = start_attempt(
816 &mut state,
817 self,
818 Arc::new(key),
819 digest,
820 signature_bytes,
821 None,
822 )?;
823 return Ok(MissReservation::Produce(guard));
824 }
825 if behavior == CacheInFlightBehavior::Refuse {
826 state.stats.capacity_refusals = state.stats.capacity_refusals.saturating_add(1);
827 return Err(capacity_error(&state));
828 }
829 if state.queue_records.len() >= state.limits.max_queued_distinct_keys.get() {
830 state.stats.capacity_refusals = state.stats.capacity_refusals.saturating_add(1);
831 return Err(capacity_error(&state));
832 }
833 let queue_id = allocate_queue_id(&mut state);
834 let key = Arc::new(key);
835 let key_bytes = key.retained_bytes().ok_or_else(accounting_prepare_error)?;
836 let metadata_bytes = checked_sum([
837 size_of::<QueueRecord<K>>(),
838 size_of::<QueueId>(),
839 size_of::<QueueId>(),
840 ])
841 .ok_or_else(accounting_prepare_error)?;
842 let retained_bytes = checked_sum([key_bytes, signature_bytes, metadata_bytes])
843 .ok_or_else(accounting_prepare_error)?;
844 let record = QueueRecord {
845 id: queue_id,
846 digest,
847 key,
848 generation: Arc::clone(&state.generation),
849 key_bytes,
850 signature_bytes,
851 metadata_bytes,
852 tickets: 1,
853 };
854 state
855 .queue_buckets
856 .entry(digest)
857 .or_default()
858 .push(queue_id);
859 state.queue_order.push_back(queue_id);
860 state.queue_records.insert(queue_id, record);
861 state.retained_bytes = state
862 .retained_bytes
863 .checked_add(retained_bytes)
864 .ok_or_else(accounting_prepare_error)?;
865 state.stats.waits = state.stats.waits.saturating_add(1);
866 state.bump_revision();
867 self.changed.notify_all();
868 Ok(MissReservation::Wait(QueueTicketGuard {
869 cache: self,
870 queue_id,
871 armed: true,
872 }))
873 }
874
875 fn lock_state(&self) -> Result<MutexGuard<'_, CacheState<K, V>>, Arc<PrepareError>> {
876 self.state.lock().map_err(|_| {
877 Arc::new(PrepareError::CacheState {
878 source: poisoned_state_error(),
879 })
880 })
881 }
882
883 fn recover_state(&self) -> MutexGuard<'_, CacheState<K, V>> {
884 match self.state.lock() {
885 Ok(state) => state,
886 Err(error) => {
887 error.into_inner()
891 }
892 }
893 }
894
895 #[cfg(test)]
896 pub(crate) fn poison_state_for_test(&self) {
897 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
898 let _state = match self.state.lock() {
899 Ok(state) => state,
900 Err(_) => panic!("cache state should not already be poisoned in test setup"),
901 };
902 panic!("poison prepared cache state for test");
903 }));
904 }
905}
906
907#[derive(Debug)]
908enum ProbeResult {
909 Entry(EntryId),
910 Queue(QueueId),
911 NoMatch {
912 revision: Arc<CacheRevisionToken>,
913 generation: Arc<CacheGenerationToken>,
914 },
915}
916
917enum EntryAction<'a, K: PreparedCacheKey, V: PreparedValue> {
918 Return(CacheLookup<K, V>),
919 Wait(EntryWaitGuard<'a, K, V>),
920 ReturnRetry,
921}
922
923enum MissReservation<'a, K: PreparedCacheKey, V: PreparedValue> {
924 Produce(ProducerGuard<'a, K, V>),
925 Wait(QueueTicketGuard<'a, K, V>),
926}
927
928enum WaitOutcome<K: PreparedCacheKey, V: PreparedValue> {
929 RetryProbe,
930 Return(CacheLookup<K, V>),
931}
932
933enum QueueOutcome<'a, K: PreparedCacheKey, V: PreparedValue> {
934 RetryProbe,
935 Produce(ProducerGuard<'a, K, V>),
936}
937
938struct ProducerGuard<'a, K: PreparedCacheKey, V: PreparedValue> {
939 cache: &'a PreparedPlanCache<K, V>,
940 attempt_id: AttemptId,
941 entry_id: EntryId,
942 published: bool,
943}
944
945impl<K: PreparedCacheKey, V: PreparedValue> ProducerGuard<'_, K, V> {
946 fn publish(
947 mut self,
948 produced: CacheProduced<K, V>,
949 ) -> Result<CacheLookup<K, V>, Arc<PrepareError>> {
950 let mut state = self.cache.lock_state()?;
951 let Some(attempt) = state.attempts.remove(&self.attempt_id) else {
952 self.published = true;
953 return Ok(lookup_from_produced(produced));
954 };
955 debug_assert_eq!(attempt.id, self.attempt_id);
956 if let Some(active_entry_id) = attempt.entry_id {
957 debug_assert_eq!(active_entry_id, self.entry_id);
958 if let Some(entry) = state.entries.get(&active_entry_id) {
959 debug_assert_eq!(attempt.digest, entry.digest);
960 }
961 }
962 finish_attempt_accounting(&mut state, &attempt);
963 let Some(entry_id) = attempt.entry_id else {
964 state.bump_revision();
965 self.published = true;
966 drop(state);
967 self.cache.changed.notify_all();
968 return Ok(lookup_from_produced(produced));
969 };
970 let Some(entry) = state.entries.get(&entry_id) else {
971 state.bump_revision();
972 self.published = true;
973 drop(state);
974 self.cache.changed.notify_all();
975 return Ok(lookup_from_produced(produced));
976 };
977 debug_assert_eq!(entry.id, entry_id);
978 debug_assert!(Arc::ptr_eq(&entry.generation, &attempt.generation));
979 debug_assert_eq!(entry.retained_bytes, attempt.visible_entry_bytes);
980 let PreparedEntryState::Preparing {
981 attempt_id,
982 waiting_readers,
983 } = &entry.state
984 else {
985 state.bump_revision();
986 self.published = true;
987 drop(state);
988 self.cache.changed.notify_all();
989 return Ok(lookup_from_produced(produced));
990 };
991 if *attempt_id != self.attempt_id {
992 state.bump_revision();
993 self.published = true;
994 drop(state);
995 self.cache.changed.notify_all();
996 return Ok(lookup_from_produced(produced));
997 }
998 let waiting_readers = *waiting_readers;
999
1000 if !Arc::ptr_eq(&attempt.generation, &state.generation) {
1001 remove_entry(&mut state, entry_id, false);
1002 state.bump_revision();
1003 self.published = true;
1004 drop(state);
1005 self.cache.changed.notify_all();
1006 return Ok(lookup_from_produced(produced));
1007 }
1008
1009 let publication = build_publication(attempt.key.as_ref(), produced);
1010 let producer_lookup = publication.lookup();
1011 match publication.retention {
1012 PublicationRetention::Transient | PublicationRetention::Uncacheable => {
1013 publish_ephemeral_or_remove(
1014 &mut state,
1015 entry_id,
1016 publication.terminal,
1017 waiting_readers,
1018 );
1019 }
1020 PublicationRetention::Retain {
1021 entry_bytes,
1022 shared,
1023 } => {
1024 let shared_new_bytes = shared
1025 .as_ref()
1026 .map(|shared| shared_new_charge(&state, shared))
1027 .transpose()?
1028 .unwrap_or(0);
1029 let total_new_entry = entry_bytes
1030 .checked_add(shared_new_bytes)
1031 .ok_or_else(accounting_prepare_error)?;
1032 if total_new_entry > state.limits.max_retained_bytes.get() {
1033 publish_ephemeral_or_remove(
1034 &mut state,
1035 entry_id,
1036 publication.terminal,
1037 waiting_readers,
1038 );
1039 } else {
1040 let shared_id = if let Some(shared) = shared {
1041 Some(retain_shared(&mut state, shared)?)
1042 } else {
1043 None
1044 };
1045 retain_entry(
1046 &mut state,
1047 entry_id,
1048 publication.terminal,
1049 entry_bytes,
1050 shared_id,
1051 )?;
1052 evict_to_limits(&mut state);
1053 }
1054 }
1055 }
1056 state.bump_revision();
1057 self.published = true;
1058 drop(state);
1059 self.cache.changed.notify_all();
1060 Ok(producer_lookup)
1061 }
1062}
1063
1064impl<K: PreparedCacheKey, V: PreparedValue> Drop for ProducerGuard<'_, K, V> {
1065 fn drop(&mut self) {
1066 if self.published {
1067 return;
1068 }
1069 let mut state = self.cache.recover_state();
1070 if let Some(attempt) = state.attempts.remove(&self.attempt_id) {
1071 finish_attempt_accounting(&mut state, &attempt);
1072 if let Some(entry_id) = attempt.entry_id {
1073 remove_entry(&mut state, entry_id, false);
1074 }
1075 state.bump_revision();
1076 } else {
1077 remove_entry(&mut state, self.entry_id, false);
1078 state.bump_revision();
1079 }
1080 drop(state);
1081 self.cache.changed.notify_all();
1082 }
1083}
1084
1085struct EntryWaitGuard<'a, K: PreparedCacheKey, V: PreparedValue> {
1086 cache: &'a PreparedPlanCache<K, V>,
1087 entry_id: EntryId,
1088 attempt_id: AttemptId,
1089 armed: bool,
1090}
1091
1092impl<K: PreparedCacheKey, V: PreparedValue> EntryWaitGuard<'_, K, V> {
1093 fn wait_once(
1094 mut self,
1095 before_condvar_wait: &mut impl FnMut(),
1096 ) -> Result<WaitOutcome<K, V>, Arc<PrepareError>> {
1097 let mut state = self.cache.lock_state()?;
1098 while self.same_attempt_is_preparing(&state) {
1099 before_condvar_wait();
1100 state = match self.cache.changed.wait(state) {
1101 Ok(state) => state,
1102 Err(error) => {
1103 drop(error.into_inner());
1104 return Err(Arc::new(PrepareError::CacheState {
1105 source: poisoned_state_error(),
1106 }));
1107 }
1108 };
1109 }
1110 let outcome = self.complete_with_locked_state(&mut state);
1111 drop(state);
1112 if matches!(outcome, WaitOutcome::RetryProbe) {
1113 self.cache.changed.notify_all();
1114 }
1115 Ok(outcome)
1116 }
1117
1118 fn same_attempt_is_preparing(&self, state: &CacheState<K, V>) -> bool {
1119 matches!(
1120 state.entries.get(&self.entry_id),
1121 Some(EntryRecord {
1122 state: PreparedEntryState::Preparing { attempt_id, .. },
1123 ..
1124 }) if *attempt_id == self.attempt_id
1125 )
1126 }
1127
1128 fn complete_with_locked_state(&mut self, state: &mut CacheState<K, V>) -> WaitOutcome<K, V> {
1129 if !self.armed {
1130 return WaitOutcome::RetryProbe;
1131 }
1132 let Some(entry) = state.entries.get_mut(&self.entry_id) else {
1133 self.armed = false;
1134 return WaitOutcome::RetryProbe;
1135 };
1136 match &mut entry.state {
1137 PreparedEntryState::Preparing {
1138 attempt_id,
1139 waiting_readers,
1140 } if *attempt_id == self.attempt_id => {
1141 *waiting_readers = waiting_readers.saturating_sub(1);
1142 self.armed = false;
1143 WaitOutcome::RetryProbe
1144 }
1145 PreparedEntryState::Retained(_) => {
1146 self.armed = false;
1147 WaitOutcome::RetryProbe
1148 }
1149 PreparedEntryState::Ephemeral {
1150 terminal,
1151 remaining_readers,
1152 } => {
1153 let lookup = lookup_from_terminal(terminal, entry.shared_id, &state.shared);
1154 *remaining_readers = remaining_readers.saturating_sub(1);
1155 let remove = *remaining_readers == 0;
1156 self.armed = false;
1157 if remove {
1158 remove_entry(state, self.entry_id, false);
1159 state.bump_revision();
1160 }
1161 WaitOutcome::Return(lookup)
1162 }
1163 _ => {
1164 self.armed = false;
1165 WaitOutcome::RetryProbe
1166 }
1167 }
1168 }
1169}
1170
1171impl<K: PreparedCacheKey, V: PreparedValue> Drop for EntryWaitGuard<'_, K, V> {
1172 fn drop(&mut self) {
1173 if !self.armed {
1174 return;
1175 }
1176 let mut state = self.cache.recover_state();
1177 match state.entries.get_mut(&self.entry_id) {
1178 Some(EntryRecord {
1179 state:
1180 PreparedEntryState::Preparing {
1181 attempt_id,
1182 waiting_readers,
1183 },
1184 ..
1185 }) if *attempt_id == self.attempt_id => {
1186 *waiting_readers = waiting_readers.saturating_sub(1);
1187 }
1188 Some(EntryRecord {
1189 state:
1190 PreparedEntryState::Ephemeral {
1191 remaining_readers, ..
1192 },
1193 ..
1194 }) => {
1195 *remaining_readers = remaining_readers.saturating_sub(1);
1196 if *remaining_readers == 0 {
1197 remove_entry(&mut state, self.entry_id, false);
1198 state.bump_revision();
1199 }
1200 }
1201 _ => {}
1202 }
1203 drop(state);
1204 self.cache.changed.notify_all();
1205 }
1206}
1207
1208struct QueueTicketGuard<'a, K: PreparedCacheKey, V: PreparedValue> {
1209 cache: &'a PreparedPlanCache<K, V>,
1210 queue_id: QueueId,
1211 armed: bool,
1212}
1213
1214impl<'a, K: PreparedCacheKey, V: PreparedValue> QueueTicketGuard<'a, K, V> {
1215 fn wait_for_turn(mut self) -> Result<QueueOutcome<'a, K, V>, Arc<PrepareError>> {
1216 if !self.armed {
1217 return Ok(QueueOutcome::RetryProbe);
1218 }
1219 let mut state = self.cache.lock_state()?;
1220 loop {
1221 let Some(record) = state.queue_records.get(&self.queue_id) else {
1222 self.armed = false;
1223 return Ok(QueueOutcome::RetryProbe);
1224 };
1225 debug_assert_eq!(record.id, self.queue_id);
1226 debug_assert!(Arc::ptr_eq(&record.generation, &state.generation));
1227 let at_front = state.queue_order.front().copied() == Some(self.queue_id);
1228 if at_front && state.in_flight < state.limits.max_in_flight_entries.get() {
1229 let key = Arc::clone(&record.key);
1230 let digest = record.digest;
1231 let signature_bytes = record.signature_bytes;
1232 let guard = start_attempt(
1233 &mut state,
1234 self.cache,
1235 key,
1236 digest,
1237 signature_bytes,
1238 Some(self.queue_id),
1239 )?;
1240 self.armed = false;
1241 return Ok(QueueOutcome::Produce(guard));
1242 }
1243 state = match self.cache.changed.wait(state) {
1244 Ok(state) => state,
1245 Err(error) => {
1246 drop(error.into_inner());
1247 return Err(Arc::new(PrepareError::CacheState {
1248 source: poisoned_state_error(),
1249 }));
1250 }
1251 };
1252 }
1253 }
1254}
1255
1256impl<K: PreparedCacheKey, V: PreparedValue> Drop for QueueTicketGuard<'_, K, V> {
1257 fn drop(&mut self) {
1258 if !self.armed {
1259 return;
1260 }
1261 let mut state = self.cache.recover_state();
1262 if let Some(record) = state.queue_records.get_mut(&self.queue_id) {
1263 record.tickets = record.tickets.saturating_sub(1);
1264 if record.tickets == 0 {
1265 remove_queue_record(&mut state, self.queue_id);
1266 state.bump_revision();
1267 }
1268 }
1269 drop(state);
1270 self.cache.changed.notify_all();
1271 }
1272}
1273
1274#[derive(Clone, Copy, Debug)]
1275struct StackFrame {
1276 type_id: TypeId,
1277 summary: PreparationKeySummary,
1278}
1279
1280struct StackGuard;
1281
1282impl StackGuard {
1283 fn push<K: PreparedCacheKey>(summary: PreparationKeySummary) -> Self {
1284 PREPARATION_STACK.with(|stack| {
1285 stack.borrow_mut().push(StackFrame {
1286 type_id: TypeId::of::<K>(),
1287 summary,
1288 });
1289 });
1290 Self
1291 }
1292}
1293
1294impl Drop for StackGuard {
1295 fn drop(&mut self) {
1296 PREPARATION_STACK.with(|stack| {
1297 let popped = stack.borrow_mut().pop();
1298 debug_assert!(popped.is_some());
1299 });
1300 }
1301}
1302
1303pub(crate) struct RuntimeCacheSet<K: PreparedCacheKey, V: PreparedValue> {
1304 prepared: PreparedPlanCache<K, V>,
1305}
1306
1307impl<K: PreparedCacheKey, V: PreparedValue> RuntimeCacheSet<K, V> {
1308 pub(crate) fn new(limits: PreparedPlanCacheLimits) -> Self {
1309 Self {
1310 prepared: PreparedPlanCache::new(limits),
1311 }
1312 }
1313
1314 pub(crate) fn cache_stats(
1315 &self,
1316 owners: &[FrozenCacheOwner],
1317 ) -> Result<RuntimeCacheStats, RuntimeCacheError> {
1318 let mut runtime_error = None;
1319 let prepared = match self.prepared.stats() {
1320 Ok(stats) => stats,
1321 Err(error) => {
1322 runtime_error = Some(error);
1323 PreparedPlanCacheStats::default()
1324 }
1325 };
1326 let mut engines = CacheStats::default();
1327 let mut extensions = CacheStats::default();
1328 let mut failures = Vec::new();
1329 for owner in owners {
1330 match owner.owner.cache_stats() {
1331 Ok(stats) => match owner.kind {
1332 FrozenCacheOwnerKind::Engine => saturating_add_cache_stats(&mut engines, stats),
1333 FrozenCacheOwnerKind::Extension => {
1334 saturating_add_cache_stats(&mut extensions, stats);
1335 }
1336 },
1337 Err(source) => failures.push(CacheOwnerFailure {
1338 owner: owner.id.clone(),
1339 source,
1340 }),
1341 }
1342 }
1343 if runtime_error.is_none() && failures.is_empty() {
1344 Ok(RuntimeCacheStats {
1345 prepared_plans: prepared,
1346 engines,
1347 extensions,
1348 })
1349 } else {
1350 Err(RuntimeCacheError::Aggregate {
1351 runtime: runtime_error,
1352 owners: failures.into_boxed_slice(),
1353 })
1354 }
1355 }
1356
1357 pub(crate) fn clear_caches(
1358 &self,
1359 owners: &[FrozenCacheOwner],
1360 ) -> Result<(), RuntimeCacheError> {
1361 let runtime_error = self.prepared.clear().err();
1362 let mut failures = Vec::new();
1363 for owner in owners {
1364 if let Err(source) = owner.owner.clear_caches() {
1365 failures.push(CacheOwnerFailure {
1366 owner: owner.id.clone(),
1367 source,
1368 });
1369 }
1370 }
1371 if runtime_error.is_none() && failures.is_empty() {
1372 Ok(())
1373 } else {
1374 Err(RuntimeCacheError::Aggregate {
1375 runtime: runtime_error,
1376 owners: failures.into_boxed_slice(),
1377 })
1378 }
1379 }
1380
1381 #[allow(dead_code)]
1382 pub(crate) fn prepared(&self) -> &PreparedPlanCache<K, V> {
1383 &self.prepared
1384 }
1385}
1386
1387#[cfg(test)]
1388pub(crate) struct RetainedChargeBreakdown {
1389 pub key_payload: usize,
1390 pub value_payload: usize,
1391 pub entry_record: usize,
1392 pub compact_bucket_record: usize,
1393 pub lru_record: usize,
1394 pub shared_record: usize,
1395 pub total: usize,
1396}
1397
1398#[derive(Debug)]
1399struct Publication<K: PreparedCacheKey, V: PreparedValue> {
1400 terminal: CacheTerminal<K, V>,
1401 lookup: CacheLookup<K, V>,
1402 retention: PublicationRetention<K>,
1403}
1404
1405impl<K: PreparedCacheKey, V: PreparedValue> Publication<K, V> {
1406 fn lookup(&self) -> CacheLookup<K, V> {
1407 match &self.lookup {
1408 CacheLookup::Ready(value) => CacheLookup::Ready(Arc::clone(value)),
1409 CacheLookup::Redirect {
1410 requirements,
1411 shared,
1412 } => CacheLookup::Redirect {
1413 requirements: requirements.clone(),
1414 shared: shared.as_ref().map(Arc::clone),
1415 },
1416 CacheLookup::FailedDeterministic(error) => {
1417 CacheLookup::FailedDeterministic(Arc::clone(error))
1418 }
1419 CacheLookup::FailedTransient(error) => CacheLookup::FailedTransient(Arc::clone(error)),
1420 }
1421 }
1422}
1423
1424#[derive(Debug)]
1425enum PublicationRetention<K: PreparedCacheKey> {
1426 Retain {
1427 entry_bytes: usize,
1428 shared: Option<SharedRetention<K::Shared>>,
1429 },
1430 Transient,
1431 Uncacheable,
1432}
1433
1434fn build_publication<K: PreparedCacheKey, V: PreparedValue>(
1435 key: &K,
1436 produced: CacheProduced<K, V>,
1437) -> Publication<K, V> {
1438 match produced {
1439 CacheProduced::Ready { value, shared } => {
1440 let effective_shared = shared.or_else(|| key.shared_retention());
1441 let terminal = CacheTerminal::Ready(Arc::clone(&value));
1442 let lookup = CacheLookup::Ready(value);
1443 let retention = ready_entry_bytes::<K, V>(
1444 key,
1445 match &terminal {
1446 CacheTerminal::Ready(value) => value,
1447 _ => unreachable!(),
1448 },
1449 )
1450 .map(|entry_bytes| PublicationRetention::Retain {
1451 entry_bytes,
1452 shared: effective_shared,
1453 })
1454 .unwrap_or(PublicationRetention::Uncacheable);
1455 Publication {
1456 terminal,
1457 lookup,
1458 retention,
1459 }
1460 }
1461 CacheProduced::Redirect {
1462 requirements,
1463 shared,
1464 } => {
1465 let effective_shared = shared.or_else(|| key.shared_retention());
1466 let shared_arc = effective_shared
1467 .as_ref()
1468 .map(|shared| Arc::clone(&shared.value));
1469 let terminal = CacheTerminal::Redirect {
1470 requirements: requirements.clone(),
1471 shared: shared_arc.clone(),
1472 };
1473 let lookup = CacheLookup::Redirect {
1474 requirements: requirements.clone(),
1475 shared: shared_arc,
1476 };
1477 let retention = redirect_entry_bytes::<K, V>(key, &requirements)
1478 .map(|entry_bytes| PublicationRetention::Retain {
1479 entry_bytes,
1480 shared: effective_shared,
1481 })
1482 .unwrap_or(PublicationRetention::Uncacheable);
1483 Publication {
1484 terminal,
1485 lookup,
1486 retention,
1487 }
1488 }
1489 CacheProduced::FailedDeterministic { error, shared } => {
1490 let effective_shared = shared.or_else(|| key.shared_retention());
1491 let terminal = CacheTerminal::FailedDeterministic(Arc::clone(&error));
1492 let lookup = CacheLookup::FailedDeterministic(error);
1493 let retention = deterministic_error_entry_bytes::<K, V>(key)
1494 .map(|entry_bytes| PublicationRetention::Retain {
1495 entry_bytes,
1496 shared: effective_shared,
1497 })
1498 .unwrap_or(PublicationRetention::Uncacheable);
1499 Publication {
1500 terminal,
1501 lookup,
1502 retention,
1503 }
1504 }
1505 CacheProduced::FailedTransient(error) => Publication {
1506 terminal: CacheTerminal::FailedTransient(Arc::clone(&error)),
1507 lookup: CacheLookup::FailedTransient(error),
1508 retention: PublicationRetention::Transient,
1509 },
1510 }
1511}
1512
1513fn start_attempt<'a, K: PreparedCacheKey, V: PreparedValue>(
1514 state: &mut CacheState<K, V>,
1515 cache: &'a PreparedPlanCache<K, V>,
1516 key: Arc<K>,
1517 digest: u128,
1518 signature_bytes: usize,
1519 promoted_queue: Option<QueueId>,
1520) -> Result<ProducerGuard<'a, K, V>, Arc<PrepareError>> {
1521 if let Some(queue_id) = promoted_queue {
1522 remove_queue_record(state, queue_id);
1523 }
1524 let key_bytes = key.retained_bytes().ok_or_else(accounting_prepare_error)?;
1525 let attempt_metadata_bytes = size_of::<ActiveAttempt<K>>();
1526 let visible_entry_bytes = checked_sum([size_of::<EntryRecord<K, V>>(), size_of::<EntryId>()])
1527 .ok_or_else(accounting_prepare_error)?;
1528 let active_bytes = checked_sum([
1529 key_bytes,
1530 signature_bytes,
1531 attempt_metadata_bytes,
1532 visible_entry_bytes,
1533 ])
1534 .ok_or_else(accounting_prepare_error)?;
1535 let attempt_id = allocate_attempt_id(state);
1536 let entry_id = allocate_entry_id(state);
1537 let attempt = ActiveAttempt {
1538 id: attempt_id,
1539 entry_id: Some(entry_id),
1540 digest,
1541 key: Arc::clone(&key),
1542 generation: Arc::clone(&state.generation),
1543 key_bytes,
1544 signature_bytes,
1545 attempt_metadata_bytes,
1546 visible_entry_bytes,
1547 };
1548 let entry = EntryRecord {
1549 id: entry_id,
1550 digest,
1551 key,
1552 state: PreparedEntryState::Preparing {
1553 attempt_id,
1554 waiting_readers: 0,
1555 },
1556 generation: Arc::clone(&state.generation),
1557 retained_bytes: visible_entry_bytes,
1558 shared_id: None,
1559 };
1560 state.attempts.insert(attempt_id, attempt);
1561 state.entries.insert(entry_id, entry);
1562 state
1563 .entry_buckets
1564 .entry(digest)
1565 .or_default()
1566 .push(entry_id);
1567 state.retained_bytes = state
1568 .retained_bytes
1569 .checked_add(active_bytes)
1570 .ok_or_else(accounting_prepare_error)?;
1571 state.in_flight = state
1572 .in_flight
1573 .checked_add(1)
1574 .ok_or_else(accounting_prepare_error)?;
1575 state.stats.preparations = state.stats.preparations.saturating_add(1);
1576 state.stats.misses = state.stats.misses.saturating_add(1);
1577 state.stats.peak_in_flight = state.stats.peak_in_flight.max(state.in_flight);
1578 state.bump_revision();
1579 cache.changed.notify_all();
1580 Ok(ProducerGuard {
1581 cache,
1582 attempt_id,
1583 entry_id,
1584 published: false,
1585 })
1586}
1587
1588fn retain_entry<K: PreparedCacheKey, V: PreparedValue>(
1589 state: &mut CacheState<K, V>,
1590 entry_id: EntryId,
1591 terminal: CacheTerminal<K, V>,
1592 entry_bytes: usize,
1593 shared_id: Option<SharedRetentionId>,
1594) -> Result<(), Arc<PrepareError>> {
1595 let Some(entry) = state.entries.get_mut(&entry_id) else {
1596 return Ok(());
1597 };
1598 let old_bytes = entry.retained_bytes;
1599 entry.state = PreparedEntryState::Retained(terminal);
1600 entry.retained_bytes = entry_bytes;
1601 entry.shared_id = shared_id;
1602 state.retained_bytes = state.retained_bytes.saturating_sub(old_bytes);
1603 state.retained_bytes = state
1604 .retained_bytes
1605 .checked_add(entry_bytes)
1606 .ok_or_else(accounting_prepare_error)?;
1607 state.lru.put(entry_id, ());
1608 Ok(())
1609}
1610
1611fn publish_ephemeral_or_remove<K: PreparedCacheKey, V: PreparedValue>(
1612 state: &mut CacheState<K, V>,
1613 entry_id: EntryId,
1614 terminal: CacheTerminal<K, V>,
1615 waiting_readers: usize,
1616) {
1617 if waiting_readers == 0 {
1618 remove_entry(state, entry_id, false);
1619 return;
1620 }
1621 let Some(digest) = state.entries.get(&entry_id).map(|entry| entry.digest) else {
1622 return;
1623 };
1624 remove_entry_index(state, digest, entry_id);
1625 if let Some(entry) = state.entries.get_mut(&entry_id) {
1626 state.retained_bytes = state.retained_bytes.saturating_sub(entry.retained_bytes);
1627 let ephemeral_bytes = size_of::<EntryRecord<K, V>>();
1628 entry.retained_bytes = ephemeral_bytes;
1629 entry.shared_id = None;
1630 entry.state = PreparedEntryState::Ephemeral {
1631 terminal,
1632 remaining_readers: waiting_readers,
1633 };
1634 state.retained_bytes = state.retained_bytes.saturating_add(ephemeral_bytes);
1635 }
1636}
1637
1638fn evict_to_limits<K: PreparedCacheKey, V: PreparedValue>(state: &mut CacheState<K, V>) {
1639 while state.lru.len() > state.limits.max_entries.get()
1640 || state.retained_bytes > state.limits.max_retained_bytes.get()
1641 {
1642 let Some((entry_id, ())) = state.lru.pop_lru() else {
1643 break;
1644 };
1645 remove_entry(state, entry_id, true);
1646 state.stats.evictions = state.stats.evictions.saturating_add(1);
1647 }
1648}
1649
1650fn remove_entry<K: PreparedCacheKey, V: PreparedValue>(
1651 state: &mut CacheState<K, V>,
1652 entry_id: EntryId,
1653 lru_already_removed: bool,
1654) {
1655 let Some(entry) = state.entries.remove(&entry_id) else {
1656 return;
1657 };
1658 remove_entry_index(state, entry.digest, entry_id);
1659 if !lru_already_removed {
1660 let _ = state.lru.pop(&entry_id);
1661 }
1662 if let Some(shared_id) = entry.shared_id {
1663 release_shared(state, shared_id);
1664 }
1665 state.retained_bytes = state.retained_bytes.saturating_sub(entry.retained_bytes);
1666}
1667
1668fn remove_entry_index<K: PreparedCacheKey, V: PreparedValue>(
1669 state: &mut CacheState<K, V>,
1670 digest: u128,
1671 entry_id: EntryId,
1672) {
1673 if let Some(bucket) = state.entry_buckets.get_mut(&digest) {
1674 bucket.retain(|id| *id != entry_id);
1675 if bucket.is_empty() {
1676 state.entry_buckets.remove(&digest);
1677 }
1678 }
1679}
1680
1681fn remove_queue_record<K: PreparedCacheKey, V: PreparedValue>(
1682 state: &mut CacheState<K, V>,
1683 queue_id: QueueId,
1684) {
1685 let Some(record) = state.queue_records.remove(&queue_id) else {
1686 return;
1687 };
1688 if let Some(bucket) = state.queue_buckets.get_mut(&record.digest) {
1689 bucket.retain(|id| *id != queue_id);
1690 if bucket.is_empty() {
1691 state.queue_buckets.remove(&record.digest);
1692 }
1693 }
1694 state.queue_order.retain(|id| *id != queue_id);
1695 state.retained_bytes = state.retained_bytes.saturating_sub(record.retained_bytes());
1696}
1697
1698fn clear_state<K: PreparedCacheKey, V: PreparedValue>(state: &mut CacheState<K, V>) {
1699 let mut retained_bytes = 0usize;
1700 for attempt in state.attempts.values_mut() {
1701 attempt.entry_id = None;
1702 retained_bytes =
1703 retained_bytes.saturating_add(attempt.retained_bytes_without_visible_entry());
1704 }
1705 state.entries.clear();
1706 state.entry_buckets.clear();
1707 state.lru.clear();
1708 state.queue_records.clear();
1709 state.queue_buckets.clear();
1710 state.queue_order.clear();
1711 state.shared.clear();
1712 state.retained_bytes = retained_bytes;
1713 state.generation = Arc::new(CacheGenerationToken);
1714 state.bump_revision();
1715}
1716
1717fn finish_attempt_accounting<K: PreparedCacheKey, V: PreparedValue>(
1718 state: &mut CacheState<K, V>,
1719 attempt: &ActiveAttempt<K>,
1720) {
1721 state.retained_bytes = state
1722 .retained_bytes
1723 .saturating_sub(attempt.retained_bytes_without_visible_entry());
1724 state.in_flight = state.in_flight.saturating_sub(1);
1725}
1726
1727fn retain_shared<K: PreparedCacheKey, V: PreparedValue>(
1728 state: &mut CacheState<K, V>,
1729 shared: SharedRetention<K::Shared>,
1730) -> Result<SharedRetentionId, Arc<PrepareError>> {
1731 let id = SharedRetentionId::of(&shared.value);
1732 if let Some(charge) = state.shared.get_mut(&id) {
1733 if !Arc::ptr_eq(&charge.value, &shared.value) {
1734 return Err(Arc::new(PrepareError::Engine {
1735 source: Arc::new(CacheInternalError::SharedPointerCollision),
1736 }));
1737 }
1738 charge.references = charge
1739 .references
1740 .checked_add(1)
1741 .ok_or_else(accounting_prepare_error)?;
1742 return Ok(id);
1743 }
1744 let retained_bytes = shared_new_charge(state, &shared)?;
1745 state.retained_bytes = state
1746 .retained_bytes
1747 .checked_add(retained_bytes)
1748 .ok_or_else(accounting_prepare_error)?;
1749 state.shared.insert(
1750 id,
1751 SharedCharge {
1752 value: shared.value,
1753 retained_bytes,
1754 references: 1,
1755 },
1756 );
1757 Ok(id)
1758}
1759
1760fn shared_new_charge<K: PreparedCacheKey, V: PreparedValue>(
1761 state: &CacheState<K, V>,
1762 shared: &SharedRetention<K::Shared>,
1763) -> Result<usize, Arc<PrepareError>> {
1764 let id = SharedRetentionId::of(&shared.value);
1765 if let Some(charge) = state.shared.get(&id) {
1766 if Arc::ptr_eq(&charge.value, &shared.value) {
1767 return Ok(0);
1768 }
1769 return Err(Arc::new(PrepareError::Engine {
1770 source: Arc::new(CacheInternalError::SharedPointerCollision),
1771 }));
1772 }
1773 checked_sum([
1774 shared.retained_bytes.ok_or_else(accounting_prepare_error)?,
1775 size_of::<SharedRetentionId>(),
1776 size_of::<SharedCharge<K::Shared>>(),
1777 ])
1778 .ok_or_else(accounting_prepare_error)
1779}
1780
1781fn release_shared<K: PreparedCacheKey, V: PreparedValue>(
1782 state: &mut CacheState<K, V>,
1783 shared_id: SharedRetentionId,
1784) {
1785 let Some(charge) = state.shared.get_mut(&shared_id) else {
1786 return;
1787 };
1788 charge.references = charge.references.saturating_sub(1);
1789 if charge.references == 0 {
1790 let charge = state
1791 .shared
1792 .remove(&shared_id)
1793 .expect("shared charge exists");
1794 state.retained_bytes = state.retained_bytes.saturating_sub(charge.retained_bytes);
1795 }
1796}
1797
1798fn lookup_from_terminal<K: PreparedCacheKey, V: PreparedValue>(
1799 terminal: &CacheTerminal<K, V>,
1800 shared_id: Option<SharedRetentionId>,
1801 shared_table: &HashMap<SharedRetentionId, SharedCharge<K::Shared>>,
1802) -> CacheLookup<K, V> {
1803 match terminal {
1804 CacheTerminal::Ready(value) => CacheLookup::Ready(Arc::clone(value)),
1805 CacheTerminal::Redirect {
1806 requirements,
1807 shared,
1808 } => CacheLookup::Redirect {
1809 requirements: requirements.clone(),
1810 shared: shared.as_ref().map(Arc::clone).or_else(|| {
1811 shared_id.and_then(|id| shared_table.get(&id).map(|s| Arc::clone(&s.value)))
1812 }),
1813 },
1814 CacheTerminal::FailedDeterministic(error) => {
1815 CacheLookup::FailedDeterministic(Arc::clone(error))
1816 }
1817 CacheTerminal::FailedTransient(error) => CacheLookup::FailedTransient(Arc::clone(error)),
1818 }
1819}
1820
1821fn lookup_from_produced<K: PreparedCacheKey, V: PreparedValue>(
1822 produced: CacheProduced<K, V>,
1823) -> CacheLookup<K, V> {
1824 match produced {
1825 CacheProduced::Ready { value, .. } => CacheLookup::Ready(value),
1826 CacheProduced::Redirect {
1827 requirements,
1828 shared,
1829 } => CacheLookup::Redirect {
1830 requirements,
1831 shared: shared.map(|shared| shared.value),
1832 },
1833 CacheProduced::FailedDeterministic { error, .. } => CacheLookup::FailedDeterministic(error),
1834 CacheProduced::FailedTransient(error) => CacheLookup::FailedTransient(error),
1835 }
1836}
1837
1838fn ready_entry_bytes<K: PreparedCacheKey, V: PreparedValue>(key: &K, value: &V) -> Option<usize> {
1839 checked_sum([
1840 key.retained_bytes()?,
1841 value.retained_bytes()?,
1842 size_of::<EntryRecord<K, V>>(),
1843 size_of::<EntryId>(),
1844 size_of::<EntryId>().checked_add(size_of::<()>())?,
1845 ])
1846}
1847
1848fn redirect_entry_bytes<K: PreparedCacheKey, V: PreparedValue>(
1849 key: &K,
1850 requirements: &SpecializationRequirements,
1851) -> Option<usize> {
1852 checked_sum([
1853 key.retained_bytes()?,
1854 specialization_requirements_retained_bytes(requirements)?,
1855 size_of::<EntryRecord<K, V>>(),
1856 size_of::<EntryId>(),
1857 size_of::<EntryId>().checked_add(size_of::<()>())?,
1858 ])
1859}
1860
1861fn deterministic_error_entry_bytes<K: PreparedCacheKey, V: PreparedValue>(
1862 key: &K,
1863) -> Option<usize> {
1864 checked_sum([
1865 key.retained_bytes()?,
1866 size_of::<PrepareError>(),
1867 size_of::<EntryRecord<K, V>>(),
1868 size_of::<EntryId>(),
1869 size_of::<EntryId>().checked_add(size_of::<()>())?,
1870 ])
1871}
1872
1873fn specialization_requirements_retained_bytes(
1874 requirements: &SpecializationRequirements,
1875) -> Option<usize> {
1876 checked_sum([
1877 size_of::<SpecializationRequirements>(),
1878 requirements
1879 .inputs()
1880 .len()
1881 .checked_mul(size_of::<super::InputSpecializationRequirements>())?,
1882 ])
1883}
1884
1885fn allocate_attempt_id<K: PreparedCacheKey, V: PreparedValue>(
1886 state: &mut CacheState<K, V>,
1887) -> AttemptId {
1888 loop {
1889 state.next_attempt = next_nonzero_id(state.next_attempt);
1890 let id = AttemptId(state.next_attempt);
1891 if !state.attempts.contains_key(&id) {
1892 return id;
1893 }
1894 }
1895}
1896
1897fn allocate_entry_id<K: PreparedCacheKey, V: PreparedValue>(
1898 state: &mut CacheState<K, V>,
1899) -> EntryId {
1900 loop {
1901 state.next_entry = next_nonzero_id(state.next_entry);
1902 let id = EntryId(state.next_entry);
1903 if !state.entries.contains_key(&id) {
1904 return id;
1905 }
1906 }
1907}
1908
1909fn allocate_queue_id<K: PreparedCacheKey, V: PreparedValue>(
1910 state: &mut CacheState<K, V>,
1911) -> QueueId {
1912 loop {
1913 state.next_queue = next_nonzero_id(state.next_queue);
1914 let id = QueueId(state.next_queue);
1915 if !state.queue_records.contains_key(&id) {
1916 return id;
1917 }
1918 }
1919}
1920
1921fn next_nonzero_id(current: u128) -> u128 {
1922 let next = current.wrapping_add(1);
1923 if next == 0 {
1924 1
1925 } else {
1926 next
1927 }
1928}
1929
1930fn checked_sum(values: impl IntoIterator<Item = usize>) -> Option<usize> {
1931 values
1932 .into_iter()
1933 .try_fold(0usize, |sum, value| sum.checked_add(value))
1934}
1935
1936fn check_preparation_stack<K: PreparedCacheKey>(
1937 requested: PreparationKeySummary,
1938) -> Result<(), Arc<PrepareError>> {
1939 PREPARATION_STACK.with(|stack| {
1940 let stack = stack.borrow();
1941 let Some(parent) = stack.last().copied() else {
1942 return Ok(());
1943 };
1944 if parent.type_id == TypeId::of::<K>() && parent.summary == requested {
1945 Err(Arc::new(PrepareError::PreparationCycle { key: requested }))
1946 } else {
1947 Err(Arc::new(PrepareError::NestedPreparationUnsupported {
1948 parent: parent.summary,
1949 requested,
1950 }))
1951 }
1952 })
1953}
1954
1955fn poisoned_state_error() -> RuntimeStateError {
1956 RuntimeStateError::Poisoned {
1957 lock: PREPARED_CACHE_LOCK,
1958 }
1959}
1960
1961fn accounting_prepare_error() -> Arc<PrepareError> {
1962 Arc::new(PrepareError::Engine {
1963 source: Arc::new(CacheInternalError::AccountingOverflow),
1964 })
1965}
1966
1967fn capacity_error<K: PreparedCacheKey, V: PreparedValue>(
1968 state: &CacheState<K, V>,
1969) -> Arc<PrepareError> {
1970 Arc::new(PrepareError::CacheInFlightCapacityExceeded {
1971 in_flight: state.in_flight,
1972 queued_distinct_keys: state.queue_records.len(),
1973 })
1974}
1975
1976fn saturating_add_cache_stats(total: &mut CacheStats, value: CacheStats) {
1977 total.entries = total.entries.saturating_add(value.entries);
1978 total.retained_bytes = total.retained_bytes.saturating_add(value.retained_bytes);
1979 total.hits = total.hits.saturating_add(value.hits);
1980 total.misses = total.misses.saturating_add(value.misses);
1981 total.evictions = total.evictions.saturating_add(value.evictions);
1982 total.clears = total.clears.saturating_add(value.clears);
1983}