Skip to main content

kernel/
limits.rs

1//! Explicit limits for the constrained entry writer. Stored in both checked
2//! metadata publications. Ordinary stores keep their existing WAL commit mode.
3use crate::{Error, Result};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct ResourceLimits {
7    pub data_bytes: u64,
8    pub wal_bytes: u64,
9    /// Maximum retired pages AND recycled pages tracked in one epoch.
10    pub tracked_pages: u32,
11    pub readers: u32,
12    pub record_bytes: u32,
13    /// Space kept unavailable to normal writes for an operator's recovery work.
14    /// Crash restart itself needs no new data pages in this mode.
15    pub recovery_bytes: u64,
16}
17
18impl ResourceLimits {
19    pub fn validate(self) -> Result<Self> {
20        if self.data_bytes < 3 * 4096
21            || self.data_bytes % 4096 != 0
22            || self.data_bytes / 4096 > u32::MAX as u64
23            || self.wal_bytes < 4096
24            || self.tracked_pages == 0
25            || self.readers == 0
26            || self.readers > 4096
27            || self.record_bytes == 0
28            || self.record_bytes as u64 + 4096 > self.wal_bytes
29        {
30            return Err(Error::ResourceLimit("invalid resource limits"));
31        }
32        self.total_bytes()?;
33        Ok(self)
34    }
35    pub fn freelist_bytes(self) -> u64 {
36        28 + 24 * self.tracked_pages as u64
37    }
38    /// Maximum managed logical bytes, including both freelist candidates,
39    /// fixed reader slots and unused recovery allowance. Filesystem metadata,
40    /// allocation rounding, unrelated files and recovery exports are separate.
41    pub fn total_bytes(self) -> Result<u64> {
42        self.data_bytes
43            .checked_add(self.wal_bytes)
44            .and_then(|n| n.checked_add(2 * self.freelist_bytes()))
45            .and_then(|n| n.checked_add(48 * self.readers as u64))
46            .and_then(|n| n.checked_add(self.recovery_bytes))
47            .ok_or(Error::ResourceLimit("total byte limit overflow"))
48    }
49    /// The 56-byte `E4LIMIT1` policy record. Public so a format owner above
50    /// the kernel (typed collections on the page-WAL) persists the identical
51    /// bytes instead of a second encoding of the same six fields.
52    pub fn encode(self) -> Vec<u8> {
53        let mut b = b"E4LIMIT1".to_vec();
54        for n in [
55            self.data_bytes,
56            self.wal_bytes,
57            self.tracked_pages as u64,
58            self.readers as u64,
59            self.record_bytes as u64,
60            self.recovery_bytes,
61        ] {
62            b.extend_from_slice(&n.to_le_bytes());
63        }
64        b
65    }
66    pub fn decode(b: &[u8]) -> Result<Self> {
67        if b.len() != 56 || &b[..8] != b"E4LIMIT1" {
68            return Err(Error::ResourceLimit(
69                "damaged resource policy; use source-preserving recovery",
70            ));
71        }
72        let n = |i| u64::from_le_bytes(b[i..i + 8].try_into().unwrap());
73        let small =
74            |i| u32::try_from(n(i)).map_err(|_| Error::ResourceLimit("resource policy overflow"));
75        Self {
76            data_bytes: n(8),
77            wal_bytes: n(16),
78            tracked_pages: small(24)?,
79            readers: small(32)?,
80            record_bytes: small(40)?,
81            recovery_bytes: n(48),
82        }
83        .validate()
84    }
85}
86
87/// Read only checked metadata, with a fixed small cache. Never changes a file.
88pub(crate) fn read(dir: &std::path::Path) -> Result<Option<ResourceLimits>> {
89    if !dir.join("data").exists() {
90        return Ok(None);
91    }
92    let file = crate::io::open_file_readonly(&dir.join("data"))?;
93    let budget = std::sync::Arc::new(crate::budget::MemoryBudget::new(65536));
94    let pool = crate::pool::BufferPool::new(file.into(), budget, 16)?;
95    crate::meta::Meta::read_limits(&pool)
96}