1#[cfg(feature = "tokio")]
19use std::collections::VecDeque;
20use std::sync::Arc;
21#[cfg(feature = "tokio")]
22use std::time::Duration;
23
24use bytes::{BufMut, Bytes, BytesMut};
25#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
26use orbit_core::RingEventFd;
27use orbit_core::fleet::FleetLaneCursor;
28use orbit_core::{Fleet, NetId64, NodeId, OrbitEpoch, OrbitTyped, RingSpec};
29
30pub type Result<T, E = Error> = std::result::Result<T, E>;
31
32pub const INVOCATION_RING_KIND: u8 = 241;
34pub const INVOCATION_RING_CAPACITY: usize =
36 orbit_core::compile::usize_from_env(option_env!("ORBIT_INVOKE_RING_CAPACITY"), 256);
37pub const INVOCATION_RING_PAYLOAD_CAPACITY: usize =
39 orbit_core::compile::usize_from_env(option_env!("ORBIT_INVOKE_RING_PAYLOAD_CAPACITY"), 8_192);
40pub const INVOCATION_RING_SPEC: RingSpec =
41 RingSpec::per_node(INVOCATION_RING_CAPACITY, INVOCATION_RING_PAYLOAD_CAPACITY);
42pub const INVOCATION_PAYLOAD_MAX: usize = INVOCATION_RING_SPEC.payload_capacity;
43
44const FRAME_KIND_INVOCATION: u8 = 1;
45const HEADER_LEN: usize = 2 + 4;
46#[cfg(feature = "tokio")]
47const FALLBACK_POLL_INTERVAL: Duration = Duration::from_millis(10);
48
49const _: () = assert!(INVOCATION_RING_CAPACITY.is_power_of_two());
50const _: () = assert!(INVOCATION_RING_PAYLOAD_CAPACITY >= HEADER_LEN);
51const _: () = assert!(INVOCATION_RING_PAYLOAD_CAPACITY <= u32::MAX as usize);
52
53#[derive(Clone, Debug)]
54struct InvocationRecord;
55
56impl OrbitTyped for InvocationRecord {
57 const KIND: u8 = INVOCATION_RING_KIND;
58 const RING_SPEC: RingSpec = INVOCATION_RING_SPEC;
59}
60
61#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66pub struct InvocationId(NetId64);
67
68impl InvocationId {
69 pub const fn from_net_id(id: NetId64) -> Self {
70 Self(id)
71 }
72
73 pub const fn net_id(self) -> NetId64 {
74 self.0
75 }
76
77 pub const fn node(self) -> u16 {
78 self.0.node()
79 }
80}
81
82impl std::fmt::Display for InvocationId {
83 fn fmt(
84 &self,
85 formatter: &mut std::fmt::Formatter<'_>
86 ) -> std::fmt::Result {
87 self.0.fmt(formatter)
88 }
89}
90
91#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct Invocation {
94 pub id: InvocationId,
95 pub operation: String,
96 pub payload: Vec<u8>,
97 pub submitted_at: OrbitEpoch
99}
100
101pub trait InvocationCodec: Sized {
106 const OPERATION: &'static str;
107
108 fn encode_invocation(&self) -> std::result::Result<Vec<u8>, String>;
109
110 fn decode_invocation(payload: &[u8]) -> std::result::Result<Self, String>;
111}
112
113impl Invocation {
114 pub fn decode<C: InvocationCodec>(&self) -> Result<C> {
115 if self.operation != C::OPERATION {
116 return Err(Error::OperationMismatch {
117 expected: C::OPERATION,
118 actual: self.operation.clone()
119 });
120 }
121 C::decode_invocation(&self.payload).map_err(Error::Codec)
122 }
123}
124
125#[derive(Clone, Debug, Default, Eq, PartialEq)]
127pub struct InvocationPoll {
128 pub invocations: Vec<Invocation>,
129 pub lagged: u64
131}
132
133impl InvocationPoll {
134 pub fn is_empty(&self) -> bool {
135 self.invocations.is_empty() && self.lagged == 0
136 }
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct InvocationCursor {
142 inner: FleetLaneCursor
143}
144
145#[derive(Clone)]
147pub struct InvocationBus {
148 fleet: Arc<Fleet>
149}
150
151impl InvocationBus {
152 pub fn new(fleet: Arc<Fleet>) -> Self {
153 Self { fleet }
154 }
155
156 pub fn node_id(&self) -> NodeId {
157 self.fleet.node_id()
158 }
159
160 pub fn cursor_at_head(&self) -> InvocationCursor {
161 InvocationCursor { inner: self.fleet.lane_cursor_at_head::<InvocationRecord>() }
162 }
163
164 pub fn cursor_from_start(&self) -> InvocationCursor {
165 InvocationCursor { inner: self.fleet.lane_cursor_from_start::<InvocationRecord>() }
166 }
167
168 #[cfg(feature = "tokio")]
173 pub fn subscribe(
174 self: &Arc<Self>,
175 operation: impl Into<String>
176 ) -> Result<InvocationSubscription> {
177 let operation = operation.into();
178 validate_operation(&operation)?;
179 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
180 let event_fd = self.subscription_event_fd()?;
181 Ok(InvocationSubscription {
182 cursor: self.cursor_at_head(),
183 bus: Arc::clone(self),
184 operation,
185 pending: VecDeque::new(),
186 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
187 event_fd
188 })
189 }
190
191 pub fn reset_ring(&self) -> Result<()> {
193 self.fleet.reset_ring::<InvocationRecord>().map_err(Error::Io)
194 }
195
196 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
198 pub fn event_fd(&self) -> Result<RingEventFd> {
199 self.fleet.ring_event_fd::<InvocationRecord>().map_err(Error::Io)
200 }
201
202 #[cfg(all(
203 feature = "tokio",
204 any(target_os = "linux", target_os = "freebsd", target_os = "macos")
205 ))]
206 fn subscription_event_fd(&self) -> Result<Option<tokio::io::unix::AsyncFd<RingEventFd>>> {
207 if !self.fleet.is_shm() {
208 return Ok(None);
209 }
210 match self.event_fd() {
211 Ok(event_fd) => Ok(Some(tokio::io::unix::AsyncFd::new(event_fd).map_err(Error::Io)?)),
212 #[cfg(target_os = "macos")]
213 Err(Error::Io(error)) if error.kind() == std::io::ErrorKind::Unsupported => Ok(None),
214 Err(error) => Err(error)
215 }
216 }
217
218 pub fn submit(
220 &self,
221 operation: &str,
222 payload: &[u8]
223 ) -> Result<InvocationId> {
224 validate_operation(operation)?;
225 let submitted_at = OrbitEpoch::now();
226 let frame = encode_frame(operation.as_bytes(), payload)?;
227 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
228 let id = self
229 .fleet
230 .publish_notified::<InvocationRecord>(
231 FRAME_KIND_INVOCATION,
232 submitted_at.as_unix_ms(),
233 frame
234 )
235 .map_err(Error::Io)?;
236 #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))]
237 let id = self.fleet.publish::<InvocationRecord>(
238 FRAME_KIND_INVOCATION,
239 submitted_at.as_unix_ms(),
240 frame
241 );
242 Ok(InvocationId(id))
243 }
244
245 pub fn submit_typed<C: InvocationCodec>(
246 &self,
247 invocation: &C
248 ) -> Result<InvocationId> {
249 let payload = invocation.encode_invocation().map_err(Error::Codec)?;
250 self.submit(C::OPERATION, &payload)
251 }
252
253 pub fn poll(
255 &self,
256 cursor: &mut InvocationCursor
257 ) -> InvocationPoll {
258 let poll = self.fleet.poll_lanes::<InvocationRecord>(&mut cursor.inner);
259 let mut lagged = poll.loss.total();
260 let mut invocations = Vec::with_capacity(poll.frames.len());
261 for frame in poll.frames {
262 if frame.kind != FRAME_KIND_INVOCATION {
263 lagged = lagged.saturating_add(1);
264 continue;
265 }
266 let Some(decoded) = decode_frame(&frame.payload) else {
267 lagged = lagged.saturating_add(1);
268 continue;
269 };
270 let Ok(operation) = std::str::from_utf8(decoded.operation) else {
271 lagged = lagged.saturating_add(1);
272 continue;
273 };
274 invocations.push(Invocation {
275 id: InvocationId(frame.id),
276 operation: operation.to_owned(),
277 payload: decoded.payload.to_vec(),
278 submitted_at: OrbitEpoch::from_unix_ms(frame.ver)
279 });
280 }
281 InvocationPoll { invocations, lagged }
282 }
283
284 pub fn poll_operation(
286 &self,
287 cursor: &mut InvocationCursor,
288 operation: &str
289 ) -> InvocationPoll {
290 let mut poll = self.poll(cursor);
291 poll.invocations.retain(|invocation| invocation.operation == operation);
292 poll
293 }
294}
295
296#[cfg(feature = "tokio")]
298pub struct InvocationSubscription {
299 bus: Arc<InvocationBus>,
300 operation: String,
301 cursor: InvocationCursor,
302 pending: VecDeque<Invocation>,
303 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
304 event_fd: Option<tokio::io::unix::AsyncFd<RingEventFd>>
305}
306
307#[cfg(feature = "tokio")]
308impl InvocationSubscription {
309 pub async fn receive(&mut self) -> Result<Invocation> {
315 loop {
316 if let Some(invocation) = self.pending.pop_front() {
317 return Ok(invocation);
318 }
319
320 let poll = self.bus.poll_operation(&mut self.cursor, &self.operation);
321 self.pending.extend(poll.invocations);
322 if poll.lagged > 0 {
323 return Err(Error::Lagged(poll.lagged));
324 }
325 if let Some(invocation) = self.pending.pop_front() {
326 return Ok(invocation);
327 }
328
329 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
330 if let Some(event_fd) = &self.event_fd {
331 let mut ready = event_fd.readable().await.map_err(Error::Io)?;
332 event_fd.get_ref().drain().map_err(Error::Io)?;
333 ready.clear_ready();
334 continue;
335 }
336
337 tokio::time::sleep(FALLBACK_POLL_INTERVAL).await;
338 }
339 }
340}
341
342#[derive(Debug)]
343pub enum Error {
344 EmptyOperation,
345 OperationTooLong(usize),
346 FrameTooLarge { operation_len: usize, payload_len: usize, max: usize },
347 OperationMismatch { expected: &'static str, actual: String },
348 Lagged(u64),
349 Codec(String),
350 Io(std::io::Error)
351}
352
353impl std::fmt::Display for Error {
354 fn fmt(
355 &self,
356 formatter: &mut std::fmt::Formatter<'_>
357 ) -> std::fmt::Result {
358 match self {
359 Self::EmptyOperation => formatter.write_str("invocation operation cannot be empty"),
360 Self::OperationTooLong(len) => {
361 write!(formatter, "invocation operation is too long: len={len} max=65535")
362 }
363 Self::FrameTooLarge { operation_len, payload_len, max } => write!(
364 formatter,
365 "invocation frame is too large: operation_len={operation_len} payload_len={payload_len} max={max}"
366 ),
367 Self::OperationMismatch { expected, actual } => write!(
368 formatter,
369 "invocation operation mismatch: expected {expected}, got {actual}"
370 ),
371 Self::Lagged(count) => {
372 write!(formatter, "invocation subscription lagged by {count} frame(s)")
373 }
374 Self::Codec(error) => write!(formatter, "invocation codec failed: {error}"),
375 Self::Io(error) => write!(formatter, "invocation ring io error: {error}")
376 }
377 }
378}
379
380impl std::error::Error for Error {
381 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
382 match self {
383 Self::Io(error) => Some(error),
384 _ => None
385 }
386 }
387}
388
389struct DecodedFrame<'a> {
390 operation: &'a [u8],
391 payload: &'a [u8]
392}
393
394fn validate_operation(operation: &str) -> Result<()> {
395 if operation.trim().is_empty() {
396 return Err(Error::EmptyOperation);
397 }
398 if operation.len() > usize::from(u16::MAX) {
399 return Err(Error::OperationTooLong(operation.len()));
400 }
401 Ok(())
402}
403
404fn encode_frame(
405 operation: &[u8],
406 payload: &[u8]
407) -> Result<Bytes> {
408 let total = HEADER_LEN
409 .checked_add(operation.len())
410 .and_then(|len| len.checked_add(payload.len()))
411 .ok_or(Error::FrameTooLarge {
412 operation_len: operation.len(),
413 payload_len: payload.len(),
414 max: INVOCATION_PAYLOAD_MAX
415 })?;
416 if operation.len() > usize::from(u16::MAX)
417 || payload.len() > u32::MAX as usize
418 || total > INVOCATION_PAYLOAD_MAX
419 {
420 return Err(Error::FrameTooLarge {
421 operation_len: operation.len(),
422 payload_len: payload.len(),
423 max: INVOCATION_PAYLOAD_MAX
424 });
425 }
426 let mut frame = BytesMut::with_capacity(total);
427 frame.put_u16_le(operation.len() as u16);
428 frame.put_u32_le(payload.len() as u32);
429 frame.put_slice(operation);
430 frame.put_slice(payload);
431 Ok(frame.freeze())
432}
433
434fn decode_frame(frame: &Bytes) -> Option<DecodedFrame<'_>> {
435 if frame.len() < HEADER_LEN {
436 return None;
437 }
438 let operation_len = u16::from_le_bytes(frame[0..2].try_into().ok()?) as usize;
439 let payload_len = u32::from_le_bytes(frame[2..6].try_into().ok()?) as usize;
440 let operation_end = HEADER_LEN.checked_add(operation_len)?;
441 let payload_end = operation_end.checked_add(payload_len)?;
442 if payload_end != frame.len() {
443 return None;
444 }
445 Some(DecodedFrame {
446 operation: &frame[HEADER_LEN..operation_end],
447 payload: &frame[operation_end..payload_end]
448 })
449}