Skip to main content

sema_core/runtime/
resource.rs

1use std::num::NonZeroU64;
2use std::time::Duration;
3
4use crate::cycle::GcEdge;
5
6use super::Trace;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
9#[error("quarantine hard deadline must be nonzero")]
10pub struct InvalidQuarantineBound;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum QuarantineBoundDescriptor {
14    HardDeadline(Duration),
15    FiniteWork {
16        kind: &'static str,
17        maximum_units: NonZeroU64,
18    },
19}
20
21pub struct QuarantineBound(QuarantineBoundDescriptor);
22
23impl QuarantineBound {
24    pub fn hard_deadline(deadline: Duration) -> Result<Self, InvalidQuarantineBound> {
25        if deadline.is_zero() {
26            return Err(InvalidQuarantineBound);
27        }
28        Ok(Self(QuarantineBoundDescriptor::HardDeadline(deadline)))
29    }
30
31    pub fn finite_work(kind: &'static str, maximum_units: NonZeroU64) -> Self {
32        Self(QuarantineBoundDescriptor::FiniteWork {
33            kind,
34            maximum_units,
35        })
36    }
37
38    pub fn descriptor(&self) -> QuarantineBoundDescriptor {
39        self.0
40    }
41
42    pub fn hard_deadline_value(&self) -> Option<Duration> {
43        match self.0 {
44            QuarantineBoundDescriptor::HardDeadline(value) => Some(value),
45            QuarantineBoundDescriptor::FiniteWork { .. } => None,
46        }
47    }
48
49    pub fn finite_work_value(&self) -> Option<(&'static str, NonZeroU64)> {
50        match self.0 {
51            QuarantineBoundDescriptor::HardDeadline(_) => None,
52            QuarantineBoundDescriptor::FiniteWork {
53                kind,
54                maximum_units,
55            } => Some((kind, maximum_units)),
56        }
57    }
58}
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub enum CancelDisposition {
62    Reaped,
63    PendingReap,
64}
65
66#[derive(Debug, thiserror::Error)]
67#[error("resource cancellation hook failed: {message}")]
68pub struct CancelHookError {
69    message: String,
70}
71
72impl CancelHookError {
73    pub fn new(message: impl Into<String>) -> Self {
74        Self {
75            message: message.into(),
76        }
77    }
78
79    pub fn message(&self) -> &str {
80        &self.message
81    }
82}
83
84pub trait CancelHook: Trace {
85    fn cancel(&mut self) -> Result<CancelDisposition, CancelHookError>;
86    fn reap(&mut self) -> Result<CancelDisposition, CancelHookError>;
87}
88
89impl Trace for ResourceClass {
90    fn trace(&self, sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
91        match &self.0 {
92            ResourceClassInner::Interruptible { hook, .. } => hook.trace(sink),
93            ResourceClassInner::QuarantinedBounded(_) => true,
94        }
95    }
96}
97
98pub struct InterruptibleResource {
99    kind: &'static str,
100    hook: Box<dyn CancelHook>,
101}
102
103impl InterruptibleResource {
104    pub fn new(kind: &'static str, hook: Box<dyn CancelHook>) -> Self {
105        Self { kind, hook }
106    }
107
108    pub fn kind(&self) -> &'static str {
109        self.kind
110    }
111
112    pub(crate) fn into_parts(self) -> (&'static str, Box<dyn CancelHook>) {
113        (self.kind, self.hook)
114    }
115}
116
117enum ResourceClassInner {
118    Interruptible {
119        kind: &'static str,
120        hook: Box<dyn CancelHook>,
121        cancel_attempted: bool,
122    },
123    QuarantinedBounded(QuarantineBound),
124}
125
126pub struct ResourceClass(ResourceClassInner);
127
128impl ResourceClass {
129    pub(crate) fn interruptible(kind: &'static str, hook: Box<dyn CancelHook>) -> Self {
130        Self(ResourceClassInner::Interruptible {
131            kind,
132            hook,
133            cancel_attempted: false,
134        })
135    }
136
137    pub(crate) fn quarantined(bound: QuarantineBound) -> Self {
138        Self(ResourceClassInner::QuarantinedBounded(bound))
139    }
140
141    pub fn kind(&self) -> &'static str {
142        match &self.0 {
143            ResourceClassInner::Interruptible { kind, .. } => kind,
144            ResourceClassInner::QuarantinedBounded(_) => "quarantined-bounded",
145        }
146    }
147
148    pub fn bound(&self) -> Option<QuarantineBoundDescriptor> {
149        match &self.0 {
150            ResourceClassInner::Interruptible { .. } => None,
151            ResourceClassInner::QuarantinedBounded(bound) => Some(bound.descriptor()),
152        }
153    }
154
155    pub fn cancel(&mut self) -> Option<Result<CancelDisposition, CancelHookError>> {
156        match &mut self.0 {
157            ResourceClassInner::Interruptible {
158                hook,
159                cancel_attempted,
160                ..
161            } => {
162                if *cancel_attempted {
163                    return None;
164                }
165                *cancel_attempted = true;
166                Some(hook.cancel())
167            }
168            ResourceClassInner::QuarantinedBounded(_) => None,
169        }
170    }
171
172    pub fn reap(&mut self) -> Option<Result<CancelDisposition, CancelHookError>> {
173        match &mut self.0 {
174            ResourceClassInner::Interruptible { hook, .. } => Some(hook.reap()),
175            ResourceClassInner::QuarantinedBounded(_) => None,
176        }
177    }
178}