1use std::collections::{HashMap, HashSet};
4use std::sync::atomic::Ordering;
5use std::sync::mpsc::Sender;
6
7use superfluid_proto::linkw::ALL_SPACES;
8
9use crate::runtime::EngineHost;
10use crate::scheduler::SchedStats;
11
12pub(crate) const LEGACY_RENEW_TICKS: u64 = 3;
13
14pub(crate) fn renew_every(ttl_ticks: Option<u64>) -> Option<u64> {
15 match ttl_ticks {
16 None => Some(LEGACY_RENEW_TICKS),
17 Some(0) => None,
18 Some(ttl) => Some((ttl / 2).max(1)),
19 }
20}
21
22pub(crate) const MAX_PINS: usize = 256;
23
24const ACQUIRE_TRIES: usize = 4;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum PinKey {
28 Session(u64),
29 Prefix(u64),
30}
31
32pub struct PinReq {
33 pub key: PinKey,
34 pub tokens: Vec<u32>,
35 pub want: u64,
36 pub deadline_unix_ms: u64,
37 pub anchor: Option<u64>,
38 pub reply: Option<Sender<u64>>,
39}
40
41const LOOKAHEAD: u32 = u32::MAX;
42
43fn stageable<'a>(host: &EngineHost, tokens: &'a [u32]) -> &'a [u32] {
44 match host.max_stream_tokens() as usize {
45 0 => tokens,
46 max => &tokens[..tokens.len().min(max)],
47 }
48}
49
50pub fn session_target(stream: &[u32]) -> (Vec<u32>, u64) {
51 let mut tokens = Vec::with_capacity(stream.len() + 1);
52 tokens.extend_from_slice(stream);
53 tokens.push(LOOKAHEAD);
54 (tokens, stream.len() as u64)
55}
56
57pub(crate) fn common_seed_lengths(
58 host: &EngineHost,
59 m: &superfluid_proto::linkw::MatchResMsg,
60 cap: u64,
61) -> Vec<u64> {
62 enum Serves {
63 UpTo(u64, u64),
64 Exactly(HashSet<u64>),
65 }
66 let mut spaces = Vec::new();
67 let exact = host.exact_seeds_only();
68 for s in &m.spaces {
69 let (kind, page) = host.space_shape(s.space_id).unwrap_or((0, 0));
70 if kind == superfluid_abi::space_kind::ENCODER_CACHE {
71 continue;
72 }
73 let lens = s.candidates.iter().map(|c| c.prefix_len);
74 spaces.push(if page > 0 && !exact {
75 let page = page as u64;
76 let longest = lens.map(|l| l / page * page).max().unwrap_or(0);
77 Serves::UpTo(page, longest)
78 } else {
79 Serves::Exactly(lens.collect())
80 });
81 }
82 if spaces.is_empty() {
83 return Vec::new();
84 }
85 fn gcd(a: u64, b: u64) -> u64 {
86 if b == 0 {
87 a
88 } else {
89 gcd(b, a % b)
90 }
91 }
92 let mut unit = 0u64;
93 let mut paged_max: Option<u64> = None;
94 for sp in &spaces {
95 if let Serves::UpTo(page, longest) = sp {
96 unit = if unit == 0 {
97 *page
98 } else {
99 unit / gcd(unit, *page) * page
100 };
101 paged_max = Some(paged_max.map_or(*longest, |m| m.min(*longest)));
102 }
103 }
104 let paged_max = paged_max.map(|m| m.min(cap) / unit * unit);
105 let mut lens: Vec<u64> = paged_max
106 .into_iter()
107 .chain(spaces.iter().flat_map(|sp| match sp {
108 Serves::UpTo(..) => Vec::new(),
109 Serves::Exactly(set) => set.iter().copied().collect(),
110 }))
111 .filter(|&l| l > 0 && l <= cap)
112 .filter(|&l| {
113 spaces.iter().all(|sp| match sp {
114 Serves::UpTo(page, longest) => l % page == 0 && l <= *longest,
115 Serves::Exactly(set) => set.contains(&l),
116 })
117 })
118 .collect();
119 lens.sort_unstable_by(|a, b| b.cmp(a));
120 lens.dedup();
121 lens
122}
123
124pub fn prefix_digest(tokens: &[u32]) -> u64 {
125 prefix_digest_from(0xCBF2_9CE4_8422_2325, tokens)
126}
127
128fn prefix_digest_from(seed: u64, tokens: &[u32]) -> u64 {
129 let mut h = seed;
130 for t in tokens {
131 h = superfluid_fingerprint::fnv1a64(&t.to_le_bytes(), h);
132 }
133 h
134}
135
136struct Lease {
137 handle: u64,
138 len: u64,
139 blocks: Vec<(u64, u64)>,
140 renewed_tick: u64,
141 renew_every: u64,
142}
143
144struct Pin {
145 tokens: Vec<u32>,
146 want: u64,
147 deadline_unix_ms: u64,
148 anchors: HashSet<u64>,
149 touched: u64,
150 lease: Option<Lease>,
151}
152
153pub struct Pins {
154 map: HashMap<PinKey, Pin>,
155 clock: u64,
156 budget_pct: u64,
157 fallback_pool_tokens: u64,
158 host_generation: u64,
159 lease_too_short: bool,
160 bytes: u64,
161 dirty: bool,
162}
163
164fn lease_blocks(host: &EngineHost, tokens: &[u32]) -> Vec<(u64, u64)> {
165 let mut out: Vec<(u64, u64)> = host
166 .paged_spaces()
167 .into_iter()
168 .flat_map(|(space_id, page, bytes)| {
169 let seed = superfluid_fingerprint::fnv1a64(&space_id.to_le_bytes(), 0);
170 block_chain_from(seed, tokens, page)
171 .into_iter()
172 .map(move |h| (h, bytes))
173 })
174 .collect();
175 for (space_id, bytes) in host.blob_spaces() {
176 let seed = superfluid_fingerprint::fnv1a64(&space_id.to_le_bytes(), 1);
177 out.push((prefix_digest_from(seed, tokens), bytes));
178 }
179 out
180}
181
182#[cfg(test)]
183fn block_chain(tokens: &[u32], page: u64) -> Vec<u64> {
184 block_chain_from(0xCBF2_9CE4_8422_2325, tokens, page)
185}
186
187fn block_chain_from(seed: u64, tokens: &[u32], page: u64) -> Vec<u64> {
188 let mut out = Vec::with_capacity(tokens.len() / page.max(1) as usize);
189 let mut h = seed;
190 for block in tokens.chunks_exact(page.max(1) as usize) {
191 for t in block {
192 h = superfluid_fingerprint::fnv1a64(&t.to_le_bytes(), h);
193 }
194 out.push(h);
195 }
196 out
197}
198
199impl Pins {
200 pub fn new(budget_pct: u8, fallback_pool_tokens: u64) -> Pins {
201 Pins {
202 map: HashMap::new(),
203 clock: 0,
204 budget_pct: budget_pct as u64,
205 fallback_pool_tokens,
206 host_generation: 0,
207 lease_too_short: false,
208 bytes: 0,
209 dirty: false,
210 }
211 }
212
213 pub fn set_fallback_pool_tokens(&mut self, tokens: u64) {
214 self.fallback_pool_tokens = tokens;
215 }
216
217 pub fn is_empty(&self) -> bool {
218 self.map.is_empty()
219 }
220
221 pub fn until_next_deadline(&self) -> Option<std::time::Duration> {
222 let next = self.map.values().map(|p| p.deadline_unix_ms).min()?;
223 let now = crate::wal::now_unix_ms();
224 Some(std::time::Duration::from_millis(next.saturating_sub(now)))
225 }
226
227 pub fn any_held(&self) -> bool {
228 self.map.values().any(|p| p.lease.is_some())
229 }
230
231 pub fn enforceable(&self, host: &EngineHost) -> bool {
232 self.budget_pct > 0
233 && !self.lease_too_short
234 && host.page_size().is_some()
235 && host.is_alive()
236 && !host.has_unservable_space()
237 && host.cache_serves_all_spaces()
238 }
239
240 fn pinned_bytes(&mut self) -> u64 {
241 if self.dirty {
242 let mut seen: HashMap<u64, u64> = HashMap::new();
243 for p in self.map.values() {
244 if let Some(l) = &p.lease {
245 seen.extend(l.blocks.iter().copied());
246 }
247 }
248 self.bytes = seen.values().sum();
249 self.dirty = false;
250 }
251 self.bytes
252 }
253
254 pub fn is_anchor(&self, session: u64) -> bool {
255 self.map.values().any(|p| p.anchors.contains(&session))
256 }
257
258 pub fn awaits_lease(&self, host: &EngineHost, session: u64) -> bool {
259 self.enforceable(host) && self.is_anchor(session)
260 }
261
262 pub fn held_max(&self, host: &EngineHost, keys: &[PinKey]) -> u64 {
263 if host.generation() != self.host_generation || !host.is_alive() {
264 return 0;
265 }
266 let now = crate::wal::now_unix_ms();
267 keys.iter()
268 .filter(|k| self.map.get(k).is_some_and(|p| p.deadline_unix_ms > now))
269 .map(|k| self.held(*k))
270 .max()
271 .unwrap_or(0)
272 }
273
274 fn held(&self, key: PinKey) -> u64 {
275 self.map
276 .get(&key)
277 .and_then(|p| p.lease.as_ref())
278 .map(|l| l.len)
279 .unwrap_or(0)
280 }
281
282 fn publish_gauges(&mut self, stats: &SchedStats) {
283 let held = self.map.values().filter(|p| p.lease.is_some()).count() as u64;
284 let bytes = self.pinned_bytes();
285 stats.pins_held.store(held, Ordering::Relaxed);
286 stats.pinned_bytes.store(bytes, Ordering::Relaxed);
287 }
288
289 fn check_generation(&mut self, host: &EngineHost) {
290 if host.generation() == self.host_generation && host.is_alive() {
291 return;
292 }
293 if host.generation() != self.host_generation {
294 self.lease_too_short = false;
295 }
296 self.host_generation = host.generation();
297 for p in self.map.values_mut() {
298 p.lease = None;
299 }
300 self.dirty = true;
301 }
302
303 fn release_lease(host: &mut EngineHost, lease: Lease) {
304 if host.is_alive() {
305 let _ = host.client().seed_release(lease.handle);
306 }
307 }
308
309 pub fn handle(&mut self, host: &mut EngineHost, stats: &SchedStats, tick: u64, req: PinReq) {
310 self.check_generation(host);
311 self.sweep(host, stats);
312 let held = if req.deadline_unix_ms == 0 {
313 if let Some(p) = self.map.remove(&req.key) {
314 if let Some(l) = p.lease {
315 Self::release_lease(host, l);
316 self.dirty = true;
317 }
318 }
319 0
320 } else {
321 self.clock += 1;
322 let clock = self.clock;
323 let pin = self.map.entry(req.key).or_insert_with(|| Pin {
324 tokens: Vec::new(),
325 want: 0,
326 deadline_unix_ms: 0,
327 anchors: HashSet::new(),
328 touched: 0,
329 lease: None,
330 });
331 pin.deadline_unix_ms = match req.key {
332 PinKey::Prefix(_) => pin.deadline_unix_ms.max(req.deadline_unix_ms),
333 PinKey::Session(_) => req.deadline_unix_ms,
334 };
335 pin.touched = clock;
336 if let Some(a) = req.anchor {
337 pin.anchors.insert(a);
338 }
339 if !req.tokens.is_empty() && req.want >= pin.want {
340 pin.tokens = req.tokens;
341 pin.want = req.want;
342 }
343 self.cap_entries(host, stats, req.key);
344 self.acquire(host, tick, req.key);
345 self.enforce_budget(host, stats, Some(req.key));
346 self.held(req.key)
347 };
348 self.publish_gauges(stats);
349 if let Some(reply) = req.reply {
350 let _ = reply.send(held);
351 }
352 }
353
354 pub fn unanchor(&mut self, session: u64) {
355 for (k, p) in self.map.iter_mut() {
356 if matches!(k, PinKey::Prefix(_)) {
357 p.anchors.remove(&session);
358 }
359 }
360 }
361
362 pub fn sweep(&mut self, host: &mut EngineHost, stats: &SchedStats) {
363 if self.map.is_empty() {
364 return;
365 }
366 if host.generation() != self.host_generation || !host.is_alive() {
367 self.check_generation(host);
368 self.publish_gauges(stats);
369 }
370 let now = crate::wal::now_unix_ms();
371 let expired: Vec<PinKey> = self
372 .map
373 .iter()
374 .filter(|(_, p)| p.deadline_unix_ms <= now)
375 .map(|(k, _)| *k)
376 .collect();
377 if expired.is_empty() {
378 return;
379 }
380 for key in expired {
381 if let Some(p) = self.map.remove(&key) {
382 if let Some(l) = p.lease {
383 Self::release_lease(host, l);
384 self.dirty = true;
385 }
386 stats.pins_expired.fetch_add(1, Ordering::Relaxed);
387 }
388 }
389 self.publish_gauges(stats);
390 }
391
392 pub fn after_tick(
393 &mut self,
394 host: &mut EngineHost,
395 stats: &SchedStats,
396 tick: u64,
397 retired: &[(u64, Vec<u32>)],
398 ) {
399 if self.map.is_empty() {
400 return;
401 }
402 self.sweep(host, stats);
403 for (session, stream) in retired {
404 let keys: Vec<PinKey> = self
405 .map
406 .iter()
407 .filter(|(_, p)| p.anchors.contains(session))
408 .map(|(k, _)| *k)
409 .collect();
410 for key in keys {
411 if key == PinKey::Session(*session) {
412 if let Some(p) = self.map.get_mut(&key) {
413 (p.tokens, p.want) = session_target(stream);
414 }
415 } else if let Some(p) = self.map.get_mut(&key) {
416 p.anchors.remove(session);
417 }
418 self.acquire(host, tick, key);
419 }
420 }
421 self.renew(host, tick);
422 self.enforce_budget(host, stats, None);
423 self.publish_gauges(stats);
424 }
425
426 fn acquire(&mut self, host: &mut EngineHost, tick: u64, key: PinKey) {
427 if !self.enforceable(host) {
428 return;
429 }
430 let Some(page) = host.page_size() else { return };
431 let Some(pin) = self.map.get(&key) else {
432 return;
433 };
434 let tokens = stageable(host, &pin.tokens).to_vec();
435 let n = tokens.len() as u64;
436 let cap = pin.want.min(n.saturating_sub(1)) / page * page;
437 let current = pin.lease.as_ref().map(|l| l.len).unwrap_or(0);
438 if cap < page || current >= cap {
439 return;
440 }
441 let Ok(span) = host.client().stage_prompt(&tokens) else {
442 return;
443 };
444 let Ok(m) = host.client().match_prefix(ALL_SPACES, span) else {
445 return;
446 };
447 let lens = common_seed_lengths(host, &m, cap);
448 for len in lens
449 .into_iter()
450 .filter(|&l| l >= page && l > current)
451 .take(ACQUIRE_TRIES)
452 {
453 let Ok((handle, ttl)) = host.client().seed_acquire_leased(span, len, 0) else {
454 continue;
455 };
456 let Some(renew_every) = renew_every(ttl) else {
457 self.refuse_short_leases(host, handle);
458 return;
459 };
460 let lease = Lease {
461 handle,
462 len,
463 blocks: lease_blocks(host, &tokens[..len as usize]),
464 renewed_tick: tick,
465 renew_every,
466 };
467 let old = self.map.get_mut(&key).and_then(|p| p.lease.replace(lease));
468 if let Some(old) = old {
469 Self::release_lease(host, old);
470 }
471 self.dirty = true;
472 return;
473 }
474 }
475
476 fn renew(&mut self, host: &mut EngineHost, tick: u64) {
477 let due: Vec<PinKey> = self
478 .map
479 .iter()
480 .filter(|(_, p)| {
481 p.lease
482 .as_ref()
483 .is_some_and(|l| tick.saturating_sub(l.renewed_tick) >= l.renew_every)
484 })
485 .map(|(k, _)| *k)
486 .collect();
487 for key in due {
488 let Some(pin) = self.map.get(&key) else {
489 continue;
490 };
491 let Some(len) = pin.lease.as_ref().map(|l| l.len) else {
492 continue;
493 };
494 let tokens = stageable(host, &pin.tokens).to_vec();
495 let fresh = if host.is_alive() {
496 host.client()
497 .stage_prompt(&tokens)
498 .ok()
499 .and_then(|span| host.client().seed_acquire_leased(span, len, 0).ok())
500 } else {
501 None
502 };
503 let Some(pin) = self.map.get_mut(&key) else {
504 continue;
505 };
506 match fresh {
507 Some((handle, ttl)) => {
508 let Some(every) = renew_every(ttl) else {
509 if let Some(l) = pin.lease.take() {
510 Self::release_lease(host, l);
511 }
512 self.refuse_short_leases(host, handle);
513 continue;
514 };
515 let lease = pin.lease.as_mut().expect("checked above");
516 let old = std::mem::replace(&mut lease.handle, handle);
517 lease.renewed_tick = tick;
518 lease.renew_every = every;
519 let _ = host.client().seed_release(old);
520 }
521 None => {
522 if let Some(l) = pin.lease.take() {
523 Self::release_lease(host, l);
524 self.dirty = true;
525 }
526 }
527 }
528 }
529 }
530
531 fn refuse_short_leases(&mut self, host: &mut EngineHost, handle: u64) {
532 let _ = host.client().seed_release(handle);
533 for p in self.map.values_mut() {
534 if let Some(l) = p.lease.take() {
535 Self::release_lease(host, l);
536 }
537 }
538 self.dirty = true;
539 if !self.lease_too_short {
540 tracing::warn!(
541 "engine seed leases live 0 ticks: pins cannot be held across ticks and are left \
542 unenforced (recorded only) under this worker"
543 );
544 }
545 self.lease_too_short = true;
546 }
547
548 fn yield_oldest(
549 &mut self,
550 host: &mut EngineHost,
551 stats: &SchedStats,
552 keep: Option<PinKey>,
553 ) -> bool {
554 let victim = self
555 .map
556 .iter()
557 .filter(|(_, p)| p.lease.is_some())
558 .min_by_key(|(k, p)| (Some(**k) == keep, p.touched))
559 .map(|(k, _)| *k);
560 let Some(key) = victim else { return false };
561 if let Some(l) = self.map.get_mut(&key).and_then(|p| p.lease.take()) {
562 Self::release_lease(host, l);
563 }
564 self.dirty = true;
565 stats.pins_yielded.fetch_add(1, Ordering::Relaxed);
566 true
567 }
568
569 fn enforce_budget(&mut self, host: &mut EngineHost, stats: &SchedStats, keep: Option<PinKey>) {
570 let Some(block_bytes) = host.kv_block_bytes() else {
571 return;
572 };
573 let mut total = stats.pool_blocks_total.load(Ordering::Relaxed);
574 if total == 0 {
575 total = self.fallback_pool_tokens / host.page_size().unwrap_or(1).max(1);
576 }
577 if total == 0 {
578 return;
579 }
580 let budget = total * block_bytes * self.budget_pct / 100;
581 while self.pinned_bytes() > budget {
582 if !self.yield_oldest(host, stats, keep) {
583 break;
584 }
585 }
586 }
587
588 fn cap_entries(&mut self, host: &mut EngineHost, stats: &SchedStats, keep: PinKey) {
589 let is_prefix = |k: &PinKey| matches!(k, PinKey::Prefix(_));
590 while self.map.keys().filter(|k| is_prefix(k)).count() > MAX_PINS {
591 let victim = self
592 .map
593 .iter()
594 .filter(|(k, _)| is_prefix(k) && **k != keep)
595 .min_by_key(|(_, p)| p.touched)
596 .map(|(k, _)| *k);
597 let Some(key) = victim else { return };
598 if let Some(p) = self.map.remove(&key) {
599 if let Some(l) = p.lease {
600 Self::release_lease(host, l);
601 self.dirty = true;
602 stats.pins_yielded.fetch_add(1, Ordering::Relaxed);
603 }
604 }
605 }
606 }
607
608 pub fn yield_for(&mut self, host: &mut EngineHost, stats: &SchedStats, bytes: u64) -> u64 {
609 let before = self.pinned_bytes();
610 while before - self.pinned_bytes() < bytes {
611 if !self.yield_oldest(host, stats, None) {
612 break;
613 }
614 }
615 self.publish_gauges(stats);
616 before - self.pinned_bytes()
617 }
618
619 pub fn yield_all(&mut self, host: &mut EngineHost, stats: &SchedStats) -> bool {
620 if !self.any_held() {
621 return false;
622 }
623 while self.yield_oldest(host, stats, None) {}
624 self.publish_gauges(stats);
625 true
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632
633 #[test]
634 fn a_head_only_runtime_is_seeded_only_at_an_entry_length() {
635 use superfluid_executor::fake::FakeConfig;
636 use superfluid_executor::{Executor, ExecutorConfig, FakePrimitives};
637 let host = |truncate_partial: bool| {
638 EngineHost::spawn(move || {
639 let cfg = FakeConfig { truncate_partial, page: 16, ..Default::default() };
640 (Executor::new(FakePrimitives::new(cfg), ExecutorConfig::default()), None)
641 })
642 .expect("spawn")
643 };
644 let cached_40 = superfluid_proto::linkw::MatchResMsg {
645 spaces: vec![superfluid_proto::linkw::SpaceMatchMsg {
646 space_id: 1,
647 candidates: vec![superfluid_proto::linkw::MatchCandidateMsg {
648 prefix_len: 40,
649 provenance_digest: [0; 32],
650 taint_bits: 0,
651 resident_tier: 0,
652 }],
653 }],
654 };
655 let paged = host(true);
656 assert!(!paged.exact_seeds_only());
657 assert_eq!(common_seed_lengths(&paged, &cached_40, 64), [32], "any page boundary within the match");
658 let head_only = host(false);
659 assert!(head_only.exact_seeds_only());
660 assert_eq!(common_seed_lengths(&head_only, &cached_40, 64), [40], "the entry's own length");
661 assert_eq!(common_seed_lengths(&head_only, &cached_40, 32), Vec::<u64>::new(), "no cut below the head");
662 }
663
664 #[test]
665 fn nested_prefixes_share_their_leading_blocks() {
666 let long: Vec<u32> = (0..64).collect();
667 let a = block_chain(&long[..32], 16);
668 let b = block_chain(&long, 16);
669 assert_eq!(a.len(), 2);
670 assert_eq!(b.len(), 4);
671 assert_eq!(
672 a[..],
673 b[..2],
674 "a prefix's blocks are the longer pin's leading blocks"
675 );
676 let other: Vec<u32> = (1..33).collect();
677 assert_ne!(block_chain(&other, 16)[0], a[0]);
678 assert_eq!(block_chain(&long[..40], 16).len(), 2);
679 }
680
681 #[test]
682 fn renewal_cadence_follows_the_granted_lease() {
683 assert_eq!(renew_every(Some(64)), Some(32));
684 assert_eq!(renew_every(Some(8)), Some(4));
685 assert_eq!(renew_every(Some(3)), Some(1));
686 assert_eq!(renew_every(Some(1)), Some(1));
687 assert_eq!(renew_every(Some(0)), None);
688 assert_eq!(renew_every(None), Some(LEGACY_RENEW_TICKS));
689 for ttl in 1..200u64 {
690 assert!(renew_every(Some(ttl)).unwrap() < ttl + 1);
691 }
692 }
693
694 #[test]
695 fn prefix_digest_is_content_identity() {
696 assert_eq!(prefix_digest(&[1, 2, 3]), prefix_digest(&[1, 2, 3]));
697 assert_ne!(prefix_digest(&[1, 2, 3]), prefix_digest(&[1, 2]));
698 }
699}