Skip to main content

ServeLoop

Struct ServeLoop 

Source
pub struct ServeLoop<R, W, DW, P> { /* private fields */ }
Expand description

Reads a peer’s frames, routes them, and writes the answers back.

Generic over both streams so that the bytes can come from a child process, a pipe, a socket, or a test, and over the diagnostics channel so that “somewhere that is not stdout” is a decision made by the caller rather than hard-wired here. See the module docs for what the loop guarantees.

Implementations§

Source§

impl<R, W, DW, P> ServeLoop<R, W, DW, P>

Source

pub const fn peer(&self) -> &PeerSession

The session every request on this connection is gated by.

Read-only, and the only way to see it: the handshake replaces the session’s profile with the one resolved for the revision the peer negotiated, so a caller who read the session before serving began would be holding the ceiling rather than the answer.

Source

pub fn new( reader: R, writer: W, diagnostics: Diagnostics<DW>, config: StdioConfig, dispatcher: Arc<Dispatcher>, peer: PeerSession, policy: P, ) -> Self
where W: AsyncWrite + Unpin + Send + 'static,

Binds a peer’s streams, a routing table, and a diagnostics channel.

Both halves of the transport are built here from one StdioConfig, and that is the point: a caller who assembled a StdioSource and a StdioSink separately could give them different ceilings, and StdioConfig exists to make that impossible. This is the only constructor, so there is no way to bypass it.

§Examples
use std::sync::Arc;

use protocol_droid::adapters::primary::stdio::{Diagnostics, ServeLoop, StdioConfig};
use protocol_droid::domain::MaxFrameSize;

let config = StdioConfig::new(MaxFrameSize::new(1024)?);
let server = ServeLoop::new(reader, writer, diagnostics, config, dispatcher, peer, policy);

The peer’s sink is replaced with this loop’s own. A handler that publishes on any other sink is publishing somewhere the peer at the other end of this pipe will never read.

Source§

impl<R, W, DW, P> ServeLoop<R, W, DW, P>
where R: AsyncRead + Unpin + Send, W: AsyncWrite + Unpin + Send + 'static, DW: AsyncWrite + Unpin + Send, P: BatchPolicy + Send + Sync,

Source

pub async fn serve(&mut self) -> Result<ServeReport, ServeError>

Serves the peer until it closes the stream.

Returns Ok when the peer closed and Err when the transport failed. Those are different events and the distinction is the point: a peer that exits normally and a pipe that breaks are the same Ok(0) from the reader’s point of view, and only StdioSource knows which one it saw. Collapsing them here would leave a caller unable to tell a clean shutdown from a crash.

§Errors

ServeError::Source when the peer’s stream failed, which is latched and unrecoverable. ServeError::Reply when a reply could not be written, including a reply the configured ceiling refused — StdioError::ReplyTooLarge — and ServeError::Diagnostics when the log could not be written. All three are terminal: continuing to serve a peer we can no longer talk to, or describe, would be a loop that fails silently.

§Examples
let server = ServeLoop::new(
    reader,
    writer,
    Diagnostics::stderr(),
    StdioConfig::default(),
    dispatcher,
    peer,
    policy,
);

let mut server = server;
let report = server.serve().await?;
// The report is returned rather than logged. This transport's stdout
// carries MCP messages and nothing else, so a caller that wants the
// numbers goes to its own log — the loop never chooses a sink.

Trait Implementations§

Source§

impl<R, W, DW, P> Debug for ServeLoop<R, W, DW, P>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Prints the session and the routing table, and nothing about the streams.

The streams are Debug-able in practice and useless in a log: a peer’s buffered bytes and a DuplexStream’s internals say nothing about which session is misbehaving, and requiring W: Debug to print the one field that would is a bound nobody wants.

Auto Trait Implementations§

§

impl<R, W, DW, P> !Freeze for ServeLoop<R, W, DW, P>

§

impl<R, W, DW, P> !RefUnwindSafe for ServeLoop<R, W, DW, P>

§

impl<R, W, DW, P> !UnwindSafe for ServeLoop<R, W, DW, P>

§

impl<R, W, DW, P> Send for ServeLoop<R, W, DW, P>
where StdioSource<R>: Send, Arc<StdioSink<W>>: Send, Diagnostics<DW>: Send, P: Send, Arc<StdioExchange<W>>: Send,

§

impl<R, W, DW, P> Sync for ServeLoop<R, W, DW, P>
where StdioSource<R>: Sync, Arc<StdioSink<W>>: Sync, Diagnostics<DW>: Sync, P: Sync, Arc<StdioExchange<W>>: Sync,

§

impl<R, W, DW, P> Unpin for ServeLoop<R, W, DW, P>
where StdioSource<R>: Unpin, Arc<StdioSink<W>>: Unpin, Diagnostics<DW>: Unpin, P: Unpin, Arc<StdioExchange<W>>: Unpin,

§

impl<R, W, DW, P> UnsafeUnpin for ServeLoop<R, W, DW, P>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more