mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Add gRPC TCP transport to the code-mode host (#37745)
## What changed - Accept `grpc://IP:PORT` endpoints through `--listen` and serve the existing code-mode gRPC service over TCP. - Print the bound HTTP endpoint to stdout so callers can discover the port when binding to port `0`. - Apply the protocol frame-size limits and disable Nagle's algorithm on accepted connections. ## Testing - Add an end-to-end test that starts the host on an ephemeral port, connects a gRPC client, and opens a session. - Verify accepted gRPC sockets have `TCP_NODELAY` enabled. GitOrigin-RevId: 51d6c21dff8cffb47068c0677ba10ff370385cec
This commit is contained in:
committed by
copyberry
parent
50ef7395fa
commit
c0ad3ab014
@@ -0,0 +1,43 @@
|
||||
use std::io;
|
||||
use std::io::Write as _;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_code_mode_protocol::grpc::code_mode_host_server::CodeModeHostServer;
|
||||
use codex_code_mode_protocol::host::MAX_FRAME_BYTES;
|
||||
use tokio::net::TcpListener;
|
||||
use tonic::transport::Server;
|
||||
use tonic::transport::server::TcpIncoming;
|
||||
use tracing::info;
|
||||
|
||||
use crate::GrpcCodeModeHost;
|
||||
|
||||
pub(super) async fn run_tcp_listener(bind_address: SocketAddr) -> Result<()> {
|
||||
let listener = bind_tcp_listener(bind_address).await?;
|
||||
let local_address = listener
|
||||
.local_addr()
|
||||
.context("failed to read code-mode gRPC listen address")?;
|
||||
info!("codex-code-mode-host listening on http://{local_address}");
|
||||
println!("http://{local_address}");
|
||||
io::stdout()
|
||||
.flush()
|
||||
.context("failed to publish code-mode gRPC listen address")?;
|
||||
|
||||
Server::builder()
|
||||
.add_service(
|
||||
CodeModeHostServer::new(GrpcCodeModeHost::new())
|
||||
.max_decoding_message_size(MAX_FRAME_BYTES)
|
||||
.max_encoding_message_size(MAX_FRAME_BYTES),
|
||||
)
|
||||
.serve_with_incoming(listener)
|
||||
.await
|
||||
.context("code-mode gRPC TCP listener failed")
|
||||
}
|
||||
|
||||
pub(super) async fn bind_tcp_listener(bind_address: SocketAddr) -> Result<TcpIncoming> {
|
||||
let listener = TcpListener::bind(bind_address)
|
||||
.await
|
||||
.with_context(|| format!("failed to bind code-mode gRPC host to {bind_address}"))?;
|
||||
Ok(TcpIncoming::from(listener).with_nodelay(Some(true)))
|
||||
}
|
||||
@@ -50,6 +50,7 @@ pub use self::transport::DEFAULT_LISTEN_URL;
|
||||
|
||||
mod delegate;
|
||||
mod grpc;
|
||||
mod grpc_transport;
|
||||
mod peer;
|
||||
mod transport;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
/// Transport endpoint: `stdio`, `stdio://`, or `ws://IP:PORT`.
|
||||
/// Transport endpoint: `stdio`, `stdio://`, `ws://IP:PORT`, or `grpc://IP:PORT`.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "URL",
|
||||
|
||||
@@ -49,6 +49,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::HostLimits;
|
||||
use crate::MAX_IN_FLIGHT_REQUESTS;
|
||||
use crate::grpc_transport;
|
||||
|
||||
/// The default transport retains the standalone host's original stdio behavior.
|
||||
pub const DEFAULT_LISTEN_URL: &str = "stdio";
|
||||
@@ -63,6 +64,7 @@ type BoxedWriter = Box<dyn AsyncWrite + Send + Unpin>;
|
||||
enum ListenTransport {
|
||||
Stdio,
|
||||
WebSocket(SocketAddr),
|
||||
Grpc(SocketAddr),
|
||||
}
|
||||
|
||||
pub(crate) enum ConnectionReader {
|
||||
@@ -204,6 +206,7 @@ pub(crate) async fn run_transport(listen_url: &str) -> Result<()> {
|
||||
match parse_listen_url(listen_url)? {
|
||||
ListenTransport::Stdio => crate::run_stdio().await,
|
||||
ListenTransport::WebSocket(bind_address) => run_websocket_listener(bind_address).await,
|
||||
ListenTransport::Grpc(bind_address) => grpc_transport::run_tcp_listener(bind_address).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +224,17 @@ fn parse_listen_url(listen_url: &str) -> Result<ListenTransport> {
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(socket_addr) = listen_url.strip_prefix("grpc://") {
|
||||
return socket_addr
|
||||
.parse::<SocketAddr>()
|
||||
.map(ListenTransport::Grpc)
|
||||
.with_context(|| {
|
||||
format!("invalid gRPC --listen URL `{listen_url}`; expected `grpc://IP:PORT`")
|
||||
});
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"unsupported --listen URL `{listen_url}`; expected `ws://IP:PORT`, `stdio`, or `stdio://`"
|
||||
"unsupported --listen URL `{listen_url}`; expected `ws://IP:PORT`, `grpc://IP:PORT`, `stdio`, or `stdio://`"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::serve::Listener;
|
||||
use futures::StreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
@@ -9,6 +10,7 @@ use super::ListenTransport;
|
||||
use super::MAX_PENDING_BULK_CONNECTIONS;
|
||||
use super::bind_websocket_listener;
|
||||
use super::parse_listen_url;
|
||||
use crate::grpc_transport::bind_tcp_listener;
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_listener_disables_nagle() {
|
||||
@@ -33,6 +35,31 @@ async fn websocket_listener_disables_nagle() {
|
||||
assert!(stream);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grpc_listener_disables_nagle() {
|
||||
let bind_address = "127.0.0.1:0"
|
||||
.parse()
|
||||
.expect("gRPC test listener should have a valid bind address");
|
||||
let mut listener = bind_tcp_listener(bind_address)
|
||||
.await
|
||||
.expect("gRPC test listener should bind");
|
||||
let local_addr = listener
|
||||
.local_addr()
|
||||
.expect("gRPC test listener should have a local address");
|
||||
let _client = TcpStream::connect(local_addr)
|
||||
.await
|
||||
.expect("gRPC test client should connect");
|
||||
let nodelay = listener
|
||||
.next()
|
||||
.await
|
||||
.expect("gRPC test listener should accept a connection")
|
||||
.expect("gRPC test listener should return a valid socket")
|
||||
.nodelay()
|
||||
.expect("accepted gRPC socket should expose TCP_NODELAY");
|
||||
|
||||
assert!(nodelay);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bulk_connection_registration_cleans_up_when_dropped() {
|
||||
let registry = BulkConnectionRegistry::default();
|
||||
|
||||
Reference in New Issue
Block a user