icydb_core/db/session/
request.rs1use std::{
7 cell::{Cell, RefCell},
8 rc::Rc,
9};
10
11use crate::db::executor::budget::{
12 ExecutionBudgetExceeded, HardExecutionBudget, HardExecutionContext,
13 HardExecutionFailureHeadroom, resource_index,
14};
15use icydb_diagnostic_code::{DiagnosticExecutionBudgetResource, DiagnosticExecutionBudgetScope};
16
17const REQUEST_FAILURE_HEADROOM: HardExecutionFailureHeadroom =
18 HardExecutionFailureHeadroom::new(500_000_000, 64 * 1_024);
19const REQUEST_HARD_BUDGET: HardExecutionBudget = HardExecutionBudget::new(
20 [
21 256, 1_024, 256, 250_000, 250_000, 128 * 1_024 * 1_024, 16_000_000, 16_000_000, 128 * 1_024 * 1_024, 128 * 1_024 * 1_024, 250_000, 32_000_000, 128 * 1_024 * 1_024, 100_000, 128 * 1_024 * 1_024, 1_000_000, 128 * 1_024 * 1_024, 1_000_000, 100_000, 64 * 1_024 * 1_024, 4_500_000_000, ],
43 REQUEST_FAILURE_HEADROOM,
44);
45
46thread_local! {
47 static CURRENT_REQUEST_SCOPE: RefCell<Option<RequestExecutionScope>> =
48 const { RefCell::new(None) };
49}
50
51pub struct RequestExecutionRoot {
57 scope: RequestExecutionScope,
58}
59
60impl RequestExecutionRoot {
61 #[doc(hidden)]
67 #[must_use]
68 pub fn __new_runtime_root() -> Self {
69 Self::from_budget(REQUEST_HARD_BUDGET)
70 }
71
72 #[doc(hidden)]
78 #[must_use]
79 pub fn __new_or_current_runtime_root() -> Self {
80 current_request_scope().map_or_else(Self::__new_runtime_root, |scope| Self { scope })
81 }
82
83 #[doc(hidden)]
89 pub fn __with_current_scope<T>(&self, run: impl FnOnce() -> T) -> T {
90 let _guard = CurrentRequestScopeGuard::enter(self.scope());
91 run()
92 }
93
94 #[doc(hidden)]
100 #[must_use]
101 pub fn __is_compatible_with_current(&self) -> bool {
102 match current_request_scope() {
103 Some(current) => current.same_counters(&self.scope),
104 None => true,
105 }
106 }
107
108 #[doc(hidden)]
110 #[must_use]
111 pub fn __is_current(&self) -> bool {
112 current_request_scope().is_some_and(|current| current.same_counters(&self.scope))
113 }
114
115 #[cfg(test)]
116 #[must_use]
117 pub(in crate::db) fn new_for_tests(budget: HardExecutionBudget) -> Self {
118 Self::from_budget(budget)
119 }
120
121 fn from_budget(budget: HardExecutionBudget) -> Self {
122 Self {
123 scope: RequestExecutionScope {
124 counters: Rc::new(RequestExecutionCounters {
125 budget,
126 observed: [const { Cell::new(0) };
127 DiagnosticExecutionBudgetResource::ALL.len()],
128 }),
129 },
130 }
131 }
132
133 pub(in crate::db) fn scope(&self) -> RequestExecutionScope {
134 self.scope.clone()
135 }
136
137 #[cfg(test)]
138 #[must_use]
139 pub(in crate::db) fn observed(&self, resource: DiagnosticExecutionBudgetResource) -> u64 {
140 self.scope.observed(resource)
141 }
142}
143
144pub(in crate::db) fn current_request_scope() -> Option<RequestExecutionScope> {
145 CURRENT_REQUEST_SCOPE.with(|current| current.borrow().clone())
146}
147
148struct CurrentRequestScopeGuard {
149 previous: Option<RequestExecutionScope>,
150}
151
152impl CurrentRequestScopeGuard {
153 fn enter(scope: RequestExecutionScope) -> Self {
154 let previous = CURRENT_REQUEST_SCOPE.with(|current| current.replace(Some(scope)));
155 Self { previous }
156 }
157}
158
159impl Drop for CurrentRequestScopeGuard {
160 fn drop(&mut self) {
161 CURRENT_REQUEST_SCOPE.with(|current| {
162 current.replace(self.previous.take());
163 });
164 }
165}
166
167#[derive(Clone)]
169pub(in crate::db) struct RequestExecutionScope {
170 counters: Rc<RequestExecutionCounters>,
171}
172
173impl RequestExecutionScope {
174 fn same_counters(&self, other: &Self) -> bool {
175 Rc::ptr_eq(&self.counters, &other.counters)
176 }
177
178 pub(in crate::db) fn charge(
179 &self,
180 context: HardExecutionContext,
181 resource: DiagnosticExecutionBudgetResource,
182 amount: u64,
183 ) -> Result<(), ExecutionBudgetExceeded> {
184 self.counters.charge(context, resource, amount)
185 }
186
187 #[cfg(test)]
188 fn observed(&self, resource: DiagnosticExecutionBudgetResource) -> u64 {
189 self.counters.observed[resource_index(resource)].get()
190 }
191}
192
193struct RequestExecutionCounters {
194 budget: HardExecutionBudget,
195 observed: [Cell<u64>; DiagnosticExecutionBudgetResource::ALL.len()],
196}
197
198impl RequestExecutionCounters {
199 fn charge(
200 &self,
201 context: HardExecutionContext,
202 resource: DiagnosticExecutionBudgetResource,
203 amount: u64,
204 ) -> Result<(), ExecutionBudgetExceeded> {
205 let index = resource_index(resource);
206 let counter = &self.observed[index];
207 let current = counter.get();
208 let (observed, overflowed) = current.overflowing_add(amount);
209 let observed = if overflowed { u64::MAX } else { observed };
210 counter.set(observed);
211 let limit = self.budget.limit(resource);
212 if overflowed || observed > limit {
213 return Err(ExecutionBudgetExceeded::new(
214 resource,
215 limit,
216 observed,
217 context.with_scope(DiagnosticExecutionBudgetScope::Request),
218 ));
219 }
220
221 Ok(())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn synchronous_scope_is_installed_then_removed() {
231 assert!(current_request_scope().is_none());
232 let root = RequestExecutionRoot::new_for_tests(REQUEST_HARD_BUDGET);
233
234 root.__with_current_scope(|| {
235 assert!(current_request_scope().is_some());
236 });
237
238 assert!(current_request_scope().is_none());
239 }
240
241 #[test]
242 fn nested_entry_reuses_current_counters() {
243 let resource = DiagnosticExecutionBudgetResource::QueryExecutions;
244 let budget = REQUEST_HARD_BUDGET.with_limit_for_tests(resource, 1);
245 let root = RequestExecutionRoot::new_for_tests(budget);
246 let context = HardExecutionContext::new(
247 DiagnosticExecutionBudgetScope::Execution,
248 icydb_diagnostic_code::DiagnosticExecutionLane::PublicRead,
249 0,
250 );
251
252 root.__with_current_scope(|| {
253 let nested = RequestExecutionRoot::__new_or_current_runtime_root();
254 nested
255 .scope()
256 .charge(context, resource, 1)
257 .expect("first nested charge should fit");
258 let exhausted = root
259 .scope()
260 .charge(context, resource, 1)
261 .expect_err("parent should observe the nested charge");
262
263 assert_eq!(exhausted.scope(), DiagnosticExecutionBudgetScope::Request);
264 assert_eq!(exhausted.observed(), 2);
265 });
266 }
267
268 #[test]
269 fn explicit_root_compatibility_rejects_a_different_active_root() {
270 let first = RequestExecutionRoot::new_for_tests(REQUEST_HARD_BUDGET);
271 let second = RequestExecutionRoot::new_for_tests(REQUEST_HARD_BUDGET);
272
273 assert!(first.__is_compatible_with_current());
274 assert!(!first.__is_current());
275 first.__with_current_scope(|| {
276 assert!(first.__is_current());
277 assert!(first.__is_compatible_with_current());
278 assert!(!second.__is_compatible_with_current());
279 });
280 assert!(second.__is_compatible_with_current());
281 }
282}