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>
impl<R, W, DW, P> ServeLoop<R, W, DW, P>
Sourcepub const fn peer(&self) -> &PeerSession
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.
Sourcepub fn new(
reader: R,
writer: W,
diagnostics: Diagnostics<DW>,
config: StdioConfig,
dispatcher: Arc<Dispatcher>,
peer: PeerSession,
policy: P,
) -> Self
pub fn new( reader: R, writer: W, diagnostics: Diagnostics<DW>, config: StdioConfig, dispatcher: Arc<Dispatcher>, peer: PeerSession, policy: P, ) -> Self
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,
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,
Sourcepub async fn serve(&mut self) -> Result<ServeReport, ServeError>
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>
impl<R, W, DW, P> Debug for ServeLoop<R, W, DW, P>
Source§fn fmt(&self, f: &mut Formatter<'_>) -> Result
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.