subetha_cxc/
virtual_endpoint.rs1use std::cell::Cell;
43use std::collections::HashMap;
44use std::marker::PhantomData;
45use std::net::SocketAddr;
46use std::sync::atomic::{AtomicU64, Ordering};
47use std::sync::{Arc, RwLock};
48
49use crate::locale_adaptive_ring::LocaleAdaptiveRing;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub struct EndpointId(pub u64);
55
56#[derive(Clone)]
58pub enum EndpointTarget {
59 Local(Arc<LocaleAdaptiveRing>),
62 Remote(RemoteEndpoint),
67}
68
69#[derive(Clone, Debug)]
71pub struct RemoteEndpoint {
72 pub server_addr: SocketAddr,
74 pub server_name: String,
76}
77
78pub struct VirtualEndpointRegistry {
82 table: RwLock<HashMap<EndpointId, EndpointTarget>>,
83 generation: AtomicU64,
85}
86
87impl VirtualEndpointRegistry {
88 pub fn new() -> Self {
90 Self {
91 table: RwLock::new(HashMap::new()),
92 generation: AtomicU64::new(0),
93 }
94 }
95
96 pub fn bind(&self, id: EndpointId, target: EndpointTarget) {
98 let mut table = self.table.write().expect("registry table poisoned");
99 table.insert(id, target);
100 self.generation.fetch_add(1, Ordering::AcqRel);
101 }
102
103 pub fn unbind(&self, id: EndpointId) -> Option<EndpointTarget> {
106 let mut table = self.table.write().expect("registry table poisoned");
107 let prior = table.remove(&id);
108 if prior.is_some() {
109 self.generation.fetch_add(1, Ordering::AcqRel);
110 }
111 prior
112 }
113
114 pub fn lookup(&self, id: EndpointId) -> Option<EndpointTarget> {
117 let table = self.table.read().expect("registry table poisoned");
118 table.get(&id).cloned()
119 }
120
121 pub fn generation(&self) -> u64 {
124 self.generation.load(Ordering::Acquire)
125 }
126
127 pub fn len(&self) -> usize {
129 self.table.read().expect("registry table poisoned").len()
130 }
131
132 pub fn is_empty(&self) -> bool { self.len() == 0 }
134}
135
136impl Default for VirtualEndpointRegistry {
137 fn default() -> Self { Self::new() }
138}
139
140pub struct VirtualEndpoint {
145 registry: Arc<VirtualEndpointRegistry>,
146 id: EndpointId,
147}
148
149impl VirtualEndpoint {
150 pub fn bind(
152 registry: Arc<VirtualEndpointRegistry>,
153 id: EndpointId,
154 target: EndpointTarget,
155 ) -> Self {
156 registry.bind(id, target);
157 Self { registry, id }
158 }
159
160 pub fn attach(
163 registry: Arc<VirtualEndpointRegistry>,
164 id: EndpointId,
165 ) -> Option<Self> {
166 registry.lookup(id)?;
167 Some(Self { registry, id })
168 }
169
170 pub fn id(&self) -> EndpointId { self.id }
172
173 pub fn pin_current_target(&self) -> Option<PinnedEndpoint<'_>> {
176 let captured_gen = self.registry.generation();
177 let target = self.registry.lookup(self.id)?;
178 Some(PinnedEndpoint {
179 registry: &self.registry,
180 id: self.id,
181 pinned_generation: captured_gen,
182 target,
183 _not_sync: PhantomData,
184 })
185 }
186}
187
188pub struct PinnedEndpoint<'a> {
192 registry: &'a VirtualEndpointRegistry,
193 id: EndpointId,
194 pinned_generation: u64,
195 target: EndpointTarget,
196 _not_sync: PhantomData<Cell<()>>,
197}
198
199impl<'a> PinnedEndpoint<'a> {
200 pub fn id(&self) -> EndpointId { self.id }
202
203 pub fn pinned_generation(&self) -> u64 { self.pinned_generation }
205
206 pub fn is_still_valid(&self) -> bool {
217 self.registry.generation() == self.pinned_generation
218 }
219
220 pub fn as_local(&self) -> Option<&Arc<LocaleAdaptiveRing>> {
224 match &self.target {
225 EndpointTarget::Local(ring) => Some(ring),
226 _ => None,
227 }
228 }
229
230 pub fn as_remote(&self) -> Option<&RemoteEndpoint> {
233 match &self.target {
234 EndpointTarget::Remote(remote) => Some(remote),
235 _ => None,
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 fn tmp(name: &str) -> std::path::PathBuf {
245 let mut p = std::env::temp_dir();
246 let pid = std::process::id();
247 let nonce = std::time::SystemTime::now()
248 .duration_since(std::time::UNIX_EPOCH)
249 .map(|d| d.as_nanos())
250 .unwrap_or(0);
251 p.push(format!("ve_{pid}_{nonce}_{name}"));
252 p
253 }
254
255 fn local_target(name: &str) -> EndpointTarget {
256 let ring = Arc::new(
257 LocaleAdaptiveRing::create(tmp(name), 1, 1, 64)
258 .expect("locale ring create"),
259 );
260 ring.register_producer().expect("p");
261 ring.register_consumer().expect("c");
262 EndpointTarget::Local(ring)
263 }
264
265 #[test]
266 fn bind_then_lookup() {
267 let registry = Arc::new(VirtualEndpointRegistry::new());
268 let id = EndpointId(42);
269 let target = local_target("bind");
270 registry.bind(id, target);
271 assert!(matches!(
272 registry.lookup(id),
273 Some(EndpointTarget::Local(_)),
274 ));
275 assert_eq!(registry.len(), 1);
276 }
277
278 #[test]
279 fn rebind_bumps_generation() {
280 let registry = Arc::new(VirtualEndpointRegistry::new());
281 let id = EndpointId(7);
282 let gen_before = registry.generation();
283 registry.bind(id, local_target("rebind_a"));
284 let gen_after_first = registry.generation();
285 assert!(gen_after_first > gen_before);
286 registry.bind(id, local_target("rebind_b"));
287 assert!(registry.generation() > gen_after_first);
288 }
289
290 #[test]
291 fn pin_invalidates_on_rebind() {
292 let registry = Arc::new(VirtualEndpointRegistry::new());
293 let id = EndpointId(99);
294 let endpoint = VirtualEndpoint::bind(
295 registry.clone(), id, local_target("pin_inv_a"),
296 );
297 let pin = endpoint.pin_current_target().expect("pinned");
298 assert!(pin.is_still_valid());
299 assert!(pin.as_local().is_some());
300 assert!(pin.as_remote().is_none());
301
302 registry.bind(id, local_target("pin_inv_b"));
304 assert!(!pin.is_still_valid(),
305 "pin must invalidate after rebind");
306
307 let pin2 = endpoint.pin_current_target().expect("re-pinned");
309 assert!(pin2.is_still_valid());
310 }
311
312 #[test]
313 fn pin_invalidates_on_unbind() {
314 let registry = Arc::new(VirtualEndpointRegistry::new());
315 let id = EndpointId(123);
316 let endpoint = VirtualEndpoint::bind(
317 registry.clone(), id, local_target("unbind_test"),
318 );
319 let pin = endpoint.pin_current_target().expect("pinned");
320 registry.unbind(id);
321 assert!(!pin.is_still_valid());
322 assert!(endpoint.pin_current_target().is_none(),
323 "after unbind, re-pin returns None");
324 }
325
326 #[test]
327 fn attach_returns_none_for_unbound() {
328 let registry = Arc::new(VirtualEndpointRegistry::new());
329 let id = EndpointId(404);
330 assert!(VirtualEndpoint::attach(registry, id).is_none());
331 }
332
333 #[test]
334 fn remote_target_pins_correctly() {
335 let registry = Arc::new(VirtualEndpointRegistry::new());
336 let id = EndpointId(8080);
337 registry.bind(id, EndpointTarget::Remote(RemoteEndpoint {
338 server_addr: "127.0.0.1:8080".parse().unwrap(),
339 server_name: "remote.example".to_string(),
340 }));
341 let endpoint = VirtualEndpoint::attach(registry, id)
342 .expect("attached");
343 let pin = endpoint.pin_current_target().expect("pinned");
344 assert!(pin.as_local().is_none());
345 let remote = pin.as_remote().expect("remote target");
346 assert_eq!(remote.server_name, "remote.example");
347 }
348
349 #[test]
350 fn pin_chains_into_locale_axis() {
351 let registry = Arc::new(VirtualEndpointRegistry::new());
352 let id = EndpointId(1);
353 let target = local_target("chain");
354 registry.bind(id, target);
355 let endpoint = VirtualEndpoint::attach(registry, id).expect("attached");
356
357 let pin_endpoint = endpoint.pin_current_target().expect("pinned");
358 let ring = pin_endpoint.as_local().expect("local target");
359 let pin_locale = ring.pin_current_locale();
360 let adaptive = pin_locale.as_anon().expect("anon locale");
361 let pin_shape = adaptive.pin_current_shape();
362 assert_eq!(pin_shape.shape(), crate::RingShape::Spsc);
363 assert!(pin_endpoint.is_still_valid()
364 && pin_locale.is_still_valid()
365 && pin_shape.is_still_valid());
366 }
367}