diff --git a/crates/engram-host-agent/src/grpc_server.rs b/crates/engram-host-agent/src/grpc_server.rs index a4546944b..905b0742a 100644 --- a/crates/engram-host-agent/src/grpc_server.rs +++ b/crates/engram-host-agent/src/grpc_server.rs @@ -39,7 +39,7 @@ use engram_protocol::grpc::{ SendHarnessToolResultRequest, SnapshotBeginResponse, SnapshotResponse, StartAgentRequest, UnbindHarnessSessionRequest, }; -use engram_protocol::grpc_client::link_remote_parent; +use engram_protocol::grpc_client::{link_remote_parent, HOST_RPC_MAX_MESSAGE_BYTES}; use engram_protocol::wire::WireExecRequest; use futures::Stream; use std::pin::Pin; @@ -191,7 +191,11 @@ pub async fn serve( .initial_stream_window_size(Some(16 * 1024 * 1024)) .initial_connection_window_size(Some(32 * 1024 * 1024)) .http2_adaptive_window(Some(true)) - .add_service(HostServiceServer::new(svc).max_encoding_message_size(32 * 1024 * 1024)) + .add_service( + HostServiceServer::new(svc) + .max_encoding_message_size(HOST_RPC_MAX_MESSAGE_BYTES) + .max_decoding_message_size(HOST_RPC_MAX_MESSAGE_BYTES), + ) .serve_with_incoming(incoming) .await } diff --git a/crates/engram-host-agent/tests/grpc_pause_resume.rs b/crates/engram-host-agent/tests/grpc_pause_resume.rs index 4951aaf9d..67a8ea051 100644 --- a/crates/engram-host-agent/tests/grpc_pause_resume.rs +++ b/crates/engram-host-agent/tests/grpc_pause_resume.rs @@ -30,6 +30,7 @@ use parking_lot::Mutex; #[derive(Default)] struct FakeHost { calls: Mutex>, + prompt_bytes: Mutex>, } #[async_trait] @@ -100,10 +101,11 @@ impl HostClient for FakeHost { &self, _: SandboxId, _: String, - _: String, + text: String, _: Option, ) -> Result<(), SandboxError> { - unreachable!() + self.prompt_bytes.lock().push(text.len()); + Ok(()) } async fn proxy_shell(&self, _: SandboxId) -> Result { unreachable!() @@ -182,3 +184,20 @@ async fn pause_then_resume_round_trip_to_the_host_with_the_right_sandbox() { "pause/resume did not round-trip in order with the right sandbox id", ); } + +/// A move's restore request carries the VM's memory manifest and is larger +/// than tonic's 4 MiB default decode limit. The host accepts it. +#[tokio::test] +async fn the_host_accepts_a_request_above_the_tonic_default_limit() { + let host = Arc::new(FakeHost::default()); + let addr = boot_grpc_server(host.clone()).await; + let client = connect_grpc_client(addr).await; + + let text = "x".repeat(6 * 1024 * 1024); + client + .send_prompt(SandboxId::new(), "p1".into(), text.clone(), None) + .await + .expect("a 6 MiB request reaches the host"); + + assert_eq!(*host.prompt_bytes.lock(), vec![text.len()]); +} diff --git a/crates/engram-protocol/src/grpc_client.rs b/crates/engram-protocol/src/grpc_client.rs index a6838feaa..2cd7167c9 100644 --- a/crates/engram-protocol/src/grpc_client.rs +++ b/crates/engram-protocol/src/grpc_client.rs @@ -141,6 +141,13 @@ fn restore_rpc_timeout() -> Duration { Duration::from_secs(secs) } +/// The largest `HostService` message either side decodes or encodes. +/// tonic's 4 MiB default is below a move's restore request, which carries +/// the memory manifest of the VM (about 6 MB for 32 GiB of guest RAM), +/// and below a peer chunk frame. The cap is a limit check; it allocates +/// nothing. +pub const HOST_RPC_MAX_MESSAGE_BYTES: usize = 32 * 1024 * 1024; + /// Coord-side client wrapping a `tonic::transport::Channel` to one /// host. Cheap to clone (tonic clients hold an `Arc` /// internally); one instance per host is the steady-state shape. @@ -159,15 +166,11 @@ impl GrpcHostClient { /// Every RPC carries the caller's `traceparent` via /// [`TraceparentInjector`] (ADR 0019 distributed tracing). /// - /// The inbound decode cap is 32 MiB on every client (was an opt-in - /// for `ReapMaterializeDir`'s 16 MiB reply; ADR 0095's - /// `PeerChunkGet` frames — 4 MiB data + overhead — need past - /// tonic's 4 MiB default too, and the cap is just a limit check, - /// no allocation change). + /// The client decodes replies up to [`HOST_RPC_MAX_MESSAGE_BYTES`]. pub fn new(channel: Channel) -> Self { Self { inner: HostServiceClient::with_interceptor(channel, TraceparentInjector) - .max_decoding_message_size(32 * 1024 * 1024), + .max_decoding_message_size(HOST_RPC_MAX_MESSAGE_BYTES), } }