mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
## Why Shared hosts serve independent chats and their subagents. Larger callback workloads need room for unrelated work and control messages, while gRPC completion bursts can exhaust HTTP/2's small-DATA-frame budget and disconnect shared sessions. ## What changed - Raise the active-cell limit from 128 to 2,048 and the pending delegate callback limit from 1,024 to 4,096. Expand request and transport queues to leave room for completion bursts and control traffic. - Negotiate larger callback budgets through the stdio `delegate-capacity-4096` capability and gRPC `max_pending_delegate_calls` field. Preserve the 1,024-callback budget for legacy clients and enforce both session and shared host limits on gRPC. - Limit concurrent gRPC completion RPCs to 64 across a provider's sessions, allowing queued completions to be canceled without waiting for delivery capacity. ## Testing Add stdio and gRPC integration tests covering 256 cells and 4,096 callbacks across shared sessions while unrelated work completes. Add tests for callback budget isolation and reuse, rejection of zero capacity, and cancellation of queued completions. Include an ignored resource-intensive test for full host capacity. GitOrigin-RevId: e85bfa24cc46cc8a735b464d80adf8066a6a6d3a
214 lines
6.6 KiB
Rust
214 lines
6.6 KiB
Rust
//! Shared-host capacity, callback completion, and unrelated-session progress.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use codex_code_mode::CellId;
|
|
use codex_code_mode::CodeModeNestedToolCall;
|
|
use codex_code_mode::CodeModeSessionDelegate;
|
|
use codex_code_mode::CodeModeSessionProvider;
|
|
use codex_code_mode::CodeModeToolKind;
|
|
use codex_code_mode::ExecuteRequest;
|
|
use codex_code_mode::FunctionCallOutputContentItem;
|
|
use codex_code_mode::GrpcCodeModeSessionProvider;
|
|
use codex_code_mode::NotificationFuture;
|
|
use codex_code_mode::ProcessOwnedCodeModeSessionProvider;
|
|
use codex_code_mode::RuntimeResponse;
|
|
use codex_code_mode::ToolDefinition;
|
|
use codex_code_mode::ToolInvocationFuture;
|
|
use codex_code_mode::WaitOutcome;
|
|
use codex_code_mode::WaitRequest;
|
|
use codex_protocol::ToolName;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
use tokio::sync::Semaphore;
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
#[path = "support/host.rs"]
|
|
mod host;
|
|
|
|
struct BlockingDelegate {
|
|
started: Semaphore,
|
|
release: Semaphore,
|
|
}
|
|
|
|
impl CodeModeSessionDelegate for BlockingDelegate {
|
|
fn invoke_tool<'a>(
|
|
&'a self,
|
|
_invocation: CodeModeNestedToolCall,
|
|
cancellation: CancellationToken,
|
|
) -> ToolInvocationFuture<'a> {
|
|
Box::pin(async move {
|
|
self.started.add_permits(1);
|
|
tokio::select! {
|
|
permit = self.release.acquire() => {
|
|
permit.map_err(|error| error.to_string())?.forget();
|
|
Ok(json!("done"))
|
|
}
|
|
_ = cancellation.cancelled() => Err("cancelled".to_string()),
|
|
}
|
|
})
|
|
}
|
|
|
|
fn notify<'a>(
|
|
&'a self,
|
|
_call_id: String,
|
|
_cell_id: CellId,
|
|
_text: String,
|
|
_cancellation: CancellationToken,
|
|
) -> NotificationFuture<'a> {
|
|
Box::pin(async { Ok(()) })
|
|
}
|
|
|
|
fn cell_closed(&self, _cell_id: &CellId) {}
|
|
}
|
|
|
|
#[allow(clippy::expect_used)]
|
|
async fn assert_shared_capacity(
|
|
provider: &dyn CodeModeSessionProvider,
|
|
cells: usize,
|
|
calls_per_cell: usize,
|
|
) {
|
|
let delegate = Arc::new(BlockingDelegate {
|
|
started: Semaphore::new(/*permits*/ 0),
|
|
release: Semaphore::new(/*permits*/ 0),
|
|
});
|
|
let mut sessions = Vec::new();
|
|
for _ in 0..2 {
|
|
sessions.push(
|
|
provider
|
|
.create_session()
|
|
.await
|
|
.expect("create shared session"),
|
|
);
|
|
}
|
|
let mut pending = Vec::new();
|
|
for index in 0..cells {
|
|
let session = &sessions[index % sessions.len()];
|
|
let request = ExecuteRequest {
|
|
tool_call_id: format!("capacity-{index}"),
|
|
enabled_tools: vec![ToolDefinition {
|
|
name: "block".to_string(),
|
|
tool_name: ToolName::plain("block"),
|
|
description: String::new(),
|
|
kind: CodeModeToolKind::Function,
|
|
input_schema: None,
|
|
input_schema_max_bytes: None,
|
|
output_schema: None,
|
|
}],
|
|
source: format!(
|
|
"const results = await Promise.all(Array.from({{length: {calls_per_cell}}}, () => tools.block({{}}))); text(results.length);"
|
|
),
|
|
yield_time_ms: Some(1),
|
|
max_output_tokens: None,
|
|
};
|
|
let started = session
|
|
.execute(request, delegate.clone(), /*preempt*/ None)
|
|
.await
|
|
.unwrap_or_else(|error| panic!("admit cell {index}: {error}"));
|
|
let cell_id = started.cell_id.clone();
|
|
let response = started.initial_response().await.expect("initial response");
|
|
assert!(
|
|
matches!(response, RuntimeResponse::Yielded { .. }),
|
|
"{response:?}"
|
|
);
|
|
pending.push((Arc::clone(session), cell_id));
|
|
}
|
|
let callbacks = cells * calls_per_cell;
|
|
delegate
|
|
.started
|
|
.acquire_many(callbacks as u32)
|
|
.await
|
|
.expect("all callbacks started")
|
|
.forget();
|
|
|
|
let probe = sessions[0]
|
|
.execute(
|
|
ExecuteRequest {
|
|
tool_call_id: "unrelated-probe".to_string(),
|
|
enabled_tools: Vec::new(),
|
|
source: "text('ready');".to_string(),
|
|
yield_time_ms: Some(60_000),
|
|
max_output_tokens: None,
|
|
},
|
|
delegate.clone(),
|
|
/*preempt*/ None,
|
|
)
|
|
.await
|
|
.expect("unrelated work admitted")
|
|
.initial_response()
|
|
.await
|
|
.expect("unrelated work completed");
|
|
assert!(
|
|
matches!(
|
|
probe,
|
|
RuntimeResponse::Result {
|
|
error_text: None,
|
|
..
|
|
}
|
|
),
|
|
"{probe:?}"
|
|
);
|
|
|
|
delegate.release.add_permits(callbacks);
|
|
for (session, cell_id) in pending {
|
|
let result = session
|
|
.wait(
|
|
WaitRequest {
|
|
cell_id,
|
|
yield_time_ms: 60_000,
|
|
max_output_tokens: None,
|
|
},
|
|
/*preempt*/ None,
|
|
)
|
|
.await
|
|
.expect("collect callback results");
|
|
let WaitOutcome::LiveCell(RuntimeResponse::Result {
|
|
content_items,
|
|
error_text,
|
|
..
|
|
}) = result
|
|
else {
|
|
panic!("expected completed cell: {result:?}");
|
|
};
|
|
assert_eq!(
|
|
(content_items, error_text),
|
|
(
|
|
vec![FunctionCallOutputContentItem::InputText {
|
|
text: calls_per_cell.to_string()
|
|
}],
|
|
None
|
|
)
|
|
);
|
|
}
|
|
for session in sessions {
|
|
session.shutdown().await.expect("close shared session");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn stdio_shared_sessions_complete_thousands_of_callbacks() {
|
|
let provider = ProcessOwnedCodeModeSessionProvider::with_host_program(
|
|
codex_utils_cargo_bin::cargo_bin("codex-code-mode-host").expect("host binary"),
|
|
);
|
|
assert_shared_capacity(&provider, /*cells*/ 256, /*calls_per_cell*/ 16).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn grpc_shared_sessions_complete_thousands_of_callbacks() {
|
|
let host = host::HostHarness::start("grpc://127.0.0.1:0")
|
|
.await
|
|
.expect("host");
|
|
let provider = GrpcCodeModeSessionProvider::new(host.endpoint);
|
|
assert_shared_capacity(&provider, /*cells*/ 256, /*calls_per_cell*/ 16).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires resources for 2,048 live V8 isolates and OS threads"]
|
|
async fn grpc_full_host_capacity() {
|
|
let host = host::HostHarness::start("grpc://127.0.0.1:0")
|
|
.await
|
|
.expect("host");
|
|
let provider = GrpcCodeModeSessionProvider::new(host.endpoint);
|
|
assert_shared_capacity(&provider, /*cells*/ 2_047, /*calls_per_cell*/ 2).await;
|
|
}
|