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 configuration;
14mod owned;
15#[cfg(feature = "grpc")]
16pub(crate) mod protobuf;
17#[cfg(feature = "streaming")]
18mod streams;
19
20/* === Re-Exports === */
21
22pub use codec::WireCodecError;
23#[cfg(feature = "streaming")]
24pub use streams::{WireEndpoints, WireStreamLimits, WireStreams};
25
26/* === Imports === */
27
28// Standard Library Imports
29use std::collections::BTreeMap;
30use std::future::Future;
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::{Contract, ContractError, ExecutionContext, FailureKind, OperationDefinition, RegistrationBatch, StartupError};
40use crate::QualifiedId;
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 = Box<dyn Fn(Vec<u8>, bool) -> Result<Box<dyn Prepared>, ServingError> + Send + Sync>;
56
57/* === Structs === */
58
59struct Contextual<F: std::future::Future> {
60    future: Option<std::pin::Pin<Box<F>>>,
61    dispatcher: tracing::Dispatch,
62}
63
64#[derive(Debug, Serialize, Deserialize)]
65#[serde(deny_unknown_fields)]
66pub struct WireChildCall {
67    pub context: [u8; 32],
68
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub generation_context: Option<[u8; 32]>,
71
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub values: Vec<super::CallValue>,
74
75    pub operation: String,
76    pub fingerprint: [u8; 32],
77    pub generation: u64,
78    pub payload: Vec<u8>,
79
80    // Submitted children answer their instance id at admission and remain
81    // owned by the calling attempt until joined, released or detached.
82    #[serde(default)]
83    pub submit: bool,
84}
85
86// Drives one child the same attempt submitted; the host resolves the instance
87// only inside the attempt context that submitted it.
88#[derive(Debug, Serialize, Deserialize)]
89#[serde(deny_unknown_fields)]
90pub struct WireChildControl {
91    pub context: [u8; 32],
92    pub instance: u64,
93    pub action: WireChildAction,
94}
95
96pub struct WireTable {
97    owner: String,
98    resources: super::ResourceSet,
99    resources_frozen: bool,
100    trusted_resources: super::ResourceSet,
101    pending: Vec<RegistrationBatch>,
102    catalogs: std::collections::HashSet<std::any::TypeId>,
103    configuration_errors: Vec<StartupError>,
104    binding_failed: bool,
105    handlers: BTreeMap<String, Route>,
106    retired: BTreeMap<String, u64>,
107    services: BTreeMap<String, super::registration::ServiceFactory>,
108    prepared: BTreeMap<u64, Arc<Slot>>,
109    capacity: Option<NonZeroUsize>,
110    closed: bool,
111    ordinary_closed: bool,
112    provided: Vec<std::any::TypeId>,
113
114    #[cfg(feature = "streaming")]
115    stream_limits: Option<WireStreamLimits>,
116}
117
118struct Slot {
119    operation: String,
120    lifecycle: bool,
121    resident: bool,
122
123    #[cfg(feature = "streaming")]
124    streams: Option<WireStreams>,
125
126    state: Mutex<SlotState>,
127}
128
129struct SlotState {
130    prepared: Option<Box<dyn Prepared>>,
131    finished: bool,
132    cancelled: bool,
133    waiters: Vec<Weak<Mutex<Option<Waker>>>>,
134}
135
136struct Loan {
137    slot: Arc<Slot>,
138    prepared: Option<Box<dyn Prepared>>,
139    finished: bool,
140}
141
142struct Route {
143    callees: &'static [super::WireDeclaration],
144    lifecycle: bool,
145    // Lifecycle actions and the observe control are built in: they serve the
146    // plugin's machinery and never appear in its declared catalog.
147    builtin: bool,
148    // A resident control runs until cancelled and is not charged against the
149    // ordinary invocation capacity.
150    resident: bool,
151    definition: OperationDefinition,
152    generation: Option<u64>,
153    closing: bool,
154    contract: Option<Contract>,
155    prepare: Prepare,
156}
157
158/* === Enums === */
159
160// Answers: `Join` the child's response payload, `Status` and `Cancel` an
161// encoded instance state, `Release` and `Detach` an empty payload.
162// Releasing cancels the child; detaching leaves it running without a handle.
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165pub enum WireChildAction {
166    Join,
167    Status,
168    Cancel,
169    Release,
170    Detach,
171}
172
173#[derive(Debug)]
174pub enum WireChild {
175    Call(WireChildCall),
176    Control(WireChildControl),
177}
178
179#[derive(Debug, Serialize, Deserialize)]
180#[serde(tag = "phase", rename_all = "snake_case", deny_unknown_fields)]
181pub enum WireCommand {
182    Prepare { invocation: u64, operation: String, fingerprint: [u8; 32], generation: u64, payload: Vec<u8> },
183    Initialize { invocation: u64, context: [u8; 32] },
184    Attempt { invocation: u64, context: [u8; 32], values: Vec<super::CallValue> },
185    Finalize { invocation: u64 },
186    StreamAttempt { invocation: u64, epoch: u64, context: [u8; 32], values: Vec<super::CallValue> },
187    StreamSend { invocation: u64, epoch: u64, payload: Vec<u8> },
188    StreamFinish { invocation: u64, epoch: u64 },
189    StreamReceive { invocation: u64, epoch: u64 },
190    PrepareGrpc { invocation: u64, operation: String, fingerprint: [u8; 32], generation: u64, payload: Vec<u8> },
191}
192
193#[derive(Debug, Serialize, Deserialize)]
194#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
195pub enum WireAnswer {
196    Stream { epoch: u64, transferred: bool, payload: Option<Vec<u8>> },
197    Ready { payload: Vec<u8> },
198    Failed { failure: crate::Failure },
199}
200
201#[derive(Debug, thiserror::Error)]
202pub enum ServingError {
203    #[error(transparent)]
204    Startup(#[from] Box<StartupError>),
205
206    #[error("the carrier resource store is frozen")]
207    StoreFrozen,
208
209    #[error("caller may not invoke operation `{operation}` without a host assignment")]
210    Unassigned { operation: &'static str },
211
212    #[error("operation endpoint types differ from the declaration")]
213    EndpointTypes,
214
215    #[error("carrier invocation does not support stream commands")]
216    StreamUnsupported,
217
218    #[cfg(feature = "streaming")]
219    #[error("streaming catalog requires explicit provider stream limits")]
220    StreamLimits,
221
222    #[cfg(feature = "streaming")]
223    #[error("carrier stream failed: {0}")]
224    Stream(#[source] super::streaming::Error),
225
226    #[cfg(feature = "streaming")]
227    #[error("carrier stream direction already has an outstanding request")]
228    StreamBusy,
229
230    #[cfg(feature = "streaming")]
231    #[error("carrier stream direction or attempt is not available")]
232    StreamPhase,
233
234    #[error(transparent)]
235    Lifecycle(#[from] harmos_domain::model::operations::lifecycle::Error),
236
237    #[error(transparent)]
238    Contract(#[from] ContractError),
239
240    #[error(transparent)]
241    Declaration(#[from] crate::Error),
242
243    #[error("operation `{operation}` declares resource `{resource}` that its registration does not provide")]
244    Resource { operation: String, resource: &'static str },
245
246    #[error("operation `{operation}` has no bound carrier handler")]
247    Unknown { operation: String },
248
249    #[error("operation `{operation}` requires streaming endpoints unsupported by this carrier binding")]
250    Streaming { operation: String },
251
252    #[error("operation `{operation}` has a mismatched fingerprint or stale generation")]
253    Identity { operation: String },
254
255    #[error("carrier invocation `{invocation}` is already prepared")]
256    Duplicate { invocation: u64 },
257
258    #[error("carrier invocation `{invocation}` has not been prepared")]
259    Missing { invocation: u64 },
260
261    #[error("carrier operation has not completed initialization")]
262    Uninitialized,
263
264    #[error("carrier operation initialization already started and cannot be replayed")]
265    InitializationStarted,
266
267    #[error("carrier invocation capacity is exhausted")]
268    Capacity,
269
270    #[error("carrier ordinary invocations have not finished settlement")]
271    Unsettled,
272
273    #[error("carrier operation admission is closing")]
274    Closing,
275
276    #[error("carrier handler definitions differ from the selected operation catalog")]
277    Catalog,
278
279    #[error("operation payload codec failed")]
280    Codec(#[source] crate::MessageError),
281
282    #[error("carrier handler panicked")]
283    Panicked,
284
285    // The handler's failure, written once at its origin.
286    #[error(transparent)]
287    Handler(crate::Failure),
288
289    #[error("native handler failed: {0}")]
290    Native(#[source] std::sync::Arc<dyn std::error::Error + Send + Sync>),
291}
292
293/* === Traits === */
294
295trait InContext: std::future::Future + Sized {
296    fn in_context(self, dispatcher: tracing::Dispatch) -> Contextual<Self> {
297        Contextual { future: Some(Box::pin(self)), dispatcher }
298    }
299
300    fn in_current_context(self) -> Contextual<Self> {
301        self.in_context(tracing::dispatcher::get_default(Clone::clone))
302    }
303}
304
305trait Prepared: Send {
306    #[cfg(feature = "streaming")]
307    fn streams(&self) -> Option<WireStreams> {
308        None
309    }
310
311    // The grant the prepared request needs, answered to the host with Prepare.
312    fn grant(&self) -> Option<crate::plugin::Grant> {
313        None
314    }
315    fn bind<'a>(&'a mut self, context: &'a ExecutionContext) -> Finished<'a>;
316    fn attempt<'a>(&'a mut self, context: &'a ExecutionContext) -> Answer<'a>;
317    fn finalize(&mut self) -> Finished<'_>;
318}
319
320/* === Implementations === */
321
322impl<F: std::future::Future> InContext for F {}
323
324impl<F: std::future::Future> std::future::Future for Contextual<F> {
325    type Output = F::Output;
326
327    fn poll(self: std::pin::Pin<&mut Self>, context: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
328        let owner = self.get_mut();
329        let _dispatcher = tracing::dispatcher::set_default(&owner.dispatcher);
330        owner.future.as_mut().expect("context future remains until drop").as_mut().poll(context)
331    }
332}
333
334impl<F: std::future::Future> Drop for Contextual<F> {
335    fn drop(&mut self) {
336        tracing::dispatcher::with_default(&self.dispatcher, || drop(self.future.take()));
337    }
338}
339
340impl WireTable {
341    pub fn new(owner: &str) -> Result<Self, ServingError> {
342        if !harmos_domain::identity::segment(owner) {
343            return Err(ServingError::Catalog);
344        }
345        Ok(Self {
346            owner: owner.to_owned(),
347            resources: super::ResourceSet::new(),
348            resources_frozen: false,
349            trusted_resources: super::ResourceSet::new(),
350            pending: Vec::new(),
351            catalogs: std::collections::HashSet::new(),
352            configuration_errors: Vec::new(),
353            binding_failed: false,
354            services: BTreeMap::new(),
355            handlers: BTreeMap::new(),
356            retired: BTreeMap::new(),
357            prepared: BTreeMap::new(),
358            capacity: None,
359            closed: false,
360            ordinary_closed: false,
361            provided: Vec::new(),
362            #[cfg(feature = "streaming")]
363            stream_limits: None,
364        })
365    }
366
367    // The Carrier Supplies These Resources To Every Attempt's Execution Context.
368    pub fn provide(&mut self, resource: std::any::TypeId) {
369        self.provided.push(resource);
370    }
371
372    pub fn provided(&self) -> &[std::any::TypeId] {
373        &self.provided
374    }
375
376    pub fn capacity(&mut self, capacity: NonZeroUsize) {
377        self.capacity = Some(capacity);
378    }
379
380    #[cfg(feature = "streaming")]
381    pub fn stream_limits(&mut self, limits: WireStreamLimits) -> Result<(), ServingError> {
382        limits.validate()?;
383        self.stream_limits = Some(limits);
384        Ok(())
385    }
386
387    #[doc(hidden)]
388    pub fn register(&mut self, batch: RegistrationBatch) -> Result<(), ServingError> {
389        if self.closed || self.ordinary_closed {
390            return Err(ServingError::Closing);
391        }
392        if !self.resources_frozen {
393            self.pending.push(batch);
394            return Ok(());
395        }
396        self.register_now(batch)
397    }
398
399    fn register_now(&mut self, batch: RegistrationBatch) -> Result<(), ServingError> {
400        if self.closed || self.ordinary_closed {
401            return Err(ServingError::Closing);
402        }
403        let mut staged = Self::new(&self.owner)?;
404        staged.resources = self.resources.clone();
405        staged.trusted_resources = self.trusted_resources.clone();
406        staged.capacity = self.capacity;
407        staged.provided.clone_from(&self.provided);
408        #[cfg(feature = "streaming")]
409        {
410            staged.stream_limits = self.stream_limits;
411        }
412        for factory in batch.operations {
413            factory.install(&mut staged)?;
414        }
415        for factory in batch.services {
416            factory.policy.validate().map_err(|_| ServingError::Catalog)?;
417            let id = QualifiedId::qualify(&self.owner, &factory.local_id).map_err(|_| ServingError::Catalog)?;
418            let id = id.as_str().to_owned();
419            if staged.handlers.contains_key(&id) || staged.services.insert(id, factory).is_some() {
420                return Err(ServingError::Catalog);
421            }
422        }
423        if staged.handlers.keys().chain(staged.services.keys()).any(|id| self.handlers.contains_key(id) || self.services.contains_key(id)) {
424            return Err(ServingError::Catalog);
425        }
426        self.handlers.append(&mut staged.handlers);
427        self.services.append(&mut staged.services);
428        self.resources_frozen = true;
429        Ok(())
430    }
431
432    pub async fn unregister(&mut self, id: &QualifiedId) -> Result<(), ServingError> {
433        let key = id.as_str();
434        if !self.handlers.contains_key(key) && !self.services.contains_key(key) {
435            return Err(ServingError::Unknown { operation: key.to_owned() });
436        }
437        if let Some(handler) = self.handlers.get_mut(key) {
438            handler.closing = true;
439        }
440        let slots: Vec<_> =
441            self.prepared.iter().filter(|(_, slot)| slot.operation == key).map(|(invocation, slot)| (*invocation, slot.clone())).collect();
442        for (_, slot) in &slots {
443            slot.cancel();
444        }
445        for (_, slot) in &slots {
446            slot.finalize().await?;
447        }
448        for (invocation, _) in slots {
449            self.prepared.remove(&invocation);
450        }
451        if let Some(handler) = self.handlers.remove(key)
452            && let Some(generation) = handler.generation
453        {
454            self.retired.insert(key.to_owned(), generation);
455        }
456        self.services.remove(key);
457        Ok(())
458    }
459
460    pub fn definitions(&self) -> Vec<OperationDefinition> {
461        self.handlers.values().filter(|handler| !handler.builtin).map(|handler| handler.definition.clone()).collect()
462    }
463
464    pub fn validate(&self, definitions: &[OperationDefinition]) -> Result<(), ServingError> {
465        if definitions.len() != self.handlers.values().filter(|handler| !handler.builtin).count() {
466            return Err(ServingError::Catalog);
467        }
468        for definition in definitions {
469            if self
470                .handlers
471                .get(QualifiedId::qualify(&self.owner, definition.id.as_str()).map_err(|_| ServingError::Catalog)?.as_str())
472                .is_none_or(|bound| bound.builtin || bound.definition != *definition)
473            {
474                return Err(ServingError::Catalog);
475            }
476        }
477        let unique: std::collections::BTreeSet<_> = definitions.iter().map(|definition| &definition.id).collect();
478        if unique.len() != definitions.len() {
479            return Err(ServingError::Catalog);
480        }
481        Ok(())
482    }
483
484    pub fn validate_callees(&self, assignments: &[u8]) -> Result<(), ServingError> {
485        let assignments: Vec<super::AssignedOperation> =
486            if assignments.is_empty() { Vec::new() } else { postcard::from_bytes(assignments).map_err(|_| ServingError::Catalog)? };
487        for route in self.handlers.values() {
488            for callee in route.callees {
489                let id = callee.operation.id;
490                let fingerprint = (callee.operation.fingerprint)().map_err(|_| ServingError::Catalog)?;
491                let local = QualifiedId::qualify(&self.owner, id).map_err(|_| ServingError::Catalog)?;
492                let actual = self
493                    .handlers
494                    .get(local.as_str())
495                    .map(|entry| entry.definition.fingerprint.0)
496                    .or_else(|| {
497                        assignments
498                            .iter()
499                            .find(|entry| entry.operation.split_once('.').is_some_and(|(_, name)| name == id))
500                            .map(|entry| entry.fingerprint)
501                    })
502                    .ok_or(ServingError::Unassigned { operation: id })?;
503                if actual != fingerprint.0 {
504                    return Err(ServingError::Identity { operation: id.to_owned() });
505                }
506            }
507        }
508        Ok(())
509    }
510
511    pub fn validate_contracts(&self, contracts: &[Contract]) -> Result<(), ServingError> {
512        if contracts.len() != self.handlers.values().filter(|handler| !handler.builtin).count() {
513            return Err(ServingError::Catalog);
514        }
515        let mut unique = std::collections::BTreeSet::new();
516        for contract in contracts {
517            let id = &contract.operation().id;
518            if !unique.insert(id)
519                || self
520                    .handlers
521                    .get(QualifiedId::qualify(&self.owner, id.as_str()).map_err(|_| ServingError::Catalog)?.as_str())
522                    .is_none_or(|bound| bound.builtin || bound.contract.as_ref() != Some(contract))
523            {
524                return Err(ServingError::Catalog);
525            }
526        }
527        Ok(())
528    }
529
530    // Answers the request's grant as the Prepare payload.
531    pub fn prepare(
532        &mut self, invocation: u64, operation: &str, fingerprint: [u8; 32], generation: u64, payload: Vec<u8>,
533    ) -> Result<Vec<u8>, ServingError> {
534        self.prepare_codec(invocation, operation, fingerprint, generation, payload, false)
535    }
536
537    fn prepare_codec(
538        &mut self, invocation: u64, operation: &str, fingerprint: [u8; 32], generation: u64, payload: Vec<u8>, grpc: bool,
539    ) -> Result<Vec<u8>, ServingError> {
540        if self.closed {
541            return Err(ServingError::Closing);
542        }
543        self.prepared.retain(|_, slot| !slot.is_finished());
544        if self.prepared.contains_key(&invocation) {
545            return Err(ServingError::Duplicate { invocation });
546        }
547        let handler = self.handlers.get_mut(operation).ok_or_else(|| ServingError::Unknown { operation: operation.to_owned() })?;
548        if handler.definition.fingerprint.0 != fingerprint
549            || generation == 0
550            || handler.generation.is_some_and(|bound| bound != generation)
551            || self.retired.get(operation).is_some_and(|retired| generation <= *retired)
552        {
553            return Err(ServingError::Identity { operation: operation.to_owned() });
554        }
555        // The authenticated host assigns a generation with its first preparation.
556        // Keep it through withdrawal so delayed preparations cannot bind a replacement.
557        handler.generation = Some(generation);
558        if handler.closing || (self.ordinary_closed && !handler.lifecycle) {
559            return Err(ServingError::Closing);
560        }
561        let occupied = self.prepared.values().filter(|slot| slot.lifecycle == handler.lifecycle && !slot.resident).count();
562        // One accounted reserved lifecycle slot remains available even when
563        // ordinary admission is full. Failed SDK finalization retains that slot.
564        let capacity = if handler.lifecycle { 1 } else { self.capacity.ok_or(ServingError::Capacity)?.get() };
565        if occupied >= capacity && !handler.resident {
566            return Err(ServingError::Capacity);
567        }
568        let prepared = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (handler.prepare)(payload, grpc)))
569            .map_err(|_| ServingError::Panicked)??;
570        let answer = encode_prepared_grant(prepared.grant().as_ref());
571        let slot = Slot {
572            operation: operation.to_owned(),
573            lifecycle: handler.lifecycle,
574            resident: handler.resident,
575            #[cfg(feature = "streaming")]
576            streams: prepared.streams(),
577            state: Mutex::new(SlotState {
578                prepared: Some(prepared),
579                finished: false,
580                cancelled: false,
581                waiters: Vec::new(),
582            }),
583        };
584        self.prepared.insert(invocation, Arc::new(slot));
585        Ok(answer)
586    }
587
588    #[cfg(feature = "streaming")]
589    pub fn streams(&self, invocation: u64) -> Result<WireStreams, ServingError> {
590        let slot = self.prepared.get(&invocation).ok_or(ServingError::Missing { invocation })?;
591        if self.closed || (self.ordinary_closed && !slot.lifecycle) || slot.is_finished() {
592            return Err(ServingError::Closing);
593        }
594        slot.streams.clone().ok_or(ServingError::StreamPhase)
595    }
596
597    #[tracing::instrument(target = "harmos::streams", name = "streams.initialize", level = "trace", skip_all, fields(invocation))]
598    pub async fn bind(&mut self, invocation: u64, context: &ExecutionContext) -> Result<(), ServingError> {
599        if self.closed {
600            return Err(ServingError::Closing);
601        }
602        let slot = self.prepared.get(&invocation).ok_or(ServingError::Missing { invocation })?;
603        if self.ordinary_closed && !slot.lifecycle {
604            return Err(ServingError::Closing);
605        }
606        slot.bind(invocation, context).await
607    }
608
609    #[tracing::instrument(target = "harmos::streams", name = "streams.attempt", level = "trace", skip_all, fields(invocation))]
610    pub async fn attempt(&mut self, invocation: u64, context: &ExecutionContext) -> Result<Vec<u8>, ServingError> {
611        if self.closed {
612            return Err(ServingError::Closing);
613        }
614        let slot = self.prepared.get(&invocation).ok_or(ServingError::Missing { invocation })?;
615        if self.ordinary_closed && !slot.lifecycle {
616            return Err(ServingError::Closing);
617        }
618        slot.attempt(invocation, context, None).await
619    }
620
621    #[tracing::instrument(target = "harmos::streams", name = "streams.finalize", level = "trace", skip_all, fields(invocation))]
622    pub async fn finalize(&mut self, invocation: u64) -> Result<(), ServingError> {
623        if let Some(slot) = self.prepared.get(&invocation) {
624            slot.finalize().await?;
625        }
626        self.prepared.remove(&invocation);
627        Ok(())
628    }
629
630    fn traffic(&self, command: WireCommand) -> Pin<Box<dyn Future<Output = WireAnswer> + Send + 'static>> {
631        #[cfg(not(feature = "streaming"))]
632        {
633            let _ = command;
634            Box::pin(async { wire_answer(Err(ServingError::StreamUnsupported)) })
635        }
636        #[cfg(feature = "streaming")]
637        {
638            let invocation = match &command {
639                WireCommand::StreamSend { invocation, .. }
640                | WireCommand::StreamFinish { invocation, .. }
641                | WireCommand::StreamReceive { invocation, .. } => *invocation,
642                _ => unreachable!("only stream traffic bypasses the phase loan"),
643            };
644            let streams = self.streams(invocation);
645            Box::pin(async move {
646                let streams = match streams {
647                    Ok(streams) => streams,
648                    Err(error) => return wire_answer(Err(error)),
649                };
650                match command {
651                    WireCommand::StreamSend { epoch, payload, .. } => {
652                        wire_answer(guarded(async { streams.send(epoch, &payload).await.map(|()| Vec::new()) }).await)
653                    }
654                    WireCommand::StreamFinish { epoch, .. } => wire_answer(streams.finish_input(epoch).map(|()| Vec::new())),
655                    WireCommand::StreamReceive { epoch, .. } => match guarded(streams.receive(epoch)).await {
656                        Ok(payload) => WireAnswer::Stream {
657                            epoch,
658                            transferred: streams.transferred(),
659                            payload,
660                        },
661                        Err(error) => wire_answer(Err(error)),
662                    },
663                    _ => unreachable!("only stream traffic bypasses the phase loan"),
664                }
665            })
666        }
667    }
668
669    pub fn begin(
670        &mut self, command: WireCommand, context: Arc<ExecutionContext>,
671    ) -> Pin<Box<dyn Future<Output = WireAnswer> + Send + 'static>> {
672        let (invocation, phase) = command_identity(&command);
673        let span = tracing::debug_span!(target: "harmos::carrier", "carrier.receive", invocation, phase, outcome = tracing::field::Empty);
674        if command.is_stream_traffic() {
675            return Box::pin(self.traffic(command).instrument(span).in_current_context());
676        }
677        let slot = match &command {
678            WireCommand::Initialize { invocation, .. }
679            | WireCommand::Attempt { invocation, .. }
680            | WireCommand::StreamAttempt { invocation, .. }
681            | WireCommand::Finalize { invocation } => self.prepared.get(invocation).cloned(),
682            WireCommand::Prepare { .. } | WireCommand::PrepareGrpc { .. } => None,
683            WireCommand::StreamSend { .. } | WireCommand::StreamFinish { .. } | WireCommand::StreamReceive { .. } => {
684                unreachable!("stream traffic already dispatched")
685            }
686        };
687        let command = match command {
688            WireCommand::PrepareGrpc {
689                invocation,
690                operation,
691                fingerprint,
692                generation,
693                payload,
694            } => {
695                let result = span.in_scope(|| self.prepare_codec(invocation, &operation, fingerprint, generation, payload, true));
696                return Box::pin(async move { wire_answer(result) }.instrument(span).in_current_context());
697            }
698            WireCommand::Prepare {
699                invocation,
700                operation,
701                fingerprint,
702                generation,
703                payload,
704            } => {
705                let result = span.in_scope(|| self.prepare(invocation, &operation, fingerprint, generation, payload));
706                return Box::pin(async move { wire_answer(result) }.instrument(span).in_current_context());
707            }
708            command => command,
709        };
710        let refused = self.closed || (self.ordinary_closed && slot.as_ref().is_some_and(|slot| !slot.lifecycle));
711        if refused && matches!(command, WireCommand::Initialize { .. } | WireCommand::Attempt { .. } | WireCommand::StreamAttempt { .. }) {
712            return Box::pin(async { wire_answer(Err(ServingError::Closing)) }.instrument(span).in_current_context());
713        }
714        Box::pin(
715            async move {
716                let result = match command {
717                    WireCommand::Initialize { invocation, .. } => match slot {
718                        Some(slot) => slot.bind(invocation, &context).await.map(|()| Vec::new()),
719                        None => Err(ServingError::Missing { invocation }),
720                    },
721                    WireCommand::Attempt { invocation, .. } => match slot {
722                        Some(slot) => slot.attempt(invocation, &context, None).await,
723                        None => Err(ServingError::Missing { invocation }),
724                    },
725                    WireCommand::StreamAttempt { invocation, epoch, .. } => match slot {
726                        Some(slot) => slot.attempt(invocation, &context, Some(epoch)).await,
727                        None => Err(ServingError::Missing { invocation }),
728                    },
729                    WireCommand::StreamSend { .. } | WireCommand::StreamFinish { .. } | WireCommand::StreamReceive { .. } => {
730                        unreachable!("stream traffic already dispatched")
731                    }
732                    WireCommand::Finalize { .. } => match slot {
733                        Some(slot) => slot.finalize().await.map(|()| Vec::new()),
734                        None => Ok(Vec::new()),
735                    },
736                    WireCommand::Prepare { .. } | WireCommand::PrepareGrpc { .. } => {
737                        unreachable!("preparation is synchronous")
738                    }
739                };
740                wire_answer(result)
741            }
742            .instrument(span)
743            .in_current_context(),
744        )
745    }
746
747    pub fn close_ordinary(&mut self) -> Pin<Box<dyn Future<Output = Result<(), ServingError>> + Send + 'static>> {
748        self.ordinary_closed = true;
749        let slots: Vec<_> = self.prepared.values().filter(|slot| !slot.lifecycle).cloned().collect();
750        for slot in &slots {
751            slot.cancel();
752        }
753        Box::pin(async move {
754            for slot in slots {
755                slot.finalize().await?;
756            }
757            Ok(())
758        })
759    }
760
761    pub fn release_ordinary(&mut self) -> Result<impl Send + use<>, ServingError> {
762        if !self.ordinary_closed || self.prepared.values().any(|slot| !slot.lifecycle && !slot.is_finished()) {
763            return Err(ServingError::Unsettled);
764        }
765        // Hand Off Retired Owners So The Caller Can Drop Them Outside Its Lock
766        let retired_slots = self.prepared.extract_if(.., |_, slot| !slot.lifecycle).collect::<Vec<_>>();
767        let retired_handlers = self.handlers.extract_if(.., |_, handler| !handler.lifecycle).collect::<Vec<_>>();
768        let retired_services = std::mem::take(&mut self.services);
769        Ok((retired_slots, retired_handlers, retired_services))
770    }
771
772    pub async fn settle_ordinary(&mut self) -> Result<(), ServingError> {
773        self.close_ordinary().await?;
774        let retired = self.release_ordinary()?;
775        drop(retired);
776        Ok(())
777    }
778
779    pub fn close(&mut self) -> Pin<Box<dyn Future<Output = Result<(), ServingError>> + Send + 'static>> {
780        self.closed = true;
781        let slots: Vec<_> = self.prepared.values().cloned().collect();
782        for slot in &slots {
783            slot.cancel();
784        }
785        Box::pin(async move {
786            for slot in slots {
787                slot.finalize().await?;
788            }
789            Ok(())
790        })
791    }
792
793    pub fn release(&mut self) -> Result<impl Send + use<>, ServingError> {
794        if !self.closed || self.prepared.values().any(|slot| !slot.is_finished()) {
795            return Err(ServingError::Unsettled);
796        }
797        Ok((std::mem::take(&mut self.prepared), std::mem::take(&mut self.handlers), std::mem::take(&mut self.services)))
798    }
799
800    pub async fn settle(&mut self) -> Result<(), ServingError> {
801        self.close().await?;
802        let retired = self.release()?;
803        drop(retired);
804        Ok(())
805    }
806
807    pub fn is_settled(&self) -> bool {
808        self.closed && self.prepared.values().all(|slot| slot.is_finished())
809    }
810
811    pub fn is_closed(&self) -> bool {
812        self.closed
813    }
814
815    #[tracing::instrument(target = "harmos::carrier", name = "carrier.answer", level = "debug", skip_all, fields(invocation = command_identity(&command).0, phase = command_identity(&command).1))]
816    pub async fn answer(&mut self, command: WireCommand, context: &ExecutionContext) -> WireAnswer {
817        if command.is_stream_traffic() {
818            return self.traffic(command).await;
819        }
820        if let WireCommand::StreamAttempt { invocation, epoch, .. } = command {
821            if self.closed || (self.ordinary_closed && self.prepared.get(&invocation).is_some_and(|slot| !slot.lifecycle)) {
822                return wire_answer(Err(ServingError::Closing));
823            }
824            let Some(slot) = self.prepared.get(&invocation) else {
825                return wire_answer(Err(ServingError::Missing { invocation }));
826            };
827            return wire_answer(slot.attempt(invocation, context, Some(epoch)).await);
828        }
829        let result = match command {
830            WireCommand::PrepareGrpc {
831                invocation,
832                operation,
833                fingerprint,
834                generation,
835                payload,
836            } => self.prepare_codec(invocation, &operation, fingerprint, generation, payload, true),
837            WireCommand::Prepare {
838                invocation,
839                operation,
840                fingerprint,
841                generation,
842                payload,
843            } => self.prepare(invocation, &operation, fingerprint, generation, payload),
844            WireCommand::Initialize { invocation, .. } => self.bind(invocation, context).await.map(|()| Vec::new()),
845            WireCommand::Attempt { invocation, .. } => self.attempt(invocation, context).await,
846            WireCommand::StreamAttempt { .. }
847            | WireCommand::StreamSend { .. }
848            | WireCommand::StreamFinish { .. }
849            | WireCommand::StreamReceive { .. } => {
850                unreachable!("stream command already dispatched")
851            }
852            WireCommand::Finalize { invocation } => self.finalize(invocation).await.map(|()| Vec::new()),
853        };
854        wire_answer(result)
855    }
856}
857
858impl Slot {
859    fn cancel(&self) {
860        let waiters = {
861            let mut state = self.state.lock().expect("invocation state lock");
862            state.cancelled = true;
863            std::mem::take(&mut state.waiters)
864        };
865        #[cfg(feature = "streaming")]
866        if let Some(streams) = &self.streams {
867            streams.close();
868        }
869        for waiter in waiters {
870            if let Some(waiter) = waiter.upgrade() {
871                let wake = waiter.lock().expect("invocation waiter lock").take();
872                if let Some(wake) = wake {
873                    wake.wake();
874                }
875            }
876        }
877    }
878
879    async fn active<T>(&self, future: impl Future<Output = Result<T, ServingError>>) -> Result<T, ServingError> {
880        let mut future = std::pin::pin!(future);
881        let waiter = Arc::new(Mutex::new(None));
882        std::future::poll_fn(|context| {
883            {
884                let mut state = self.state.lock().expect("invocation state lock");
885                if state.cancelled {
886                    return Poll::Ready(Err(ServingError::Closing));
887                }
888                *waiter.lock().expect("invocation waiter lock") = Some(context.waker().clone());
889                state.waiters.retain(|entry| entry.strong_count() != 0);
890                if !state.waiters.iter().any(|entry| entry.ptr_eq(&Arc::downgrade(&waiter))) {
891                    state.waiters.push(Arc::downgrade(&waiter));
892                }
893            }
894            future.as_mut().poll(context)
895        })
896        .await
897    }
898
899    fn is_finished(&self) -> bool {
900        self.state.lock().expect("invocation state lock").finished
901    }
902
903    async fn borrow(self: &Arc<Self>) -> Option<Loan> {
904        let waiter = Arc::new(Mutex::new(None));
905        std::future::poll_fn(|context| {
906            let mut state = self.state.lock().expect("invocation state lock");
907            if state.finished {
908                return Poll::Ready(None);
909            }
910            if let Some(prepared) = state.prepared.take() {
911                return Poll::Ready(Some(Loan {
912                    slot: Arc::clone(self),
913                    prepared: Some(prepared),
914                    finished: false,
915                }));
916            }
917            *waiter.lock().expect("invocation waiter lock") = Some(context.waker().clone());
918            state.waiters.retain(|entry| entry.strong_count() != 0);
919            if !state.waiters.iter().any(|entry| entry.ptr_eq(&Arc::downgrade(&waiter))) {
920                state.waiters.push(Arc::downgrade(&waiter));
921            }
922            Poll::Pending
923        })
924        .await
925    }
926}
927
928impl Slot {
929    #[tracing::instrument(target = "harmos::streams", name = "streams.initialize", level = "trace", skip_all, fields(invocation))]
930    async fn bind(self: &Arc<Self>, invocation: u64, context: &ExecutionContext) -> Result<(), ServingError> {
931        let mut loan = self.borrow().await.ok_or(ServingError::Missing { invocation })?;
932        self.active(loan.prepared.as_mut().expect("loaned invocation state").bind(context)).await
933    }
934
935    #[tracing::instrument(target = "harmos::streams", name = "streams.attempt", level = "trace", skip_all, fields(invocation))]
936    async fn attempt(self: &Arc<Self>, invocation: u64, context: &ExecutionContext, epoch: Option<u64>) -> Result<Vec<u8>, ServingError> {
937        let mut loan = self.borrow().await.ok_or(ServingError::Missing { invocation })?;
938        #[cfg(feature = "streaming")]
939        let attempt = match (&self.streams, epoch) {
940            (Some(streams), Some(epoch)) => Some(streams.start(epoch)?),
941            (None, None) => None,
942            _ => return Err(ServingError::StreamUnsupported),
943        };
944        #[cfg(not(feature = "streaming"))]
945        if epoch.is_some() {
946            return Err(ServingError::StreamUnsupported);
947        }
948        let result = self.active(loan.prepared.as_mut().expect("loaned invocation state").attempt(context)).await;
949        #[cfg(feature = "streaming")]
950        if let Some(attempt) = attempt {
951            attempt.end();
952        }
953        result
954    }
955
956    #[tracing::instrument(target = "harmos::carrier", name = "carrier.finalize", level = "debug", skip_all)]
957    async fn finalize(self: &Arc<Self>) -> Result<(), ServingError> {
958        #[cfg(feature = "streaming")]
959        if let Some(streams) = &self.streams {
960            streams.close();
961        }
962        let Some(mut loan) = self.borrow().await else {
963            return Ok(());
964        };
965        loan.prepared.as_mut().expect("loaned invocation state").finalize().await?;
966        // Drop Physical State Before Releasing Its Capacity
967        loan.finished = true;
968        let prepared = loan.prepared.take();
969        drop(prepared);
970        Ok(())
971    }
972}
973
974impl Drop for Loan {
975    fn drop(&mut self) {
976        let waiters = {
977            let mut state = self.slot.state.lock().expect("invocation state lock");
978            state.prepared = self.prepared.take();
979            state.finished = self.finished;
980            std::mem::take(&mut state.waiters)
981        };
982        for waiter in waiters {
983            if let Some(waiter) = waiter.upgrade() {
984                let wake = waiter.lock().expect("invocation waiter lock").take();
985                if let Some(wake) = wake {
986                    wake.wake();
987                }
988            }
989        }
990    }
991}
992
993impl WireCommand {
994    pub const LIMIT: usize = codec::LIMIT;
995    fn is_stream_traffic(&self) -> bool {
996        matches!(self, Self::StreamSend { .. } | Self::StreamFinish { .. } | Self::StreamReceive { .. })
997    }
998}
999
1000impl ServingError {
1001    pub fn classification(&self) -> FailureKind {
1002        match self {
1003            Self::Handler(failure) => failure.code(),
1004            Self::Panicked => FailureKind::Ambiguous,
1005            Self::Codec(_) | Self::Declaration(_) | Self::Lifecycle(_) | Self::Contract(_) | Self::Catalog | Self::Streaming { .. } => {
1006                FailureKind::Invalid
1007            }
1008            _ => FailureKind::Permanent,
1009        }
1010    }
1011}
1012
1013/* === Public Functions === */
1014
1015// A Prepare answer is empty when the request needs no grant, else the grant as JSON.
1016pub fn encode_prepared_grant(grant: Option<&crate::plugin::Grant>) -> Vec<u8> {
1017    grant.map_or_else(Vec::new, |grant| serde_json::to_vec(grant).expect("a grant serializes to JSON"))
1018}
1019
1020pub fn decode_prepared_grant(payload: &[u8]) -> Result<Option<crate::plugin::Grant>, serde_json::Error> {
1021    if payload.is_empty() {
1022        return Ok(None);
1023    }
1024    serde_json::from_slice(payload).map(Some)
1025}
1026
1027/* === Private Functions === */
1028
1029fn command_identity(command: &WireCommand) -> (u64, &'static str) {
1030    match command {
1031        WireCommand::Prepare { invocation, .. } => (*invocation, "prepare"),
1032        WireCommand::PrepareGrpc { invocation, .. } => (*invocation, "prepare_grpc"),
1033        WireCommand::Initialize { invocation, .. } => (*invocation, "initialize"),
1034        WireCommand::Attempt { invocation, .. } => (*invocation, "attempt"),
1035        WireCommand::Finalize { invocation, .. } => (*invocation, "finalize"),
1036        WireCommand::StreamAttempt { invocation, .. } => (*invocation, "stream_attempt"),
1037        WireCommand::StreamSend { invocation, .. } => (*invocation, "stream_send"),
1038        WireCommand::StreamFinish { invocation, .. } => (*invocation, "stream_finish"),
1039        WireCommand::StreamReceive { invocation, .. } => (*invocation, "stream_receive"),
1040    }
1041}
1042
1043fn wire_answer(result: Result<Vec<u8>, ServingError>) -> WireAnswer {
1044    tracing::Span::current().record("outcome", if result.is_ok() { "ready" } else { "failed" });
1045    match result {
1046        Ok(payload) => WireAnswer::Ready { payload },
1047        Err(ServingError::Handler(failure)) => WireAnswer::Failed { failure },
1048        Err(error) => WireAnswer::Failed {
1049            failure: crate::Failure::originate(error.classification(), &error, crate::failure::Origin::Plugin),
1050        },
1051    }
1052}
1053
1054async fn guarded<T>(future: impl Future<Output = Result<T, ServingError>>) -> Result<T, ServingError> {
1055    let mut future = std::pin::pin!(future);
1056    std::future::poll_fn(|context| match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.as_mut().poll(context))) {
1057        Ok(result) => result,
1058        Err(_) => std::task::Poll::Ready(Err(ServingError::Panicked)),
1059    })
1060    .await
1061}
1062
1063/* === Tests === */
1064
1065#[cfg(test)]
1066mod tests {
1067    use super::*;
1068
1069    #[test]
1070    fn withdrawal_cancels_a_pending_active_future() {
1071        let slot = Slot {
1072            operation: "host.read".to_owned(),
1073            lifecycle: false,
1074            resident: false,
1075            #[cfg(feature = "streaming")]
1076            streams: None,
1077            state: Mutex::new(SlotState {
1078                prepared: None,
1079                finished: false,
1080                cancelled: false,
1081                waiters: Vec::new(),
1082            }),
1083        };
1084        let mut active = Box::pin(slot.active(std::future::pending::<Result<(), ServingError>>()));
1085        let mut context = std::task::Context::from_waker(Waker::noop());
1086        assert!(active.as_mut().poll(&mut context).is_pending());
1087        slot.cancel();
1088        assert!(matches!(active.as_mut().poll(&mut context), Poll::Ready(Err(ServingError::Closing))));
1089    }
1090}