1use std::sync::Arc;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use bytes::Bytes;
8use dashmap::DashMap;
9
10use crate::OrbitTyped;
11use crate::error::{Error, Result};
12use crate::id::NetId64;
13#[cfg(any(target_os = "linux", target_os = "freebsd"))]
14use crate::ring::RingEventFd;
15#[cfg(unix)]
16use crate::ring::shm::{ShmRing, ShmRingRegistry};
17use crate::ring::{Frame, Ring, RingRegistry, RingTopology};
18
19mod cursor;
20pub use cursor::{FleetLaneCursor, FleetLanePoll};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[repr(transparent)]
29pub struct NodeId(pub u16);
30
31impl NodeId {
32 pub const ZERO: Self = Self(0);
33
34 pub const fn new(value: u16) -> Self {
35 Self(value)
36 }
37
38 pub const fn get(self) -> u16 {
39 self.0
40 }
41}
42
43impl std::fmt::Display for NodeId {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "node:{}", self.0)
46 }
47}
48
49#[derive(Clone)]
52pub struct Fleet {
53 inner: Arc<FleetInner>,
54}
55
56struct FleetInner {
57 name: &'static str,
58 fleet_size: u8,
59 node_id: NodeId,
60 id_counters: DashMap<u8, Arc<AtomicU64>>,
65 backing: RingBacking,
69}
70
71enum RingBacking {
75 InMemory(RingRegistry),
79 #[cfg(unix)]
83 Shm(ShmRingRegistry),
84}
85
86impl Fleet {
87 pub fn join(name: &'static str, fleet_size: u8) -> Result<Self> {
91 Self::join_as(name, fleet_size, NodeId::ZERO)
92 }
93
94 pub fn join_as(name: &'static str, fleet_size: u8, node_id: NodeId) -> Result<Self> {
96 if fleet_size == 0 {
97 return Err(Error::EmptyFleet);
98 }
99 if node_id.get() >= u16::from(fleet_size) {
100 return Err(Error::NodeOutsideFleet {
101 node_id: node_id.get(),
102 fleet_size,
103 });
104 }
105 Ok(Self {
106 inner: Arc::new(FleetInner {
107 name,
108 fleet_size,
109 node_id,
110 id_counters: DashMap::new(),
111 backing: RingBacking::InMemory(RingRegistry::new(fleet_size)),
112 }),
113 })
114 }
115
116 #[cfg(unix)]
128 pub fn join_shm(name: &'static str, fleet_size: u8) -> Result<Self> {
129 Self::join_shm_as(name, fleet_size, NodeId::ZERO)
130 }
131
132 #[cfg(unix)]
138 pub fn join_shm_as(name: &'static str, fleet_size: u8, node_id: NodeId) -> Result<Self> {
139 if fleet_size == 0 {
140 return Err(Error::EmptyFleet);
141 }
142 if node_id.get() >= u16::from(fleet_size) {
143 return Err(Error::NodeOutsideFleet {
144 node_id: node_id.get(),
145 fleet_size,
146 });
147 }
148 Ok(Self {
149 inner: Arc::new(FleetInner {
150 name,
151 fleet_size,
152 node_id,
153 id_counters: DashMap::new(),
154 backing: RingBacking::Shm(ShmRingRegistry::new(name, fleet_size)),
155 }),
156 })
157 }
158
159 pub fn name(&self) -> &'static str {
160 self.inner.name
161 }
162
163 pub fn fleet_size(&self) -> u8 {
164 self.inner.fleet_size
165 }
166
167 pub fn node_id(&self) -> NodeId {
168 self.inner.node_id
169 }
170
171 pub fn next_id<T: OrbitTyped>(&self) -> NetId64 {
179 let counter_arc = self
180 .inner
181 .id_counters
182 .entry(T::KIND)
183 .or_insert_with(|| Arc::new(AtomicU64::new(0)))
184 .clone();
185 let counter = counter_arc.fetch_add(1, Ordering::Relaxed);
186 NetId64::make(T::KIND, self.node_id().get(), counter)
187 }
188
189 pub fn is_shm(&self) -> bool {
192 #[cfg(unix)]
193 {
194 matches!(self.inner.backing, RingBacking::Shm(_))
195 }
196 #[cfg(not(unix))]
197 {
198 false
199 }
200 }
201
202 pub fn ring<T: OrbitTyped>(&self) -> Arc<Ring> {
210 match &self.inner.backing {
211 RingBacking::InMemory(r) => r.get_or_create::<T>(),
212 #[cfg(unix)]
213 RingBacking::Shm(_) => {
214 panic!("Fleet::ring called on SHM-backed fleet — use Fleet::shm_ring instead");
215 }
216 }
217 }
218
219 #[cfg(unix)]
231 pub fn shm_ring<T: OrbitTyped>(&self) -> std::io::Result<Arc<ShmRing>> {
232 match &self.inner.backing {
233 RingBacking::Shm(r) => r.get_or_create_for::<T>(),
234 RingBacking::InMemory(_) => {
235 panic!("Fleet::shm_ring called on in-memory fleet — use Fleet::ring instead");
236 }
237 }
238 }
239
240 pub fn publish<T: OrbitTyped>(&self, frame_kind: u8, ver: u64, payload: Bytes) -> NetId64 {
250 match &self.inner.backing {
251 RingBacking::InMemory(r) => {
252 let ring = r.get_or_create::<T>();
253 ring.write(self.node_id(), frame_kind, ver, payload)
254 }
255 #[cfg(unix)]
256 RingBacking::Shm(r) => {
257 let ring = r
258 .get_or_create_for::<T>()
259 .expect("SHM ring open failed — fleet unusable");
260 ring.write(self.node_id(), frame_kind, ver, payload)
261 .expect("SHM ring write failed")
262 }
263 }
264 }
265
266 pub fn publish_batch<T: OrbitTyped>(
279 &self,
280 frame_kind: u8,
281 ver: u64,
282 payloads: Vec<Bytes>,
283 ) -> Vec<NetId64> {
284 match &self.inner.backing {
285 RingBacking::InMemory(r) => {
286 let ring = r.get_or_create::<T>();
287 ring.write_batch(self.node_id(), frame_kind, ver, payloads)
288 }
289 #[cfg(unix)]
290 RingBacking::Shm(r) => {
291 let ring = r
292 .get_or_create_for::<T>()
293 .expect("SHM ring open failed — fleet unusable");
294 ring.write_batch(self.node_id(), frame_kind, ver, payloads)
295 .expect("SHM ring batch write failed")
296 }
297 }
298 }
299
300 pub fn read(&self, id: NetId64) -> Option<Frame> {
304 match &self.inner.backing {
305 RingBacking::InMemory(r) => r.lookup(id.kind())?.read(id),
306 #[cfg(unix)]
307 RingBacking::Shm(r) => r.lookup(id.kind())?.read(id),
308 }
309 }
310
311 pub fn read_head<T: OrbitTyped>(&self) -> Option<Frame> {
314 if T::RING_SPEC.topology == RingTopology::PerNode {
315 let head = self.lane_head::<T>(self.node_id());
316 return (head > 0)
317 .then(|| self.read_lane_at::<T>(self.node_id(), head - 1))
318 .flatten();
319 }
320 match &self.inner.backing {
321 RingBacking::InMemory(r) => {
322 let ring = r.get_or_create::<T>();
323 ring.read_head()
324 }
325 #[cfg(unix)]
326 RingBacking::Shm(r) => {
327 let ring = r.get_or_create_for::<T>().ok()?;
328 ring.read_head()
329 }
330 }
331 }
332
333 pub fn head<T: OrbitTyped>(&self) -> u64 {
342 if T::RING_SPEC.topology == RingTopology::PerNode {
343 return self.lane_head::<T>(self.node_id());
344 }
345 match &self.inner.backing {
346 RingBacking::InMemory(r) => r.get_or_create::<T>().head(),
347 #[cfg(unix)]
348 RingBacking::Shm(r) => r
349 .get_or_create_for::<T>()
350 .map(|ring| ring.head())
351 .unwrap_or(0),
352 }
353 }
354
355 pub fn read_at<T: OrbitTyped>(&self, counter: u64) -> Option<Frame> {
363 if T::RING_SPEC.topology == RingTopology::PerNode {
364 return self.read_lane_at::<T>(self.node_id(), counter);
365 }
366 match &self.inner.backing {
367 RingBacking::InMemory(r) => r.get_or_create::<T>().read_at(counter),
368 #[cfg(unix)]
369 RingBacking::Shm(r) => r.get_or_create_for::<T>().ok()?.read_at(counter),
370 }
371 }
372
373 pub(crate) fn read_state_at<T: OrbitTyped>(
374 &self,
375 counter: u64,
376 ) -> crate::ring::cursor::RingRead {
377 if T::RING_SPEC.topology == RingTopology::PerNode {
378 return self.read_lane_state_at::<T>(self.node_id(), counter);
379 }
380 match &self.inner.backing {
381 RingBacking::InMemory(r) => r.get_or_create::<T>().read_state_at(counter),
382 #[cfg(unix)]
383 RingBacking::Shm(r) => r
384 .get_or_create_for::<T>()
385 .map(|ring| ring.read_state_at(counter))
386 .unwrap_or(crate::ring::cursor::RingRead::Unavailable),
387 }
388 }
389
390 pub fn lane_head<T: OrbitTyped>(&self, node_id: NodeId) -> u64 {
394 match &self.inner.backing {
395 RingBacking::InMemory(r) => r.get_or_create::<T>().lane_head(node_id),
396 #[cfg(unix)]
397 RingBacking::Shm(r) => r
398 .get_or_create_for::<T>()
399 .map(|ring| ring.lane_head(node_id))
400 .unwrap_or(0),
401 }
402 }
403
404 pub fn read_lane_at<T: OrbitTyped>(&self, node_id: NodeId, counter: u64) -> Option<Frame> {
406 match &self.inner.backing {
407 RingBacking::InMemory(r) => r.get_or_create::<T>().read_lane_at(node_id, counter),
408 #[cfg(unix)]
409 RingBacking::Shm(r) => r
410 .get_or_create_for::<T>()
411 .ok()?
412 .read_lane_at(node_id, counter),
413 }
414 }
415
416 pub(crate) fn read_lane_state_at<T: OrbitTyped>(
417 &self,
418 node_id: NodeId,
419 counter: u64,
420 ) -> crate::ring::cursor::RingRead {
421 match &self.inner.backing {
422 RingBacking::InMemory(r) => r.get_or_create::<T>().read_lane_state_at(node_id, counter),
423 #[cfg(unix)]
424 RingBacking::Shm(r) => r
425 .get_or_create_for::<T>()
426 .map(|ring| ring.read_lane_state_at(node_id, counter))
427 .unwrap_or(crate::ring::cursor::RingRead::Unavailable),
428 }
429 }
430
431 pub fn ring_capacity<T: OrbitTyped>(&self) -> usize {
435 match &self.inner.backing {
436 RingBacking::InMemory(r) => r.get_or_create::<T>().capacity(),
437 #[cfg(unix)]
438 RingBacking::Shm(r) => r
439 .get_or_create_for::<T>()
440 .map(|ring| ring.capacity())
441 .unwrap_or(T::RING_SPEC.capacity),
442 }
443 }
444
445 pub fn next_ring_version<T: OrbitTyped>(&self) -> u64 {
451 match &self.inner.backing {
452 RingBacking::InMemory(r) => r.get_or_create::<T>().next_version(),
453 #[cfg(unix)]
454 RingBacking::Shm(r) => r
455 .get_or_create_for::<T>()
456 .expect("SHM ring open failed — fleet unusable")
457 .next_version(),
458 }
459 }
460
461 pub fn current_ring_version<T: OrbitTyped>(&self) -> u64 {
463 match &self.inner.backing {
464 RingBacking::InMemory(r) => r.get_or_create::<T>().current_version(),
465 #[cfg(unix)]
466 RingBacking::Shm(r) => r
467 .get_or_create_for::<T>()
468 .expect("SHM ring open failed — fleet unusable")
469 .current_version(),
470 }
471 }
472
473 pub fn reset_ring<T: OrbitTyped>(&self) -> std::io::Result<()> {
481 match &self.inner.backing {
482 RingBacking::InMemory(r) => {
483 r.get_or_create::<T>().reset();
484 Ok(())
485 }
486 #[cfg(unix)]
487 RingBacking::Shm(r) => {
488 r.get_or_create_for::<T>()?.reset();
489 Ok(())
490 }
491 }
492 }
493
494 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
495 pub fn ring_event_fd<T: OrbitTyped>(&self) -> std::io::Result<RingEventFd> {
501 match &self.inner.backing {
502 RingBacking::Shm(rings) => RingEventFd::new(rings.get_or_create_for::<T>()?),
503 RingBacking::InMemory(_) => Err(std::io::Error::new(
504 std::io::ErrorKind::Unsupported,
505 "Orbit eventfd requires a shared-memory fleet",
506 )),
507 }
508 }
509
510 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
511 pub fn publish_notified<T: OrbitTyped>(
513 &self,
514 frame_kind: u8,
515 ver: u64,
516 payload: Bytes,
517 ) -> std::io::Result<NetId64> {
518 match &self.inner.backing {
519 RingBacking::Shm(rings) => {
520 let ring = rings.get_or_create_for::<T>()?;
521 let id = ring.write(self.node_id(), frame_kind, ver, payload)?;
522 RingEventFd::notify(&ring)?;
523 Ok(id)
524 }
525 RingBacking::InMemory(rings) => {
527 Ok(rings
528 .get_or_create::<T>()
529 .write(self.node_id(), frame_kind, ver, payload))
530 }
531 }
532 }
533
534 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
535 pub fn publish_batch_notified<T: OrbitTyped>(
538 &self,
539 frame_kind: u8,
540 ver: u64,
541 payloads: Vec<Bytes>,
542 ) -> std::io::Result<Vec<NetId64>> {
543 match &self.inner.backing {
544 RingBacking::Shm(rings) => {
545 let ring = rings.get_or_create_for::<T>()?;
546 let ids = ring.write_batch(self.node_id(), frame_kind, ver, payloads)?;
547 if !ids.is_empty() {
548 RingEventFd::notify(&ring)?;
549 }
550 Ok(ids)
551 }
552 RingBacking::InMemory(rings) => Ok(rings.get_or_create::<T>().write_batch(
553 self.node_id(),
554 frame_kind,
555 ver,
556 payloads,
557 )),
558 }
559 }
560}
561
562impl std::fmt::Debug for Fleet {
563 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
564 f.debug_struct("Fleet")
565 .field("name", &self.inner.name)
566 .field("fleet_size", &self.inner.fleet_size)
567 .field("node_id", &self.inner.node_id)
568 .field("id_counters", &self.inner.id_counters.len())
569 .finish()
570 }
571}