Skip to main content

superfluid_daemon/
pins.rs

1//! Pinned prefixes.
2
3use 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}