Skip to main content

harmos_sdk/operations/
serving.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12mod codec;
13mod owned;
14#[cfg(feature = "grpc")]
15mod protobuf;
16#[cfg(feature = "streaming")]
17mod streams;
18
19/* === Re-Exports === */
20
21pub use codec::WireCodecError;
22#[cfg(feature = "streaming")]
23pub use streams::{WireEndpoints, WireStreamLimits, WireStreams};
24
25/* === Imports === */
26
27// Standard Library Imports
28use std::collections::BTreeMap;
29use std::future::Future;
30use std::marker::PhantomData;
31use std::num::NonZeroUsize;
32use std::pin::Pin;
33use std::sync::{Arc, Mutex, Weak};
34use std::task::{Poll, Waker};
35// Third Party Imports
36use serde::{Deserialize, Serialize};
37use tracing::Instrument;
38// Local Imports
39use super::{AuthoredDeclarations, Contract, ContractError, Definition, FailureKind, Operation};
40use crate::catalog::{Catalog, Member, Projection, Visitor};
41
42/* === Constants === */
43
44pub const WIRE_METHOD: &str = "harmos.operation.v2";
45
46pub const CHILD_METHOD: &str = "harmos.operation.child.v2";
47
48/* === Macros === */
49
50/* === Type Aliases === */
51
52type Answer<'a> = Pin<Box<dyn Future<Output = Result<Vec<u8>, ServingError>> + Send + 'a>>;
53type Finished<'a> = Pin<Box<dyn Future<Output = Result<(), ServingError>> + Send + 'a>>;
54type Finalization<S> = Pin<Box<dyn Future<Output = (S, Result<(), ServingError>)> + Send + 'static>>;
55type Prepare<C> = Box<dyn Fn(Vec<u8>, bool) -> Result<Box<dyn Prepared<C>>, ServingError> + Send + Sync>;
56type Install<H, C> = fn(&mut WireTable<C>, Arc<H>) -> Result<(), ServingError>;
57
58/* === Structs === */
59
60struct Contextual<F: std::future::Future> {
61    future: Option<std::pin::Pin<Box<F>>>,
62    dispatcher: tracing::Dispatch,
63}
64
65#[derive(Debug, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct WireChildCall {
68    pub context: [u8; 32],
69
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub generation_context: Option<[u8; 32]>,
72
73    #[serde(default, skip_serializing_if = "Vec::is_empty")]
74    pub values: Vec<super::CallValue>,
75
76    pub operation: String,
77    pub payload: Vec<u8>,
78
79    // Submitted children answer their instance id at admission and remain
80    // owned by the calling attempt until joined, released or detached.
81    #[serde(default)]
82    pub submit: bool,
83}
84
85// Drives one child the same attempt submitted; the host resolves the instance
86// only inside the attempt context that submitted it.
87#[derive(Debug, Serialize, Deserialize)]
88#[serde(deny_unknown_fields)]
89pub struct WireChildControl {
90    pub context: [u8; 32],
91    pub instance: u64,
92    pub action: WireChildAction,
93}
94
95pub struct WireTable<C> {
96    handlers: BTreeMap<String, Handler<C>>,
97    prepared: BTreeMap<u64, Arc<Slot<C>>>,
98    capacity: NonZeroUsize,
99    closed: bool,
100    ordinary_closed: bool,
101
102    #[cfg(feature = "streaming")]
103    stream_limits: Option<WireStreamLimits>,
104}
105
106struct Slot<C> {
107    lifecycle: bool,
108    resident: bool,
109
110    #[cfg(feature = "streaming")]
111    streams: Option<WireStreams>,
112
113    state: Mutex<SlotState<C>>,
114}
115
116struct SlotState<C> {
117    prepared: Option<Box<dyn Prepared<C>>>,
118    finished: bool,
119    waiters: Vec<Weak<Mutex<Option<Waker>>>>,
120}
121
122struct Loan<C> {
123    slot: Arc<Slot<C>>,
124    prepared: Option<Box<dyn Prepared<C>>>,
125    finished: bool,
126}
127
128struct Handler<C> {
129    lifecycle: bool,
130    // Lifecycle actions and the observe control are built in: they serve the
131    // plugin's machinery and never appear in its declared catalog.
132    builtin: bool,
133    // A resident control runs until cancelled and is not charged against the
134    // ordinary invocation capacity.
135    resident: bool,
136    definition: Definition,
137    contract: Option<Contract>,
138    prepare: Prepare<C>,
139}
140
141pub struct Serves<H, C>(PhantomData<fn() -> (H, C)>);
142
143pub struct WireBinding<H, C> {
144    install: Install<H, C>,
145}
146
147/* === Enums === */
148
149// Answers: `Join` the child's response payload, `Status` and `Cancel` an
150// encoded `builtin::InstanceState`, `Release` and `Detach` an empty payload.
151// Releasing cancels the child; detaching leaves it running without a handle.
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub enum WireChildAction {
155    Join,
156    Status,
157    Cancel,
158    Release,
159    Detach,
160}
161
162#[derive(Debug)]
163pub enum WireChild {
164    Call(WireChildCall),
165    Control(WireChildControl),
166}
167
168#[derive(Debug, Serialize, Deserialize)]
169#[serde(tag = "phase", rename_all = "snake_case", deny_unknown_fields)]
170pub enum WireCommand {
171    Prepare { invocation: u64, operation: String, payload: Vec<u8> },
172    Initialize { invocation: u64, context: [u8; 32] },
173    Attempt { invocation: u64, context: [u8; 32], values: Vec<super::CallValue> },
174    Finalize { invocation: u64 },
175    StreamAttempt { invocation: u64, epoch: u64, context: [u8; 32], values: Vec<super::CallValue> },
176    StreamSend { invocation: u64, epoch: u64, payload: Vec<u8> },
177    StreamFinish { invocation: u64, epoch: u64 },
178    StreamReceive { invocation: u64, epoch: u64 },
179    PrepareGrpc { invocation: u64, operation: String, payload: Vec<u8> },
180}
181
182#[derive(Debug, Serialize, Deserialize)]
183#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
184pub enum WireAnswer {
185    Stream { epoch: u64, transferred: bool, payload: Option<Vec<u8>> },
186    Ready { payload: Vec<u8> },
187    Failed { failure: crate::Failure },
188}
189
190#[derive(Debug, thiserror::Error)]
191pub enum ServingError {
192    #[error("carrier invocation does not support stream commands")]
193    StreamUnsupported,
194
195    #[cfg(feature = "streaming")]
196    #[error("streaming catalog requires explicit provider stream limits")]
197    StreamLimits,
198
199    #[cfg(feature = "streaming")]
200    #[error("carrier stream failed: {0}")]
201    Stream(#[source] super::streaming::Error),
202
203    #[cfg(feature = "streaming")]
204    #[error("carrier stream direction already has an outstanding request")]
205    StreamBusy,
206
207    #[cfg(feature = "streaming")]
208    #[error("carrier stream direction or attempt is not available")]
209    StreamPhase,
210
211    #[error(transparent)]
212    Lifecycle(#[from] crate::operations::lifecycle::Error),
213
214    #[error(transparent)]
215    Contract(#[from] ContractError),
216
217    #[error(transparent)]
218    Declaration(#[from] crate::Error),
219
220    #[error("operation `{operation}` has no bound carrier handler")]
221    Unknown { operation: String },
222
223    #[error("operation `{operation}` requires streaming endpoints unsupported by this carrier binding")]
224    Streaming { operation: String },
225
226    #[error("carrier invocation `{invocation}` is already prepared")]
227    Duplicate { invocation: u64 },
228
229    #[error("carrier invocation `{invocation}` has not been prepared")]
230    Missing { invocation: u64 },
231
232    #[error("carrier operation has not completed initialization")]
233    Uninitialized,
234
235    #[error("carrier operation initialization already started and cannot be replayed")]
236    InitializationStarted,
237
238    #[error("carrier invocation capacity is exhausted")]
239    Capacity,
240
241    #[error("carrier ordinary invocations have not finished settlement")]
242    Unsettled,
243
244    #[error("carrier operation admission is closing")]
245    Closing,
246
247    #[error("carrier handler definitions differ from the selected operation catalog")]
248    Catalog,
249
250    #[error("operation payload codec failed")]
251    Codec(#[source] crate::MessageError),
252
253    #[error("carrier handler panicked")]
254    Panicked,
255
256    // The handler's failure, written once at its origin.
257    #[error(transparent)]
258    Handler(crate::Failure),
259}
260
261/* === Traits === */
262
263trait InContext: std::future::Future + Sized {
264    fn in_context(self, dispatcher: tracing::Dispatch) -> Contextual<Self> {
265        Contextual { future: Some(Box::pin(self)), dispatcher }
266    }
267
268    fn in_current_context(self) -> Contextual<Self> {
269        self.in_context(tracing::dispatcher::get_default(Clone::clone))
270    }
271}
272
273trait Prepared<C>: Send {
274    #[cfg(feature = "streaming")]
275    fn streams(&self) -> Option<WireStreams> {
276        None
277    }
278
279    // The grant the prepared request needs, answered to the host with Prepare.
280    fn grant(&self) -> Option<crate::plugin::Grant> {
281        None
282    }
283    fn initialize<'a>(&'a mut self, context: &'a C) -> Finished<'a>;
284    fn attempt<'a>(&'a mut self, context: &'a C) -> Answer<'a>;
285    fn finalize(&mut self) -> Finished<'_>;
286}
287
288/* === Implementations === */
289
290impl<F: std::future::Future> InContext for F {}
291
292impl<F: std::future::Future> std::future::Future for Contextual<F> {
293    type Output = F::Output;
294
295    fn poll(self: std::pin::Pin<&mut Self>, context: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
296        let owner = self.get_mut();
297        let _dispatcher = tracing::dispatcher::set_default(&owner.dispatcher);
298        owner.future.as_mut().expect("context future remains until drop").as_mut().poll(context)
299    }
300}
301
302impl<F: std::future::Future> Drop for Contextual<F> {
303    fn drop(&mut self) {
304        tracing::dispatcher::with_default(&self.dispatcher, || drop(self.future.take()));
305    }
306}
307
308impl<C: Send + Sync + 'static> WireTable<C> {
309    pub fn new(capacity: NonZeroUsize) -> Self {
310        Self {
311            handlers: BTreeMap::new(),
312            prepared: BTreeMap::new(),
313            capacity,
314            closed: false,
315            ordinary_closed: false,
316            #[cfg(feature = "streaming")]
317            stream_limits: None,
318        }
319    }
320
321    pub fn of<T, R>(resources: Arc<R>, capacity: NonZeroUsize) -> Result<Self, ServingError>
322    where
323        T: Catalog<AuthoredDeclarations> + Catalog<Serves<R, C>>,
324        R: Send + Sync + 'static,
325    {
326        Self::project::<T, R>(Self::new(capacity), resources)
327    }
328
329    fn project<T, R>(mut table: Self, resources: Arc<R>) -> Result<Self, ServingError>
330    where
331        T: Catalog<AuthoredDeclarations> + Catalog<Serves<R, C>>,
332        R: Send + Sync + 'static,
333    {
334        let definitions = AuthoredDeclarations::of::<T>()?;
335        <T as Catalog<Serves<R, C>>>::ENTRIES.try_for_each(|binding| (binding.install)(&mut table, Arc::clone(&resources)))?;
336        table.validate(&definitions)?;
337        Ok(table)
338    }
339
340    pub fn bind_operations<T, R>(&mut self, resources: Arc<R>) -> Result<(), ServingError>
341    where
342        T: Catalog<AuthoredDeclarations> + Catalog<Serves<R, C>>,
343        R: Send + Sync + 'static,
344    {
345        if self.closed || self.ordinary_closed {
346            return Err(ServingError::Closing);
347        }
348        let staged = Self::of::<T, R>(resources, self.capacity)?;
349        self.extend(staged)
350    }
351
352    #[cfg(feature = "streaming")]
353    pub fn bind_streaming_operations<T, R>(&mut self, resources: Arc<R>, limits: WireStreamLimits) -> Result<(), ServingError>
354    where
355        T: Catalog<AuthoredDeclarations> + Catalog<Serves<R, C>>,
356        R: Send + Sync + 'static,
357    {
358        if self.closed || self.ordinary_closed {
359            return Err(ServingError::Closing);
360        }
361        limits.validate()?;
362        let mut staged = Self::new(self.capacity);
363        staged.stream_limits = Some(limits);
364        let staged = Self::project::<T, R>(staged, resources)?;
365        self.extend(staged)
366    }
367
368    fn extend(&mut self, mut staged: Self) -> Result<(), ServingError> {
369        for id in staged.handlers.keys() {
370            if self.handlers.contains_key(id) {
371                return Err(crate::Error::DuplicateOperation { id: id.clone() }.into());
372            }
373        }
374        self.handlers.append(&mut staged.handlers);
375        Ok(())
376    }
377
378    pub fn definitions(&self) -> Vec<Definition> {
379        self.handlers.values().filter(|handler| !handler.builtin).map(|handler| handler.definition.clone()).collect()
380    }
381
382    pub fn validate(&self, definitions: &[Definition]) -> Result<(), ServingError> {
383        if definitions.len() != self.handlers.values().filter(|handler| !handler.builtin).count() {
384            return Err(ServingError::Catalog);
385        }
386        for definition in definitions {
387            if self.handlers.get(definition.id.as_str()).is_none_or(|bound| bound.builtin || bound.definition != *definition) {
388                return Err(ServingError::Catalog);
389            }
390        }
391        let unique: std::collections::BTreeSet<_> = definitions.iter().map(|definition| &definition.id).collect();
392        if unique.len() != definitions.len() {
393            return Err(ServingError::Catalog);
394        }
395        Ok(())
396    }
397
398    pub fn validate_contracts(&self, contracts: &[Contract]) -> Result<(), ServingError> {
399        if contracts.len() != self.handlers.values().filter(|handler| !handler.builtin).count() {
400            return Err(ServingError::Catalog);
401        }
402        let mut unique = std::collections::BTreeSet::new();
403        for contract in contracts {
404            let id = &contract.operation().id;
405            if !unique.insert(id)
406                || self.handlers.get(id.as_str()).is_none_or(|bound| bound.builtin || bound.contract.as_ref() != Some(contract))
407            {
408                return Err(ServingError::Catalog);
409            }
410        }
411        Ok(())
412    }
413
414    // Answers the request's grant as the Prepare payload.
415    pub fn prepare(&mut self, invocation: u64, operation: &str, payload: Vec<u8>) -> Result<Vec<u8>, ServingError> {
416        self.prepare_codec(invocation, operation, payload, false)
417    }
418
419    fn prepare_codec(&mut self, invocation: u64, operation: &str, payload: Vec<u8>, grpc: bool) -> Result<Vec<u8>, ServingError> {
420        if self.closed {
421            return Err(ServingError::Closing);
422        }
423        self.prepared.retain(|_, slot| !slot.is_finished());
424        if self.prepared.contains_key(&invocation) {
425            return Err(ServingError::Duplicate { invocation });
426        }
427        let handler = self.handlers.get(operation).ok_or_else(|| ServingError::Unknown { operation: operation.to_owned() })?;
428        if self.ordinary_closed && !handler.lifecycle {
429            return Err(ServingError::Closing);
430        }
431        let occupied = self.prepared.values().filter(|slot| slot.lifecycle == handler.lifecycle && !slot.resident).count();
432        // One accounted reserved lifecycle slot remains available even when
433        // ordinary admission is full. Failed SDK finalization retains that slot.
434        let capacity = if handler.lifecycle { 1 } else { self.capacity.get() };
435        if occupied >= capacity && !handler.resident {
436            return Err(ServingError::Capacity);
437        }
438        let prepared = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (handler.prepare)(payload, grpc)))
439            .map_err(|_| ServingError::Panicked)??;
440        let answer = encode_prepared_grant(prepared.grant().as_ref());
441        let slot = Slot {
442            lifecycle: handler.lifecycle,
443            resident: handler.resident,
444            #[cfg(feature = "streaming")]
445            streams: prepared.streams(),
446            state: Mutex::new(SlotState { prepared: Some(prepared), finished: false, waiters: Vec::new() }),
447        };
448        self.prepared.insert(invocation, Arc::new(slot));
449        Ok(answer)
450    }
451
452    #[cfg(feature = "streaming")]
453    pub fn streams(&self, invocation: u64) -> Result<WireStreams, ServingError> {
454        let slot = self.prepared.get(&invocation).ok_or(ServingError::Missing { invocation })?;
455        if self.closed || (self.ordinary_closed && !slot.lifecycle) || slot.is_finished() {
456            return Err(ServingError::Closing);
457        }
458        slot.streams.clone().ok_or(ServingError::StreamPhase)
459    }
460
461    #[tracing::instrument(target = "harmos::streams", name = "streams.initialize", level = "trace", skip_all, fields(invocation))]
462    pub async fn initialize(&mut self, invocation: u64, context: &C) -> Result<(), ServingError> {
463        if self.closed {
464            return Err(ServingError::Closing);
465        }
466        let slot = self.prepared.get(&invocation).ok_or(ServingError::Missing { invocation })?;
467        if self.ordinary_closed && !slot.lifecycle {
468            return Err(ServingError::Closing);
469        }
470        slot.initialize(invocation, context).await
471    }
472
473    #[tracing::instrument(target = "harmos::streams", name = "streams.attempt", level = "trace", skip_all, fields(invocation))]
474    pub async fn attempt(&mut self, invocation: u64, context: &C) -> Result<Vec<u8>, ServingError> {
475        if self.closed {
476            return Err(ServingError::Closing);
477        }
478        let slot = self.prepared.get(&invocation).ok_or(ServingError::Missing { invocation })?;
479        if self.ordinary_closed && !slot.lifecycle {
480            return Err(ServingError::Closing);
481        }
482        slot.attempt(invocation, context, None).await
483    }
484
485    #[tracing::instrument(target = "harmos::streams", name = "streams.finalize", level = "trace", skip_all, fields(invocation))]
486    pub async fn finalize(&mut self, invocation: u64) -> Result<(), ServingError> {
487        if let Some(slot) = self.prepared.get(&invocation) {
488            slot.finalize().await?;
489        }
490        self.prepared.remove(&invocation);
491        Ok(())
492    }
493
494    fn traffic(&self, command: WireCommand) -> Pin<Box<dyn Future<Output = WireAnswer> + Send + 'static>> {
495        #[cfg(not(feature = "streaming"))]
496        {
497            let _ = command;
498            Box::pin(async { wire_answer(Err(ServingError::StreamUnsupported)) })
499        }
500        #[cfg(feature = "streaming")]
501        {
502            let invocation = match &command {
503                WireCommand::StreamSend { invocation, .. }
504                | WireCommand::StreamFinish { invocation, .. }
505                | WireCommand::StreamReceive { invocation, .. } => *invocation,
506                _ => unreachable!("only stream traffic bypasses the phase loan"),
507            };
508            let streams = self.streams(invocation);
509            Box::pin(async move {
510                let streams = match streams {
511                    Ok(streams) => streams,
512                    Err(error) => return wire_answer(Err(error)),
513                };
514                match command {
515                    WireCommand::StreamSend { epoch, payload, .. } => {
516                        wire_answer(guarded(async { streams.send(epoch, &payload).await.map(|()| Vec::new()) }).await)
517                    }
518                    WireCommand::StreamFinish { epoch, .. } => wire_answer(streams.finish_input(epoch).map(|()| Vec::new())),
519                    WireCommand::StreamReceive { epoch, .. } => match guarded(streams.receive(epoch)).await {
520                        Ok(payload) => WireAnswer::Stream { epoch, transferred: streams.transferred(), payload },
521                        Err(error) => wire_answer(Err(error)),
522                    },
523                    _ => unreachable!("only stream traffic bypasses the phase loan"),
524                }
525            })
526        }
527    }
528
529    pub fn begin(&mut self, command: WireCommand, context: Arc<C>) -> Pin<Box<dyn Future<Output = WireAnswer> + Send + 'static>> {
530        let (invocation, phase) = command_identity(&command);
531        let span = tracing::debug_span!(target: "harmos::carrier", "carrier.receive", invocation, phase, outcome = tracing::field::Empty);
532        if command.is_stream_traffic() {
533            return Box::pin(self.traffic(command).instrument(span).in_current_context());
534        }
535        let slot = match &command {
536            WireCommand::Initialize { invocation, .. }
537            | WireCommand::Attempt { invocation, .. }
538            | WireCommand::StreamAttempt { invocation, .. }
539            | WireCommand::Finalize { invocation } => self.prepared.get(invocation).cloned(),
540            WireCommand::Prepare { .. } | WireCommand::PrepareGrpc { .. } => None,
541            WireCommand::StreamSend { .. } | WireCommand::StreamFinish { .. } | WireCommand::StreamReceive { .. } => {
542                unreachable!("stream traffic already dispatched")
543            }
544        };
545        let command = match command {
546            WireCommand::PrepareGrpc { invocation, operation, payload } => {
547                let result = span.in_scope(|| self.prepare_codec(invocation, &operation, payload, true));
548                return Box::pin(async move { wire_answer(result) }.instrument(span).in_current_context());
549            }
550            WireCommand::Prepare { invocation, operation, payload } => {
551                let result = span.in_scope(|| self.prepare(invocation, &operation, payload));
552                return Box::pin(async move { wire_answer(result) }.instrument(span).in_current_context());
553            }
554            command => command,
555        };
556        let refused = self.closed || (self.ordinary_closed && slot.as_ref().is_some_and(|slot| !slot.lifecycle));
557        if refused && matches!(command, WireCommand::Initialize { .. } | WireCommand::Attempt { .. } | WireCommand::StreamAttempt { .. }) {
558            return Box::pin(async { wire_answer(Err(ServingError::Closing)) }.instrument(span).in_current_context());
559        }
560        Box::pin(
561            async move {
562                let result = match command {
563                    WireCommand::Initialize { invocation, .. } => match slot {
564                        Some(slot) => slot.initialize(invocation, &context).await.map(|()| Vec::new()),
565                        None => Err(ServingError::Missing { invocation }),
566                    },
567                    WireCommand::Attempt { invocation, .. } => match slot {
568                        Some(slot) => slot.attempt(invocation, &context, None).await,
569                        None => Err(ServingError::Missing { invocation }),
570                    },
571                    WireCommand::StreamAttempt { invocation, epoch, .. } => match slot {
572                        Some(slot) => slot.attempt(invocation, &context, Some(epoch)).await,
573                        None => Err(ServingError::Missing { invocation }),
574                    },
575                    WireCommand::StreamSend { .. } | WireCommand::StreamFinish { .. } | WireCommand::StreamReceive { .. } => {
576                        unreachable!("stream traffic already dispatched")
577                    }
578                    WireCommand::Finalize { .. } => match slot {
579                        Some(slot) => slot.finalize().await.map(|()| Vec::new()),
580                        None => Ok(Vec::new()),
581                    },
582                    WireCommand::Prepare { .. } | WireCommand::PrepareGrpc { .. } => {
583                        unreachable!("preparation is synchronous")
584                    }
585                };
586                wire_answer(result)
587            }
588            .instrument(span)
589            .in_current_context(),
590        )
591    }
592
593    pub fn close_ordinary(&mut self) -> Pin<Box<dyn Future<Output = Result<(), ServingError>> + Send + 'static>> {
594        self.ordinary_closed = true;
595        let slots: Vec<_> = self.prepared.values().filter(|slot| !slot.lifecycle).cloned().collect();
596        #[cfg(feature = "streaming")]
597        for slot in &slots {
598            if let Some(streams) = &slot.streams {
599                streams.close();
600            }
601        }
602        Box::pin(async move {
603            for slot in slots {
604                slot.finalize().await?;
605            }
606            Ok(())
607        })
608    }
609
610    pub fn release_ordinary(&mut self) -> Result<impl Send + use<C>, ServingError> {
611        if !self.ordinary_closed || self.prepared.values().any(|slot| !slot.lifecycle && !slot.is_finished()) {
612            return Err(ServingError::Unsettled);
613        }
614        // Hand Off Retired Owners So The Caller Can Drop Them Outside Its Lock
615        let retired_slots = self.prepared.extract_if(.., |_, slot| !slot.lifecycle).collect::<Vec<_>>();
616        let retired_handlers = self.handlers.extract_if(.., |_, handler| !handler.lifecycle).collect::<Vec<_>>();
617        Ok((retired_slots, retired_handlers))
618    }
619
620    pub async fn settle_ordinary(&mut self) -> Result<(), ServingError> {
621        self.close_ordinary().await?;
622        let retired = self.release_ordinary()?;
623        drop(retired);
624        Ok(())
625    }
626
627    pub fn close(&mut self) -> Pin<Box<dyn Future<Output = Result<(), ServingError>> + Send + 'static>> {
628        self.closed = true;
629        let slots: Vec<_> = self.prepared.values().cloned().collect();
630        #[cfg(feature = "streaming")]
631        for slot in &slots {
632            if let Some(streams) = &slot.streams {
633                streams.close();
634            }
635        }
636        Box::pin(async move {
637            for slot in slots {
638                slot.finalize().await?;
639            }
640            Ok(())
641        })
642    }
643
644    pub fn release(&mut self) -> Result<impl Send + use<C>, ServingError> {
645        if !self.closed || self.prepared.values().any(|slot| !slot.is_finished()) {
646            return Err(ServingError::Unsettled);
647        }
648        Ok((std::mem::take(&mut self.prepared), std::mem::take(&mut self.handlers)))
649    }
650
651    pub async fn settle(&mut self) -> Result<(), ServingError> {
652        self.close().await?;
653        let retired = self.release()?;
654        drop(retired);
655        Ok(())
656    }
657
658    pub fn is_settled(&self) -> bool {
659        self.closed && self.prepared.values().all(|slot| slot.is_finished())
660    }
661
662    pub fn is_closed(&self) -> bool {
663        self.closed
664    }
665
666    #[tracing::instrument(target = "harmos::carrier", name = "carrier.answer", level = "debug", skip_all, fields(invocation = command_identity(&command).0, phase = command_identity(&command).1))]
667    pub async fn answer(&mut self, command: WireCommand, context: &C) -> WireAnswer {
668        if command.is_stream_traffic() {
669            return self.traffic(command).await;
670        }
671        if let WireCommand::StreamAttempt { invocation, epoch, .. } = command {
672            if self.closed || (self.ordinary_closed && self.prepared.get(&invocation).is_some_and(|slot| !slot.lifecycle)) {
673                return wire_answer(Err(ServingError::Closing));
674            }
675            let Some(slot) = self.prepared.get(&invocation) else {
676                return wire_answer(Err(ServingError::Missing { invocation }));
677            };
678            return wire_answer(slot.attempt(invocation, context, Some(epoch)).await);
679        }
680        let result = match command {
681            WireCommand::PrepareGrpc { invocation, operation, payload } => self.prepare_codec(invocation, &operation, payload, true),
682            WireCommand::Prepare { invocation, operation, payload } => self.prepare(invocation, &operation, payload),
683            WireCommand::Initialize { invocation, .. } => self.initialize(invocation, context).await.map(|()| Vec::new()),
684            WireCommand::Attempt { invocation, .. } => self.attempt(invocation, context).await,
685            WireCommand::StreamAttempt { .. }
686            | WireCommand::StreamSend { .. }
687            | WireCommand::StreamFinish { .. }
688            | WireCommand::StreamReceive { .. } => {
689                unreachable!("stream command already dispatched")
690            }
691            WireCommand::Finalize { invocation } => self.finalize(invocation).await.map(|()| Vec::new()),
692        };
693        wire_answer(result)
694    }
695}
696
697impl<C> Slot<C> {
698    fn is_finished(&self) -> bool {
699        self.state.lock().expect("invocation state lock").finished
700    }
701
702    async fn borrow(self: &Arc<Self>) -> Option<Loan<C>> {
703        let waiter = Arc::new(Mutex::new(None));
704        std::future::poll_fn(|context| {
705            let mut state = self.state.lock().expect("invocation state lock");
706            if state.finished {
707                return Poll::Ready(None);
708            }
709            if let Some(prepared) = state.prepared.take() {
710                return Poll::Ready(Some(Loan { slot: Arc::clone(self), prepared: Some(prepared), finished: false }));
711            }
712            *waiter.lock().expect("invocation waiter lock") = Some(context.waker().clone());
713            state.waiters.retain(|entry| entry.strong_count() != 0);
714            if !state.waiters.iter().any(|entry| entry.ptr_eq(&Arc::downgrade(&waiter))) {
715                state.waiters.push(Arc::downgrade(&waiter));
716            }
717            Poll::Pending
718        })
719        .await
720    }
721}
722
723impl<C: Send + Sync + 'static> Slot<C> {
724    #[tracing::instrument(target = "harmos::streams", name = "streams.initialize", level = "trace", skip_all, fields(invocation))]
725    async fn initialize(self: &Arc<Self>, invocation: u64, context: &C) -> Result<(), ServingError> {
726        let mut loan = self.borrow().await.ok_or(ServingError::Missing { invocation })?;
727        loan.prepared.as_mut().expect("loaned invocation state").initialize(context).await
728    }
729
730    #[tracing::instrument(target = "harmos::streams", name = "streams.attempt", level = "trace", skip_all, fields(invocation))]
731    async fn attempt(self: &Arc<Self>, invocation: u64, context: &C, epoch: Option<u64>) -> Result<Vec<u8>, ServingError> {
732        let mut loan = self.borrow().await.ok_or(ServingError::Missing { invocation })?;
733        #[cfg(feature = "streaming")]
734        let attempt = match (&self.streams, epoch) {
735            (Some(streams), Some(epoch)) => Some(streams.start(epoch)?),
736            (None, None) => None,
737            _ => return Err(ServingError::StreamUnsupported),
738        };
739        #[cfg(not(feature = "streaming"))]
740        if epoch.is_some() {
741            return Err(ServingError::StreamUnsupported);
742        }
743        let result = loan.prepared.as_mut().expect("loaned invocation state").attempt(context).await;
744        #[cfg(feature = "streaming")]
745        if let Some(attempt) = attempt {
746            attempt.end();
747        }
748        result
749    }
750
751    #[tracing::instrument(target = "harmos::carrier", name = "carrier.finalize", level = "debug", skip_all)]
752    async fn finalize(self: &Arc<Self>) -> Result<(), ServingError> {
753        #[cfg(feature = "streaming")]
754        if let Some(streams) = &self.streams {
755            streams.close();
756        }
757        let Some(mut loan) = self.borrow().await else {
758            return Ok(());
759        };
760        loan.prepared.as_mut().expect("loaned invocation state").finalize().await?;
761        // Drop Physical State Before Releasing Its Capacity
762        loan.finished = true;
763        let prepared = loan.prepared.take();
764        drop(prepared);
765        Ok(())
766    }
767}
768
769impl<C> Drop for Loan<C> {
770    fn drop(&mut self) {
771        let waiters = {
772            let mut state = self.slot.state.lock().expect("invocation state lock");
773            state.prepared = self.prepared.take();
774            state.finished = self.finished;
775            std::mem::take(&mut state.waiters)
776        };
777        for waiter in waiters {
778            if let Some(waiter) = waiter.upgrade() {
779                let wake = waiter.lock().expect("invocation waiter lock").take();
780                if let Some(wake) = wake {
781                    wake.wake();
782                }
783            }
784        }
785    }
786}
787
788impl WireCommand {
789    pub const LIMIT: usize = codec::LIMIT;
790    fn is_stream_traffic(&self) -> bool {
791        matches!(self, Self::StreamSend { .. } | Self::StreamFinish { .. } | Self::StreamReceive { .. })
792    }
793}
794
795impl ServingError {
796    pub fn classification(&self) -> FailureKind {
797        match self {
798            Self::Handler(failure) => failure.code(),
799            Self::Panicked => FailureKind::Ambiguous,
800            Self::Codec(_) | Self::Declaration(_) | Self::Lifecycle(_) | Self::Contract(_) | Self::Catalog | Self::Streaming { .. } => {
801                FailureKind::Invalid
802            }
803            _ => FailureKind::Permanent,
804        }
805    }
806}
807
808impl<C: Send + Sync + 'static, H: Send + Sync + 'static> Visitor for Serves<H, C> {
809    type Entry = WireBinding<H, C>;
810}
811
812#[cfg(not(feature = "streaming"))]
813impl<T, R, C> Member<T> for Serves<R, C>
814where
815    C: Send + Sync + 'static,
816    R: Send + Sync + 'static,
817    T: for<'a> Operation<Resources = R, Context<'a> = &'a C, Endpoints = ()>,
818{
819    const ENTRY: WireBinding<R, C> = WireBinding { install: WireTable::<C>::bind_operation::<T> };
820}
821
822#[cfg(feature = "streaming")]
823impl<T, R, C> Member<T> for Serves<R, C>
824where
825    C: Send + Sync + 'static,
826    R: Send + Sync + 'static,
827    T: for<'a> Operation<Resources = R, Context<'a> = &'a C>,
828    T::Endpoints: WireEndpoints,
829{
830    const ENTRY: WireBinding<R, C> = WireBinding { install: WireTable::<C>::bind_catalog_operation::<T> };
831}
832
833/* === Public Functions === */
834
835// A Prepare answer is empty when the request needs no grant, else the grant as JSON.
836pub fn encode_prepared_grant(grant: Option<&crate::plugin::Grant>) -> Vec<u8> {
837    grant.map_or_else(Vec::new, |grant| serde_json::to_vec(grant).expect("a grant serializes to JSON"))
838}
839
840pub fn decode_prepared_grant(payload: &[u8]) -> Result<Option<crate::plugin::Grant>, serde_json::Error> {
841    if payload.is_empty() {
842        return Ok(None);
843    }
844    serde_json::from_slice(payload).map(Some)
845}
846
847/* === Private Functions === */
848
849fn command_identity(command: &WireCommand) -> (u64, &'static str) {
850    match command {
851        WireCommand::Prepare { invocation, .. } => (*invocation, "prepare"),
852        WireCommand::PrepareGrpc { invocation, .. } => (*invocation, "prepare_grpc"),
853        WireCommand::Initialize { invocation, .. } => (*invocation, "initialize"),
854        WireCommand::Attempt { invocation, .. } => (*invocation, "attempt"),
855        WireCommand::Finalize { invocation, .. } => (*invocation, "finalize"),
856        WireCommand::StreamAttempt { invocation, .. } => (*invocation, "stream_attempt"),
857        WireCommand::StreamSend { invocation, .. } => (*invocation, "stream_send"),
858        WireCommand::StreamFinish { invocation, .. } => (*invocation, "stream_finish"),
859        WireCommand::StreamReceive { invocation, .. } => (*invocation, "stream_receive"),
860    }
861}
862
863fn wire_answer(result: Result<Vec<u8>, ServingError>) -> WireAnswer {
864    tracing::Span::current().record("outcome", if result.is_ok() { "ready" } else { "failed" });
865    match result {
866        Ok(payload) => WireAnswer::Ready { payload },
867        Err(ServingError::Handler(failure)) => WireAnswer::Failed { failure },
868        Err(error) => {
869            WireAnswer::Failed { failure: crate::Failure::originate(error.classification(), &error, crate::failure::Origin::Plugin) }
870        }
871    }
872}
873
874async fn guarded<T>(future: impl Future<Output = Result<T, ServingError>>) -> Result<T, ServingError> {
875    let mut future = std::pin::pin!(future);
876    std::future::poll_fn(|context| match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.as_mut().poll(context))) {
877        Ok(result) => result,
878        Err(_) => std::task::Poll::Ready(Err(ServingError::Panicked)),
879    })
880    .await
881}
882
883/* === Tests === */