sema_core/runtime/
resource.rs1use 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}