1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2use std::sync::{Arc, Mutex};
3
4use serde::{Deserialize, Serialize};
5
6use crate::ProofFrameError;
7
8const DEFAULT_MEMORY_BYTES: u64 = 512 * 1024 * 1024;
9const DEFAULT_TEMP_BYTES: u64 = 4 * 1024 * 1024 * 1024;
10const DEFAULT_OUTPUT_RECORDS: u64 = 100_000;
11const DEFAULT_SAMPLES: usize = 100;
12
13#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct ResourceLimits {
17 pub max_memory_bytes: u64,
18 pub max_temp_bytes: u64,
19 pub max_output_records: u64,
20 pub max_samples: usize,
21}
22
23impl Default for ResourceLimits {
24 fn default() -> Self {
25 Self {
26 max_memory_bytes: DEFAULT_MEMORY_BYTES,
27 max_temp_bytes: DEFAULT_TEMP_BYTES,
28 max_output_records: DEFAULT_OUTPUT_RECORDS,
29 max_samples: DEFAULT_SAMPLES,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Default)]
36pub struct CancellationToken {
37 cancelled: Arc<AtomicBool>,
38}
39
40impl CancellationToken {
41 #[must_use]
42 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn cancel(&self) {
47 self.cancelled.store(true, Ordering::Release);
48 }
49
50 #[must_use]
51 pub fn is_cancelled(&self) -> bool {
52 self.cancelled.load(Ordering::Acquire)
53 }
54
55 pub fn check(&self) -> Result<(), ProofFrameError> {
56 if self.is_cancelled() {
57 Err(ProofFrameError::Cancelled)
58 } else {
59 Ok(())
60 }
61 }
62}
63
64#[derive(Debug)]
65struct Counters {
66 memory_cap: u64,
67 temp_cap: u64,
68 memory: AtomicU64,
69 peak_memory: AtomicU64,
70 temp: AtomicU64,
71 peak_temp: AtomicU64,
72 reservation_lock: Mutex<()>,
73}
74
75impl Counters {
76 fn new(memory_cap: u64, temp_cap: u64) -> Self {
77 Self {
78 memory_cap,
79 temp_cap,
80 memory: AtomicU64::new(0),
81 peak_memory: AtomicU64::new(0),
82 temp: AtomicU64::new(0),
83 peak_temp: AtomicU64::new(0),
84 reservation_lock: Mutex::new(()),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct ResourceAccount {
92 nodes: Arc<[Arc<Counters>]>,
93 limits: ResourceLimits,
94}
95
96impl ResourceAccount {
97 #[must_use]
98 pub fn root(limits: ResourceLimits) -> Self {
99 Self {
100 nodes: Arc::from([Arc::new(Counters::new(
101 limits.max_memory_bytes,
102 limits.max_temp_bytes,
103 ))]),
104 limits,
105 }
106 }
107
108 #[must_use]
109 pub fn child(&self, memory_cap: u64, temp_cap: u64) -> Self {
110 let mut nodes = Vec::with_capacity(self.nodes.len() + 1);
111 nodes.extend(self.nodes.iter().cloned());
112 nodes.push(Arc::new(Counters::new(memory_cap, temp_cap)));
113 Self {
114 nodes: nodes.into(),
115 limits: self.limits,
116 }
117 }
118
119 pub fn try_reserve_memory(&self, bytes: u64) -> Result<MemoryReservation, ProofFrameError> {
120 Reservation::try_new(self.nodes.clone(), bytes, ResourceKind::Memory).map(|reservation| {
121 MemoryReservation {
122 _reservation: reservation,
123 }
124 })
125 }
126
127 pub fn try_reserve_temp(&self, bytes: u64) -> Result<TempReservation, ProofFrameError> {
128 Reservation::try_new(self.nodes.clone(), bytes, ResourceKind::Temp).map(|reservation| {
129 TempReservation {
130 _reservation: reservation,
131 }
132 })
133 }
134
135 #[must_use]
136 pub fn memory_used(&self) -> u64 {
137 self.current().memory.load(Ordering::Acquire)
138 }
139
140 #[must_use]
141 pub fn peak_memory_used(&self) -> u64 {
142 self.current().peak_memory.load(Ordering::Acquire)
143 }
144
145 #[must_use]
146 pub fn temp_used(&self) -> u64 {
147 self.current().temp.load(Ordering::Acquire)
148 }
149
150 #[must_use]
151 pub fn peak_temp_used(&self) -> u64 {
152 self.current().peak_temp.load(Ordering::Acquire)
153 }
154
155 #[must_use]
156 pub const fn limits(&self) -> ResourceLimits {
157 self.limits
158 }
159
160 fn current(&self) -> &Counters {
161 self.nodes.last().expect("a resource account has a root")
162 }
163}
164
165#[derive(Debug, Clone, Copy)]
166enum ResourceKind {
167 Memory,
168 Temp,
169}
170
171impl ResourceKind {
172 const fn name(self) -> &'static str {
173 match self {
174 Self::Memory => "memory",
175 Self::Temp => "temporary storage",
176 }
177 }
178
179 fn used_and_limit(self, counters: &Counters) -> (&AtomicU64, u64) {
180 match self {
181 Self::Memory => (&counters.memory, counters.memory_cap),
182 Self::Temp => (&counters.temp, counters.temp_cap),
183 }
184 }
185
186 fn peak(self, counters: &Counters) -> &AtomicU64 {
187 match self {
188 Self::Memory => &counters.peak_memory,
189 Self::Temp => &counters.peak_temp,
190 }
191 }
192}
193
194#[derive(Debug)]
195struct Reservation {
196 nodes: Arc<[Arc<Counters>]>,
197 bytes: u64,
198 kind: ResourceKind,
199}
200
201impl Reservation {
202 fn try_new(
203 nodes: Arc<[Arc<Counters>]>,
204 bytes: u64,
205 kind: ResourceKind,
206 ) -> Result<Self, ProofFrameError> {
207 let transaction_root = Arc::clone(&nodes[0]);
208 let _transaction = transaction_root
209 .reservation_lock
210 .lock()
211 .unwrap_or_else(|poisoned| poisoned.into_inner());
212 let mut committed_usage = Vec::with_capacity(nodes.len());
213 for (charged, counters) in nodes.iter().enumerate() {
214 let (used, cap) = kind.used_and_limit(counters);
215 let usage = match try_charge(used, cap, bytes) {
216 Ok(usage) => usage,
217 Err((current, limit)) => {
218 for rollback in &nodes[..charged] {
219 let (used, _) = kind.used_and_limit(rollback);
220 used.fetch_sub(bytes, Ordering::AcqRel);
221 }
222 return Err(ProofFrameError::ResourceLimit {
223 resource: kind.name(),
224 requested: bytes,
225 used: current,
226 limit,
227 });
228 }
229 };
230 committed_usage.push(usage);
231 }
232 for (counters, usage) in nodes.iter().zip(committed_usage) {
233 kind.peak(counters).fetch_max(usage, Ordering::AcqRel);
234 }
235 Ok(Self { nodes, bytes, kind })
236 }
237}
238
239impl Drop for Reservation {
240 fn drop(&mut self) {
241 for counters in self.nodes.iter() {
242 let (used, _) = self.kind.used_and_limit(counters);
243 used.fetch_sub(self.bytes, Ordering::AcqRel);
244 }
245 }
246}
247
248#[derive(Debug)]
250pub struct MemoryReservation {
251 _reservation: Reservation,
252}
253
254#[derive(Debug)]
256pub struct TempReservation {
257 _reservation: Reservation,
258}
259
260fn try_charge(used: &AtomicU64, limit: u64, bytes: u64) -> Result<u64, (u64, u64)> {
261 let mut current = used.load(Ordering::Acquire);
262 loop {
263 let Some(next) = current.checked_add(bytes) else {
264 return Err((current, limit));
265 };
266 if next > limit {
267 return Err((current, limit));
268 }
269 match used.compare_exchange_weak(current, next, Ordering::AcqRel, Ordering::Acquire) {
270 Ok(_) => {
271 return Ok(next);
272 }
273 Err(observed) => current = observed,
274 }
275 }
276}