1use std::any::Any;
2use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use super::{VmError, VmValue};
8
9#[derive(Clone)]
16pub struct VmResourceHandle {
17 label: Arc<str>,
18 payload: Arc<dyn Any + Send + Sync>,
19}
20
21impl VmResourceHandle {
22 pub fn new<T>(label: impl Into<Arc<str>>, payload: T) -> Self
23 where
24 T: Any + Send + Sync,
25 {
26 Self {
27 label: label.into(),
28 payload: Arc::new(payload),
29 }
30 }
31
32 pub fn from_arc<T>(label: impl Into<Arc<str>>, payload: Arc<T>) -> Self
33 where
34 T: Any + Send + Sync,
35 {
36 Self {
37 label: label.into(),
38 payload,
39 }
40 }
41
42 pub fn label(&self) -> &str {
45 &self.label
46 }
47
48 pub fn downcast<T>(&self) -> Option<Arc<T>>
49 where
50 T: Any + Send + Sync,
51 {
52 Arc::clone(&self.payload).downcast::<T>().ok()
53 }
54
55 pub fn ptr_eq(&self, other: &Self) -> bool {
56 Arc::ptr_eq(&self.payload, &other.payload)
57 }
58}
59
60impl std::fmt::Debug for VmResourceHandle {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("VmResourceHandle")
63 .field("label", &self.label)
64 .finish_non_exhaustive()
65 }
66}
67
68type VmResourceRelease = Box<dyn FnOnce() -> Result<VmValue, String> + Send + 'static>;
69
70struct VmResourceGuardState {
71 release: Option<VmResourceRelease>,
72 result: Option<Result<VmValue, String>>,
73}
74
75pub struct VmResourceGuardHandle {
82 label: Arc<str>,
83 state: Mutex<VmResourceGuardState>,
84}
85
86impl VmResourceGuardHandle {
87 pub fn new(
89 label: impl Into<Arc<str>>,
90 release: impl FnOnce() -> Result<VmValue, String> + Send + 'static,
91 ) -> Self {
92 Self {
93 label: label.into(),
94 state: Mutex::new(VmResourceGuardState {
95 release: Some(Box::new(release)),
96 result: None,
97 }),
98 }
99 }
100
101 pub fn label(&self) -> &str {
103 &self.label
104 }
105
106 pub fn release(&self) -> Result<VmValue, VmError> {
108 let mut state = self.state.lock();
109 if let Some(result) = &state.result {
110 return result.clone().map_err(VmError::Runtime);
111 }
112 let release = state
113 .release
114 .take()
115 .expect("resource guard without callback or cached result");
116 let result = release();
117 state.result = Some(result.clone());
118 result.map_err(VmError::Runtime)
119 }
120
121 pub fn is_released(&self) -> bool {
123 self.state.lock().result.is_some()
124 }
125}
126
127impl std::fmt::Debug for VmResourceGuardHandle {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("VmResourceGuardHandle")
130 .field("label", &self.label)
131 .field("released", &self.is_released())
132 .finish()
133 }
134}
135
136impl Drop for VmResourceGuardHandle {
137 fn drop(&mut self) {
138 let _ = self.release();
139 }
140}
141
142#[cfg(test)]
143mod resource_guard_tests {
144 use std::sync::atomic::{AtomicUsize, Ordering};
145
146 use super::*;
147
148 #[test]
149 fn explicit_release_is_replayed_without_repeating_cleanup() {
150 let calls = Arc::new(AtomicUsize::new(0));
151 let observed = Arc::clone(&calls);
152 let guard = VmResourceGuardHandle::new("fixture", move || {
153 observed.fetch_add(1, Ordering::SeqCst);
154 Ok(VmValue::string("released"))
155 });
156
157 assert_eq!(guard.release().unwrap().display(), "released");
158 assert_eq!(guard.release().unwrap().display(), "released");
159 assert_eq!(calls.load(Ordering::SeqCst), 1);
160 drop(guard);
161 assert_eq!(calls.load(Ordering::SeqCst), 1);
162 }
163
164 #[test]
165 fn drop_runs_abandoned_resource_cleanup() {
166 let calls = Arc::new(AtomicUsize::new(0));
167 let observed = Arc::clone(&calls);
168 let guard = VmResourceGuardHandle::new("fixture", move || {
169 observed.fetch_add(1, Ordering::SeqCst);
170 Ok(VmValue::Nil)
171 });
172
173 drop(guard);
174 assert_eq!(calls.load(Ordering::SeqCst), 1);
175 }
176}
177
178pub type VmJoinHandle = tokio::task::JoinHandle<Result<(VmValue, String), VmError>>;
180
181pub struct VmTaskHandle {
183 pub handle: VmJoinHandle,
184 pub cancel_token: Arc<AtomicBool>,
186 pub wait_task_id: String,
188}
189
190#[derive(Debug, Clone)]
192pub struct VmChannelHandle {
193 pub name: Arc<str>,
194 pub sender: Arc<tokio::sync::mpsc::Sender<VmValue>>,
195 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
196 pub close: Arc<VmChannelCloseState>,
197}
198
199#[derive(Debug)]
200pub struct VmChannelCloseState {
201 closed: AtomicBool,
202 signal: tokio::sync::watch::Sender<bool>,
203}
204
205impl VmChannelCloseState {
206 pub(crate) fn open() -> Self {
207 let (signal, _) = tokio::sync::watch::channel(false);
208 Self {
209 closed: AtomicBool::new(false),
210 signal,
211 }
212 }
213
214 pub(crate) fn close(&self) -> bool {
215 if self.closed.swap(true, Ordering::SeqCst) {
216 return false;
217 }
218 self.signal.send_replace(true);
219 true
220 }
221
222 pub(crate) fn is_closed(&self) -> bool {
223 self.closed.load(Ordering::SeqCst)
224 }
225
226 pub(crate) fn subscribe(&self) -> tokio::sync::watch::Receiver<bool> {
227 self.signal.subscribe()
228 }
229}
230
231impl VmChannelHandle {
232 pub(crate) fn close(&self) -> bool {
233 self.close.close()
234 }
235
236 pub(crate) fn is_closed(&self) -> bool {
237 self.close.is_closed()
238 }
239
240 pub(crate) fn subscribe_closed(&self) -> tokio::sync::watch::Receiver<bool> {
241 self.close.subscribe()
242 }
243}
244
245#[derive(Debug, Clone)]
247pub struct VmAtomicHandle {
248 pub value: Arc<AtomicI64>,
249}
250
251#[derive(Clone)]
253pub struct VmRngHandle {
254 pub rng: Arc<Mutex<rand::rngs::StdRng>>,
255}
256
257impl std::fmt::Debug for VmRngHandle {
258 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 f.write_str("VmRngHandle { .. }")
260 }
261}
262
263#[derive(Debug, Clone)]
278pub struct VmVerdictReceipt {
279 pub artifact_id: Arc<str>,
282 pub content_hash: Arc<str>,
285 pub plan_id: Arc<str>,
287 pub workspace_hash: Arc<str>,
289 pub command_hash: Arc<str>,
291 pub passed: u32,
294 pub total: u32,
295 pub execution_scope: Arc<str>,
300 pub subject: Option<Arc<str>>,
303}
304
305#[derive(Debug, Clone)]
307pub struct VmSyncPermitHandle {
308 pub(crate) lease: Arc<crate::synchronization::VmSyncLease>,
309}
310
311impl VmSyncPermitHandle {
312 pub(crate) fn release(&self) -> bool {
313 self.lease.release()
314 }
315
316 pub(crate) fn kind(&self) -> &str {
317 self.lease.kind()
318 }
319
320 pub(crate) fn key(&self) -> &str {
321 self.lease.key()
322 }
323
324 pub(crate) fn permits(&self) -> u32 {
325 self.lease.permits()
326 }
327
328 pub(crate) fn is_released(&self) -> bool {
329 self.lease.is_released()
330 }
331
332 pub(crate) fn same_lease(&self, other: &Self) -> bool {
333 Arc::ptr_eq(&self.lease, &other.lease)
334 }
335}
336
337#[derive(Debug, Clone, Copy)]
349pub struct VmRange {
350 pub start: i64,
351 pub end: i64,
352 pub inclusive: bool,
353}
354
355impl VmRange {
356 pub fn len(&self) -> i64 {
366 if self.inclusive {
367 if self.start > self.end {
368 0
369 } else {
370 self.end.saturating_sub(self.start).saturating_add(1)
371 }
372 } else if self.start >= self.end {
373 0
374 } else {
375 self.end.saturating_sub(self.start)
376 }
377 }
378
379 pub fn is_empty(&self) -> bool {
380 self.len() == 0
381 }
382
383 pub fn get(&self, idx: i64) -> Option<i64> {
387 if idx < 0 || idx >= self.len() {
388 None
389 } else {
390 self.start.checked_add(idx)
391 }
392 }
393
394 pub fn first(&self) -> Option<i64> {
396 if self.is_empty() {
397 None
398 } else {
399 Some(self.start)
400 }
401 }
402
403 pub fn last(&self) -> Option<i64> {
405 if self.is_empty() {
406 None
407 } else if self.inclusive {
408 Some(self.end)
409 } else {
410 Some(self.end - 1)
411 }
412 }
413
414 pub fn contains(&self, v: i64) -> bool {
416 if self.is_empty() {
417 return false;
418 }
419 if self.inclusive {
420 v >= self.start && v <= self.end
421 } else {
422 v >= self.start && v < self.end
423 }
424 }
425
426 pub fn to_vec(&self) -> Vec<VmValue> {
433 let len = self.len();
434 if len <= 0 {
435 return Vec::new();
436 }
437 let cap = len as usize;
438 let mut out = Vec::with_capacity(cap);
439 for i in 0..len {
440 match self.start.checked_add(i) {
441 Some(v) => out.push(VmValue::Int(v)),
442 None => break,
443 }
444 }
445 out
446 }
447}
448
449#[derive(Debug, Clone)]
452pub struct VmGenerator {
453 pub done: Arc<AtomicBool>,
455 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Result<VmValue, VmError>>>>,
459}
460
461impl VmGenerator {
462 pub(crate) fn is_done(&self) -> bool {
463 self.done.load(Ordering::Relaxed)
464 }
465
466 pub(crate) fn mark_done(&self) {
467 self.done.store(true, Ordering::Relaxed);
468 }
469}
470
471#[derive(Debug, Clone)]
473pub struct VmStream {
474 pub done: Arc<AtomicBool>,
476 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Result<VmValue, VmError>>>>,
478 pub cancel: Option<VmStreamCancel>,
480}
481
482impl VmStream {
483 pub(crate) fn is_done(&self) -> bool {
484 self.done.load(Ordering::Relaxed)
485 }
486
487 pub(crate) fn mark_done(&self) {
488 self.done.store(true, Ordering::Relaxed);
489 }
490}
491
492#[derive(Clone)]
493pub struct VmStreamCancel {
494 sender: Arc<tokio::sync::watch::Sender<bool>>,
495}
496
497impl VmStreamCancel {
498 pub fn new() -> Self {
499 let (sender, _receiver) = tokio::sync::watch::channel(false);
500 Self {
501 sender: Arc::new(sender),
502 }
503 }
504
505 pub fn cancel(&self) {
506 let _ = self.sender.send(true);
507 }
508
509 pub fn subscribe(&self) -> tokio::sync::watch::Receiver<bool> {
510 self.sender.subscribe()
511 }
512}
513
514impl Default for VmStreamCancel {
515 fn default() -> Self {
516 Self::new()
517 }
518}
519
520impl std::fmt::Debug for VmStreamCancel {
521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522 f.debug_struct("VmStreamCancel")
523 .field("cancelled", &*self.sender.borrow())
524 .finish()
525 }
526}
527
528impl VmStream {
529 pub(crate) fn cancel(&self) {
530 if let Some(cancel) = &self.cancel {
531 cancel.cancel();
532 }
533 }
534}