Skip to main content

strat9_kernel/ipc/
quota.rs

1//! Per-process IPC resource quotas.
2//!
3//! Prevents a single process from exhausting system-wide IPC resources
4//! (channels, queue slots, buffered bytes) via unbounded creation.
5
6use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
7
8/// Maximum number of channels a single process may hold simultaneously.
9pub const MAX_CHANNELS_PER_PROCESS: u32 = 256;
10
11/// Maximum total queue slots (capacity sum) across all channels owned by a
12/// single process.
13pub const MAX_QUEUE_SLOTS_PER_PROCESS: u64 = 65_536;
14
15/// Maximum total buffered bytes across all channels owned by a single process.
16///
17/// Each `IpcMessage` is `IPC_MESSAGE_SIZE` (256) bytes, so this effectively
18/// caps the total number of queued messages.
19pub const MAX_BUFFERED_BYTES_PER_PROCESS: u64 = MAX_QUEUE_SLOTS_PER_PROCESS * 256;
20
21/// Per-process IPC resource counters.
22///
23/// All fields are atomic so they can be updated without holding a global
24/// lock.  They are always accessed from the owning process's context (the
25/// syscall handler), so `Relaxed` ordering is sufficient for the common
26/// case; `AcqRel` is used for the reserve/release pair to ensure that a
27/// failed reservation is never silently lost.
28#[derive(Debug)]
29pub struct IpcQuota {
30    /// Number of live channel capabilities (handles) held by this process.
31    pub channels: AtomicU32,
32    /// Sum of `capacity` across all channels created by this process.
33    pub queue_slots: AtomicU64,
34    /// `queue_slots * size_of::<IpcMessage>()` : total bytes that could be
35    /// buffered across all channels.
36    pub buffered_bytes: AtomicU64,
37}
38
39impl IpcQuota {
40    /// Creates a new, empty quota (all counters zero).
41    pub const fn new() -> Self {
42        IpcQuota {
43            channels: AtomicU32::new(0),
44            queue_slots: AtomicU64::new(0),
45            buffered_bytes: AtomicU64::new(0),
46        }
47    }
48
49    /// Try to reserve resources for a new channel.
50    ///
51    /// Returns `Ok(())` if the reservation fits within the process quota,
52    /// `Err(QuotaExceeded)` otherwise.  On failure **nothing** is modified.
53    pub fn try_reserve(&self, capacity: usize) -> Result<(), QuotaExceeded> {
54        let new_channels = self
55            .channels
56            .load(Ordering::Relaxed)
57            .checked_add(1)
58            .ok_or(QuotaExceeded)?;
59        if new_channels > MAX_CHANNELS_PER_PROCESS {
60            return Err(QuotaExceeded);
61        }
62
63        let slots = capacity as u64;
64        let new_slots = self
65            .queue_slots
66            .load(Ordering::Relaxed)
67            .checked_add(slots)
68            .ok_or(QuotaExceeded)?;
69        if new_slots > MAX_QUEUE_SLOTS_PER_PROCESS {
70            return Err(QuotaExceeded);
71        }
72
73        let bytes = slots * core::mem::size_of::<crate::ipc::message::IpcMessage>() as u64;
74        let new_bytes = self
75            .buffered_bytes
76            .load(Ordering::Relaxed)
77            .checked_add(bytes)
78            .ok_or(QuotaExceeded)?;
79        if new_bytes > MAX_BUFFERED_BYTES_PER_PROCESS {
80            return Err(QuotaExceeded);
81        }
82
83        // All checks passed : commit the reservation atomically.
84        self.channels.store(new_channels, Ordering::Release);
85        self.queue_slots.store(new_slots, Ordering::Release);
86        self.buffered_bytes.store(new_bytes, Ordering::Release);
87        Ok(())
88    }
89
90    /// Release resources previously reserved by [`try_reserve`].
91    ///
92    /// Must be called exactly once for every successful `try_reserve`.
93    /// Passing a `capacity` that was never reserved will underflow the
94    /// counters : the caller must ensure correctness.
95    pub fn release(&self, capacity: usize) {
96        let _ = self.channels.fetch_sub(1, Ordering::AcqRel);
97
98        let slots = capacity as u64;
99        let _ = self.queue_slots.fetch_sub(slots, Ordering::AcqRel);
100
101        let bytes = slots * core::mem::size_of::<crate::ipc::message::IpcMessage>() as u64;
102        let _ = self.buffered_bytes.fetch_sub(bytes, Ordering::AcqRel);
103    }
104
105    /// Returns a snapshot of the current counters.
106    pub fn snapshot(&self) -> QuotaSnapshot {
107        QuotaSnapshot {
108            channels: self.channels.load(Ordering::Relaxed),
109            queue_slots: self.queue_slots.load(Ordering::Relaxed),
110            buffered_bytes: self.buffered_bytes.load(Ordering::Relaxed),
111        }
112    }
113}
114
115impl Default for IpcQuota {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121/// Error returned when a quota reservation would exceed the limit.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct QuotaExceeded;
124
125/// Read-only snapshot of [`IpcQuota`] counters.
126#[derive(Debug, Clone, Copy)]
127pub struct QuotaSnapshot {
128    pub channels: u32,
129    pub queue_slots: u64,
130    pub buffered_bytes: u64,
131}