vyre_driver/backend/
resource.rs1use std::sync::atomic::{AtomicU64, Ordering};
4
5use crate::accounting::checked_atomic_next_u64_with_order;
6use crate::backend::error::BackendError;
7
8static NEXT_RESIDENT_OWNER: AtomicU64 = AtomicU64::new(1);
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
22pub struct ResidentOwner(u64);
23
24impl ResidentOwner {
25 pub fn new() -> Result<Self, BackendError> {
33 let id = checked_atomic_next_u64_with_order(
34 &NEXT_RESIDENT_OWNER,
35 Ordering::Acquire,
36 Ordering::AcqRel,
37 Ordering::Acquire,
38 |_| {
39 BackendError::InvalidProgram {
40 fix: "Fix: the process exhausted backend instance identities for resident buffers. Restart the process instead of reusing an identity, which would let a stale resident handle resolve against a live buffer."
41 .to_string(),
42 }
43 },
44 )?;
45 Ok(Self(id))
46 }
47
48 #[must_use]
50 pub fn get(self) -> u64 {
51 self.0
52 }
53
54 #[must_use]
56 pub fn handle(self, id: u64) -> ResidentHandle {
57 ResidentHandle { owner: self, id }
58 }
59
60 pub fn resolve(self, handle: ResidentHandle, context: &str) -> Result<u64, BackendError> {
72 if handle.owner != self {
73 return Err(BackendError::InvalidProgram {
74 fix: format!(
75 "Fix: {context} received resident handle {} owned by backend instance {}, but this instance is {}. A resident handle is only valid on the backend instance that allocated it; reallocate and re-upload the buffer on this instance, or keep the original instance alive for as long as the handle is held.",
76 handle.id,
77 handle.owner.0,
78 self.0
79 ),
80 });
81 }
82 Ok(handle.id)
83 }
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
94pub struct ResidentHandle {
95 owner: ResidentOwner,
96 id: u64,
97}
98
99impl ResidentHandle {
100 #[must_use]
102 pub fn owner(self) -> ResidentOwner {
103 self.owner
104 }
105
106 #[must_use]
111 pub fn id(self) -> u64 {
112 self.id
113 }
114}
115
116impl std::fmt::Display for ResidentHandle {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 write!(f, "{} on backend instance {}", self.id, self.owner.0)
124 }
125}
126
127#[derive(Clone, Debug, Eq, PartialEq)]
129pub enum Resource {
130 Borrowed(Vec<u8>),
132 Resident(ResidentHandle),
134}
135
136impl Default for Resource {
137 fn default() -> Self {
138 Resource::Borrowed(Vec::new())
139 }
140}
141
142impl From<Vec<u8>> for Resource {
143 fn from(bytes: Vec<u8>) -> Self {
144 Self::Borrowed(bytes)
145 }
146}
147
148impl From<ResidentHandle> for Resource {
149 fn from(handle: ResidentHandle) -> Self {
150 Self::Resident(handle)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn distinct_owners_refuse_each_others_handles() {
160 let first = ResidentOwner::new().expect("owner ids are available");
161 let second = ResidentOwner::new().expect("owner ids are available");
162 assert_ne!(first, second);
163
164 let handle = first.handle(7);
165 assert_eq!(
166 first.resolve(handle, "test resolve").expect("own handle"),
167 7
168 );
169
170 let error = second.resolve(handle, "test resolve").expect_err(
171 "Fix: a foreign resident handle must be refused, never resolved by bare id",
172 );
173 let BackendError::InvalidProgram { fix } = error else {
174 panic!("Fix: foreign resident handle refusal must be BackendError::InvalidProgram");
175 };
176 assert!(
177 fix.contains("owned by backend instance") && fix.contains("Fix: "),
178 "Fix: foreign-handle refusal must name the owning instance and carry actionable text, got {fix}"
179 );
180 }
181
182 #[test]
183 fn same_id_in_two_namespaces_stays_distinct() {
184 let first = ResidentOwner::new().expect("owner ids are available");
185 let second = ResidentOwner::new().expect("owner ids are available");
186 assert_ne!(first.handle(1), second.handle(1));
187 assert_eq!(first.handle(1), first.handle(1));
188 }
189}