1use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
2use std::sync::Arc;
3
4use parking_lot::Mutex;
5
6use super::{VmError, VmValue};
7
8type VmResourceRelease = Box<dyn FnOnce() -> Result<VmValue, String> + Send + 'static>;
9
10struct VmResourceGuardState {
11 release: Option<VmResourceRelease>,
12 result: Option<Result<VmValue, String>>,
13}
14
15pub struct VmResourceGuardHandle {
22 label: Arc<str>,
23 state: Mutex<VmResourceGuardState>,
24}
25
26impl VmResourceGuardHandle {
27 pub fn new(
29 label: impl Into<Arc<str>>,
30 release: impl FnOnce() -> Result<VmValue, String> + Send + 'static,
31 ) -> Self {
32 Self {
33 label: label.into(),
34 state: Mutex::new(VmResourceGuardState {
35 release: Some(Box::new(release)),
36 result: None,
37 }),
38 }
39 }
40
41 pub fn label(&self) -> &str {
43 &self.label
44 }
45
46 pub fn release(&self) -> Result<VmValue, VmError> {
48 let mut state = self.state.lock();
49 if let Some(result) = &state.result {
50 return result.clone().map_err(VmError::Runtime);
51 }
52 let release = state
53 .release
54 .take()
55 .expect("resource guard without callback or cached result");
56 let result = release();
57 state.result = Some(result.clone());
58 result.map_err(VmError::Runtime)
59 }
60
61 pub fn is_released(&self) -> bool {
63 self.state.lock().result.is_some()
64 }
65}
66
67impl std::fmt::Debug for VmResourceGuardHandle {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("VmResourceGuardHandle")
70 .field("label", &self.label)
71 .field("released", &self.is_released())
72 .finish()
73 }
74}
75
76impl Drop for VmResourceGuardHandle {
77 fn drop(&mut self) {
78 let _ = self.release();
79 }
80}
81
82#[cfg(test)]
83mod resource_guard_tests {
84 use std::sync::atomic::{AtomicUsize, Ordering};
85
86 use super::*;
87
88 #[test]
89 fn explicit_release_is_replayed_without_repeating_cleanup() {
90 let calls = Arc::new(AtomicUsize::new(0));
91 let observed = Arc::clone(&calls);
92 let guard = VmResourceGuardHandle::new("fixture", move || {
93 observed.fetch_add(1, Ordering::SeqCst);
94 Ok(VmValue::string("released"))
95 });
96
97 assert_eq!(guard.release().unwrap().display(), "released");
98 assert_eq!(guard.release().unwrap().display(), "released");
99 assert_eq!(calls.load(Ordering::SeqCst), 1);
100 drop(guard);
101 assert_eq!(calls.load(Ordering::SeqCst), 1);
102 }
103
104 #[test]
105 fn drop_runs_abandoned_resource_cleanup() {
106 let calls = Arc::new(AtomicUsize::new(0));
107 let observed = Arc::clone(&calls);
108 let guard = VmResourceGuardHandle::new("fixture", move || {
109 observed.fetch_add(1, Ordering::SeqCst);
110 Ok(VmValue::Nil)
111 });
112
113 drop(guard);
114 assert_eq!(calls.load(Ordering::SeqCst), 1);
115 }
116}
117
118pub type VmJoinHandle = tokio::task::JoinHandle<Result<(VmValue, String), VmError>>;
120
121pub struct VmTaskHandle {
123 pub handle: VmJoinHandle,
124 pub cancel_token: Arc<AtomicBool>,
126 pub wait_task_id: String,
128}
129
130#[derive(Debug, Clone)]
132pub struct VmChannelHandle {
133 pub name: Arc<str>,
134 pub sender: Arc<tokio::sync::mpsc::Sender<VmValue>>,
135 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
136 pub close: Arc<VmChannelCloseState>,
137}
138
139#[derive(Debug)]
140pub struct VmChannelCloseState {
141 closed: AtomicBool,
142 signal: tokio::sync::watch::Sender<bool>,
143}
144
145impl VmChannelCloseState {
146 pub(crate) fn open() -> Self {
147 let (signal, _) = tokio::sync::watch::channel(false);
148 Self {
149 closed: AtomicBool::new(false),
150 signal,
151 }
152 }
153
154 pub(crate) fn close(&self) -> bool {
155 if self.closed.swap(true, Ordering::SeqCst) {
156 return false;
157 }
158 self.signal.send_replace(true);
159 true
160 }
161
162 pub(crate) fn is_closed(&self) -> bool {
163 self.closed.load(Ordering::SeqCst)
164 }
165
166 pub(crate) fn subscribe(&self) -> tokio::sync::watch::Receiver<bool> {
167 self.signal.subscribe()
168 }
169}
170
171impl VmChannelHandle {
172 pub(crate) fn close(&self) -> bool {
173 self.close.close()
174 }
175
176 pub(crate) fn is_closed(&self) -> bool {
177 self.close.is_closed()
178 }
179
180 pub(crate) fn subscribe_closed(&self) -> tokio::sync::watch::Receiver<bool> {
181 self.close.subscribe()
182 }
183}
184
185#[derive(Debug, Clone)]
187pub struct VmAtomicHandle {
188 pub value: Arc<AtomicI64>,
189}
190
191#[derive(Clone)]
193pub struct VmRngHandle {
194 pub rng: Arc<Mutex<rand::rngs::StdRng>>,
195}
196
197impl std::fmt::Debug for VmRngHandle {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 f.write_str("VmRngHandle { .. }")
200 }
201}
202
203#[derive(Debug, Clone)]
218pub struct VmVerdictReceipt {
219 pub artifact_id: Arc<str>,
222 pub content_hash: Arc<str>,
225 pub plan_id: Arc<str>,
227 pub workspace_hash: Arc<str>,
229 pub command_hash: Arc<str>,
231 pub passed: u32,
234 pub total: u32,
235 pub execution_scope: Arc<str>,
240 pub subject: Option<Arc<str>>,
243}
244
245#[derive(Debug, Clone)]
247pub struct VmSyncPermitHandle {
248 pub(crate) lease: Arc<crate::synchronization::VmSyncLease>,
249}
250
251impl VmSyncPermitHandle {
252 pub(crate) fn release(&self) -> bool {
253 self.lease.release()
254 }
255
256 pub(crate) fn kind(&self) -> &str {
257 self.lease.kind()
258 }
259
260 pub(crate) fn key(&self) -> &str {
261 self.lease.key()
262 }
263
264 pub(crate) fn permits(&self) -> u32 {
265 self.lease.permits()
266 }
267
268 pub(crate) fn is_released(&self) -> bool {
269 self.lease.is_released()
270 }
271
272 pub(crate) fn same_lease(&self, other: &Self) -> bool {
273 Arc::ptr_eq(&self.lease, &other.lease)
274 }
275}
276
277#[derive(Debug, Clone, Copy)]
289pub struct VmRange {
290 pub start: i64,
291 pub end: i64,
292 pub inclusive: bool,
293}
294
295impl VmRange {
296 pub fn len(&self) -> i64 {
306 if self.inclusive {
307 if self.start > self.end {
308 0
309 } else {
310 self.end.saturating_sub(self.start).saturating_add(1)
311 }
312 } else if self.start >= self.end {
313 0
314 } else {
315 self.end.saturating_sub(self.start)
316 }
317 }
318
319 pub fn is_empty(&self) -> bool {
320 self.len() == 0
321 }
322
323 pub fn get(&self, idx: i64) -> Option<i64> {
327 if idx < 0 || idx >= self.len() {
328 None
329 } else {
330 self.start.checked_add(idx)
331 }
332 }
333
334 pub fn first(&self) -> Option<i64> {
336 if self.is_empty() {
337 None
338 } else {
339 Some(self.start)
340 }
341 }
342
343 pub fn last(&self) -> Option<i64> {
345 if self.is_empty() {
346 None
347 } else if self.inclusive {
348 Some(self.end)
349 } else {
350 Some(self.end - 1)
351 }
352 }
353
354 pub fn contains(&self, v: i64) -> bool {
356 if self.is_empty() {
357 return false;
358 }
359 if self.inclusive {
360 v >= self.start && v <= self.end
361 } else {
362 v >= self.start && v < self.end
363 }
364 }
365
366 pub fn to_vec(&self) -> Vec<VmValue> {
373 let len = self.len();
374 if len <= 0 {
375 return Vec::new();
376 }
377 let cap = len as usize;
378 let mut out = Vec::with_capacity(cap);
379 for i in 0..len {
380 match self.start.checked_add(i) {
381 Some(v) => out.push(VmValue::Int(v)),
382 None => break,
383 }
384 }
385 out
386 }
387}
388
389#[derive(Debug, Clone)]
392pub struct VmGenerator {
393 pub done: Arc<AtomicBool>,
395 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Result<VmValue, VmError>>>>,
399}
400
401impl VmGenerator {
402 pub(crate) fn is_done(&self) -> bool {
403 self.done.load(Ordering::Relaxed)
404 }
405
406 pub(crate) fn mark_done(&self) {
407 self.done.store(true, Ordering::Relaxed);
408 }
409}
410
411#[derive(Debug, Clone)]
413pub struct VmStream {
414 pub done: Arc<AtomicBool>,
416 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Result<VmValue, VmError>>>>,
418 pub cancel: Option<VmStreamCancel>,
420}
421
422impl VmStream {
423 pub(crate) fn is_done(&self) -> bool {
424 self.done.load(Ordering::Relaxed)
425 }
426
427 pub(crate) fn mark_done(&self) {
428 self.done.store(true, Ordering::Relaxed);
429 }
430}
431
432#[derive(Clone)]
433pub struct VmStreamCancel {
434 sender: Arc<tokio::sync::watch::Sender<bool>>,
435}
436
437impl VmStreamCancel {
438 pub fn new() -> Self {
439 let (sender, _receiver) = tokio::sync::watch::channel(false);
440 Self {
441 sender: Arc::new(sender),
442 }
443 }
444
445 pub fn cancel(&self) {
446 let _ = self.sender.send(true);
447 }
448
449 pub fn subscribe(&self) -> tokio::sync::watch::Receiver<bool> {
450 self.sender.subscribe()
451 }
452}
453
454impl Default for VmStreamCancel {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460impl std::fmt::Debug for VmStreamCancel {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 f.debug_struct("VmStreamCancel")
463 .field("cancelled", &*self.sender.borrow())
464 .finish()
465 }
466}
467
468impl VmStream {
469 pub(crate) fn cancel(&self) {
470 if let Some(cancel) = &self.cancel {
471 cancel.cancel();
472 }
473 }
474}