1use eredu_core::{execution_control::*, generation::FinishReason};
4use std::{cell::RefCell, rc::Rc};
5
6mod choice;
7mod sampling;
8mod snapshot;
9pub use choice::{TokenChoiceController, TokenChoiceError};
10pub use sampling::{
11 apply_prepared_sampling_override, apply_sampling_override, SamplingOverride,
12 SamplingOverrideError, SamplingStateFacts, TextSamplingControlBackend,
13 ValidatedSamplingOverride,
14};
15
16pub fn intervention_plan_storage_bytes(
19 plan: &eredu_core::intervention::InterventionPlan,
20) -> Option<u64> {
21 (std::mem::size_of_val(plan) as u64).checked_add(storage::heap_bytes(plan)?)
22}
23pub(crate) mod storage;
24pub use snapshot::{
25 ManagedTextContinuation, SnapshotTokenController, TextBranchRequest, TextContinuationBranch,
26 TextContinuationSnapshot, TextSnapshotBackend, TextSnapshotError,
27};
28
29#[derive(Debug, Clone)]
32pub struct GenerationBoundary {
33 status: GenerationStatus,
34 prediction: u64,
35 finish_reason: Option<FinishReason>,
36}
37
38#[derive(Debug)]
41pub struct GenerationLifecycle {
42 boundary: GenerationBoundary,
43 epoch: u64,
44}
45
46impl Default for GenerationLifecycle {
47 fn default() -> Self {
48 Self {
49 boundary: GenerationBoundary {
50 status: GenerationStatus::Prepared,
51 prediction: 0,
52 finish_reason: None,
53 },
54 epoch: 0,
55 }
56 }
57}
58
59impl GenerationLifecycle {
60 pub fn status(&self) -> GenerationStatus {
62 self.boundary.status
63 }
64 pub fn next_prediction(&self) -> u64 {
66 self.boundary.prediction
67 }
68 pub fn epoch(&self) -> u64 {
70 self.epoch
71 }
72 pub fn finish_reason(&self) -> Option<FinishReason> {
74 self.boundary.finish_reason
75 }
76
77 fn invalid(&self, to: GenerationStatus) -> ExecutionControlError {
78 ExecutionControlError::Transition {
79 from: self.status(),
80 to,
81 }
82 }
83
84 pub fn begin_prediction(&mut self) -> Result<(), ExecutionControlError> {
86 if !matches!(
87 self.status(),
88 GenerationStatus::Prepared | GenerationStatus::Paused
89 ) {
90 return Err(self.invalid(GenerationStatus::Running));
91 }
92 self.boundary
93 .prediction
94 .checked_add(1)
95 .ok_or(ExecutionControlError::Overflow)?;
96 self.boundary.status = GenerationStatus::Running;
97 Ok(())
98 }
99
100 pub fn complete_prediction(
103 &mut self,
104 reason: Option<FinishReason>,
105 ) -> Result<(), ExecutionControlError> {
106 if self.status() != GenerationStatus::Running {
107 return Err(self.invalid(GenerationStatus::Paused));
108 }
109 self.boundary.prediction = self
110 .boundary
111 .prediction
112 .checked_add(1)
113 .ok_or(ExecutionControlError::Overflow)?;
114 self.boundary.finish_reason = reason;
115 self.boundary.status = match reason {
116 Some(FinishReason::Cancelled) => GenerationStatus::Cancelled,
117 Some(_) => GenerationStatus::Completed,
118 None => GenerationStatus::Paused,
119 };
120 Ok(())
121 }
122
123 pub fn pause(&mut self) -> Result<(), ExecutionControlError> {
125 if !matches!(
126 self.status(),
127 GenerationStatus::Prepared | GenerationStatus::Paused
128 ) {
129 return Err(self.invalid(GenerationStatus::Paused));
130 }
131 self.boundary.status = GenerationStatus::Paused;
132 Ok(())
133 }
134
135 pub fn cancel(&mut self) -> Result<(), ExecutionControlError> {
137 if !matches!(
138 self.status(),
139 GenerationStatus::Prepared | GenerationStatus::Paused
140 ) {
141 return Err(self.invalid(GenerationStatus::Cancelled));
142 }
143 self.boundary.status = GenerationStatus::Cancelled;
144 self.boundary.finish_reason = Some(FinishReason::Cancelled);
145 Ok(())
146 }
147
148 pub fn cancel_without_prediction(&mut self) -> Result<(), ExecutionControlError> {
152 if self.status() != GenerationStatus::Running {
153 return Err(self.invalid(GenerationStatus::Cancelled));
154 }
155 self.boundary.status = GenerationStatus::Cancelled;
156 self.boundary.finish_reason = Some(FinishReason::Cancelled);
157 Ok(())
158 }
159
160 pub fn fail(&mut self) {
163 self.boundary.status = GenerationStatus::Failed;
164 }
165
166 pub fn checkpoint(&self) -> Result<GenerationBoundary, ExecutionControlError> {
168 if !matches!(
169 self.status(),
170 GenerationStatus::Prepared | GenerationStatus::Paused | GenerationStatus::Completed
171 ) {
172 return Err(self.invalid(GenerationStatus::Paused));
173 }
174 Ok(self.boundary.clone())
175 }
176
177 pub fn validate_restore(&self) -> Result<(), ExecutionControlError> {
179 self.checkpoint()?;
180 self.epoch
181 .checked_add(1)
182 .ok_or(ExecutionControlError::Overflow)?;
183 Ok(())
184 }
185
186 pub fn restore(&mut self, saved: &GenerationBoundary) -> Result<(), ExecutionControlError> {
190 self.validate_restore()?;
191 self.epoch += 1;
192 self.boundary = saved.clone();
193 Ok(())
194 }
195
196 pub fn fork(saved: &GenerationBoundary) -> Self {
199 Self {
200 boundary: saved.clone(),
201 epoch: 0,
202 }
203 }
204}
205
206struct BudgetState {
207 limits: SnapshotLimits,
208 usage: SnapshotUsage,
209}
210
211#[derive(Clone)]
214pub struct SnapshotBudget(Rc<RefCell<BudgetState>>);
215
216impl SnapshotBudget {
217 pub fn new(limits: SnapshotLimits) -> Self {
219 Self(Rc::new(RefCell::new(BudgetState {
220 limits,
221 usage: SnapshotUsage::default(),
222 })))
223 }
224 pub fn usage(&self) -> SnapshotUsage {
226 self.0.borrow().usage
227 }
228 pub fn reserve(
232 &self,
233 kind: SnapshotResourceKind,
234 estimate: Option<SnapshotEstimate>,
235 ) -> Result<SnapshotReservation, ExecutionControlError> {
236 let estimate = estimate.ok_or(ExecutionControlError::UnknownEstimate)?;
237 let mut state = self.0.borrow_mut();
238 let add = |a: u64, b: u64| a.checked_add(b).ok_or(ExecutionControlError::Overflow);
239 let next = SnapshotUsage {
240 snapshots: add(
241 state.usage.snapshots,
242 u64::from(kind == SnapshotResourceKind::Snapshot),
243 )?,
244 branches: add(
245 state.usage.branches,
246 u64::from(kind == SnapshotResourceKind::Branch),
247 )?,
248 retained_bytes: add(state.usage.retained_bytes, estimate.retained_bytes)?,
249 cumulative_copy_bytes: add(state.usage.cumulative_copy_bytes, estimate.copy_bytes)?,
250 };
251 for (exceeded, name) in [
252 (
253 next.snapshots > state.limits.max_snapshots,
254 "snapshot count",
255 ),
256 (next.branches > state.limits.max_branches, "branch count"),
257 (
258 next.retained_bytes > state.limits.retained_bytes,
259 "retained bytes",
260 ),
261 (
262 next.cumulative_copy_bytes > state.limits.cumulative_copy_bytes,
263 "cumulative copy bytes",
264 ),
265 ] {
266 if exceeded {
267 return Err(ExecutionControlError::Limit(name));
268 }
269 }
270 state.usage = next;
271 Ok(SnapshotReservation {
272 lease: Rc::new(ReservationLease {
273 budget: self.clone(),
274 kind,
275 retained_bytes: estimate.retained_bytes,
276 }),
277 })
278 }
279}
280
281#[derive(Clone)]
285pub struct SnapshotReservation {
286 lease: Rc<ReservationLease>,
287}
288
289impl SnapshotReservation {
290 pub fn retained_bytes(&self) -> u64 {
292 self.lease.retained_bytes
293 }
294}
295
296struct ReservationLease {
297 budget: SnapshotBudget,
298 kind: SnapshotResourceKind,
299 retained_bytes: u64,
300}
301
302impl Drop for ReservationLease {
303 fn drop(&mut self) {
304 let mut state = self.budget.0.borrow_mut();
305 state.usage.snapshots -= u64::from(self.kind == SnapshotResourceKind::Snapshot);
306 state.usage.branches -= u64::from(self.kind == SnapshotResourceKind::Branch);
307 state.usage.retained_bytes -= self.retained_bytes;
308 }
309}
310
311#[cfg(test)]
312mod tests;