mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Add opt-in concurrent exec-server request dispatch (#36987)
## Why Sequential dispatch lets a long-running request block unrelated health checks and cleanup on the same connection. ## What changed - Add `--concurrent-requests <COUNT>` for local and remote exec-server connections, while retaining sequential dispatch when the option is omitted or set to `1`. - Preserve handshake ordering before enabling concurrent dispatch. - Reserve separate capacity for status, signal, terminate, and close requests so they remain responsive when ordinary request capacity is saturated. - Drain queued client responses during disconnect and cancel outstanding request tasks during connection shutdown. ## Testing - Cover CLI parsing and concurrency-limit validation. - Verify default sequential behavior, pipelined handshake ordering, concurrent request progress, control-request responsiveness, and disconnect handling. GitOrigin-RevId: 48e4b092e318204ed635543f01f9ee0e7df095fc
This commit is contained in:
committed by
copyberry
parent
1d952f027e
commit
eeae88d8a6
@@ -87,7 +87,7 @@ async fn exec_server_replays_only_chatgpt_cloudflare_cookies() -> anyhow::Result
|
||||
(OsString::from("NO_PROXY"), empty.clone()),
|
||||
(OsString::from("no_proxy"), empty),
|
||||
];
|
||||
let mut server = exec_server_with_env(env).await?;
|
||||
let mut server = exec_server_with_env(env, &[]).await?;
|
||||
initialize_exec_server(&mut server).await?;
|
||||
|
||||
let first_response = execute_http_request(&mut server, CHATGPT_MCP_URL, "first").await?;
|
||||
|
||||
@@ -76,10 +76,13 @@ pub(crate) fn test_codex_helper_paths() -> anyhow::Result<TestCodexHelperPaths>
|
||||
}
|
||||
|
||||
pub(crate) async fn exec_server() -> anyhow::Result<ExecServerHarness> {
|
||||
exec_server_with_env(std::iter::empty::<(&str, &str)>()).await
|
||||
exec_server_with_env(std::iter::empty::<(&str, &str)>(), &[]).await
|
||||
}
|
||||
|
||||
pub(crate) async fn exec_server_with_env<I, K, V>(env: I) -> anyhow::Result<ExecServerHarness>
|
||||
pub(crate) async fn exec_server_with_env<I, K, V>(
|
||||
env: I,
|
||||
args: &[&str],
|
||||
) -> anyhow::Result<ExecServerHarness>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: AsRef<std::ffi::OsStr>,
|
||||
@@ -89,6 +92,7 @@ where
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut child = Command::new(&helper_paths.codex_exe);
|
||||
child.args(["exec-server", "--listen", "ws://127.0.0.1:0"]);
|
||||
child.args(args);
|
||||
child.stdin(Stdio::null());
|
||||
child.stdout(Stdio::piped());
|
||||
child.stderr(Stdio::inherit());
|
||||
|
||||
@@ -9,6 +9,8 @@ use std::time::Duration;
|
||||
use codex_exec_server::CODEX_ARG0_EXEC_HELPER_ARG1;
|
||||
use codex_exec_server::CODEX_FS_HELPER_ARG1;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_exec_server::ExecServerTelemetry;
|
||||
use codex_exec_server::RequestDispatchMode;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
@@ -161,10 +163,21 @@ fn maybe_run_exec_server_from_test_binary(guard: Option<&TestBinaryDispatchGuard
|
||||
eprintln!("expected listen URL");
|
||||
std::process::exit(1);
|
||||
};
|
||||
if args.next().is_some() {
|
||||
eprintln!("unexpected extra arguments");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let remaining_args = args.collect::<Vec<_>>();
|
||||
let request_dispatch_mode = match remaining_args.as_slice() {
|
||||
[] => RequestDispatchMode::Inline,
|
||||
[flag, value] if flag == "--concurrent-requests" => match value.parse() {
|
||||
Ok(mode) => mode,
|
||||
Err(error) => {
|
||||
eprintln!("invalid concurrent request count: {error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
args => {
|
||||
eprintln!("unexpected exec-server arguments: {args:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let current_exe = match env::current_exe() {
|
||||
Ok(current_exe) => current_exe,
|
||||
@@ -209,10 +222,12 @@ fn maybe_run_exec_server_from_test_binary(guard: Option<&TestBinaryDispatchGuard
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let exit_code = match runtime.block_on(codex_exec_server::run_main(
|
||||
let exit_code = match runtime.block_on(codex_exec_server::run_main_with_telemetry(
|
||||
&listen_url,
|
||||
runtime_paths,
|
||||
ExecServerTelemetry::default(),
|
||||
http_client_factory,
|
||||
request_dispatch_mode,
|
||||
)) {
|
||||
Ok(()) => 0,
|
||||
Err(error) => {
|
||||
|
||||
@@ -190,7 +190,7 @@ async fn sandboxed_file_system_helper_finds_bwrap_on_preserved_path() -> Result<
|
||||
}
|
||||
let helper_path = std::env::join_paths(path_entries)?;
|
||||
|
||||
let server = exec_server_with_env([("PATH", helper_path.as_os_str())]).await?;
|
||||
let server = exec_server_with_env([("PATH", helper_path.as_os_str())], &[]).await?;
|
||||
let environment = Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
|
||||
let file_system = environment.get_filesystem();
|
||||
let workspace = tmp.path().join("workspace");
|
||||
|
||||
@@ -162,18 +162,21 @@ async fn exec_server_http_request_uses_configured_system_proxy() -> anyhow::Resu
|
||||
let proxy_listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let proxy_url = format!("http://{}", proxy_listener.local_addr()?);
|
||||
let request_url = "http://exec-server-system-proxy.invalid/delegated?route=system";
|
||||
let mut server = exec_server_with_env([
|
||||
(SYSTEM_PROXY_REQUEST_URL_ENV, request_url),
|
||||
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
|
||||
("HTTP_PROXY", ""),
|
||||
("http_proxy", ""),
|
||||
("HTTPS_PROXY", ""),
|
||||
("https_proxy", ""),
|
||||
("ALL_PROXY", ""),
|
||||
("all_proxy", ""),
|
||||
("NO_PROXY", ""),
|
||||
("no_proxy", ""),
|
||||
])
|
||||
let mut server = exec_server_with_env(
|
||||
[
|
||||
(SYSTEM_PROXY_REQUEST_URL_ENV, request_url),
|
||||
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
|
||||
("HTTP_PROXY", ""),
|
||||
("http_proxy", ""),
|
||||
("HTTPS_PROXY", ""),
|
||||
("https_proxy", ""),
|
||||
("ALL_PROXY", ""),
|
||||
("all_proxy", ""),
|
||||
("NO_PROXY", ""),
|
||||
("no_proxy", ""),
|
||||
],
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
initialize_exec_server(&mut server).await?;
|
||||
|
||||
@@ -218,18 +221,21 @@ async fn exec_server_http_request_normalizes_unicode_hostname() -> anyhow::Resul
|
||||
let proxy_url = format!("http://{}", proxy_listener.local_addr()?);
|
||||
let request_url = "http://münich.invalid/mcp?route=unicode";
|
||||
let normalized_url = "http://xn--mnich-kva.invalid/mcp?route=unicode";
|
||||
let mut server = exec_server_with_env([
|
||||
(SYSTEM_PROXY_REQUEST_URL_ENV, normalized_url),
|
||||
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
|
||||
("HTTP_PROXY", ""),
|
||||
("http_proxy", ""),
|
||||
("HTTPS_PROXY", ""),
|
||||
("https_proxy", ""),
|
||||
("ALL_PROXY", ""),
|
||||
("all_proxy", ""),
|
||||
("NO_PROXY", ""),
|
||||
("no_proxy", ""),
|
||||
])
|
||||
let mut server = exec_server_with_env(
|
||||
[
|
||||
(SYSTEM_PROXY_REQUEST_URL_ENV, normalized_url),
|
||||
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
|
||||
("HTTP_PROXY", ""),
|
||||
("http_proxy", ""),
|
||||
("HTTPS_PROXY", ""),
|
||||
("https_proxy", ""),
|
||||
("ALL_PROXY", ""),
|
||||
("all_proxy", ""),
|
||||
("NO_PROXY", ""),
|
||||
("no_proxy", ""),
|
||||
],
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
initialize_exec_server(&mut server).await?;
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
mod common;
|
||||
|
||||
use codex_exec_server::InitializeParams;
|
||||
use codex_exec_server::InitializeResponse;
|
||||
use codex_exec_server_protocol::JSONRPCError;
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_exec_server_protocol::JSONRPCMessage;
|
||||
use codex_exec_server_protocol::JSONRPCResponse;
|
||||
use common::exec_server::exec_server;
|
||||
use common::exec_server::exec_server_with_env;
|
||||
use pretty_assertions::assert_eq;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -34,3 +35,64 @@ async fn exec_server_accepts_initialize() -> anyhow::Result<()> {
|
||||
server.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Requests retain their wire-order initialization errors even when later handshake messages are pipelined.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_rejects_pipelined_requests_before_initialized() -> anyhow::Result<()> {
|
||||
let mut server = exec_server_with_env(
|
||||
std::iter::empty::<(&str, &str)>(),
|
||||
&["--concurrent-requests", "32"],
|
||||
)
|
||||
.await?;
|
||||
let before_initialize_id = server
|
||||
.send_request("environment/info", serde_json::json!({}))
|
||||
.await?;
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
serde_json::to_value(InitializeParams {
|
||||
client_name: "exec-server-test".to_string(),
|
||||
resume_session_id: None,
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
server.next_event().await?,
|
||||
JSONRPCMessage::Error(JSONRPCError {
|
||||
id: before_initialize_id,
|
||||
error: JSONRPCErrorError {
|
||||
code: -32600,
|
||||
data: None,
|
||||
message: "client must call initialize before using environment info methods"
|
||||
.to_string(),
|
||||
},
|
||||
})
|
||||
);
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = server.next_event().await? else {
|
||||
panic!("expected initialize response");
|
||||
};
|
||||
assert_eq!(id, initialize_id);
|
||||
|
||||
let before_initialized_id = server
|
||||
.send_request("environment/info", serde_json::json!({}))
|
||||
.await?;
|
||||
server
|
||||
.send_notification("initialized", serde_json::json!({}))
|
||||
.await?;
|
||||
assert_eq!(
|
||||
server.next_event().await?,
|
||||
JSONRPCMessage::Error(JSONRPCError {
|
||||
id: before_initialized_id,
|
||||
error: JSONRPCErrorError {
|
||||
code: -32600,
|
||||
data: None,
|
||||
message: "client must send initialized before using environment info methods"
|
||||
.to_string(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
server.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
mod common;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_exec_server::EnvironmentInfo;
|
||||
use codex_exec_server::EnvironmentStatus;
|
||||
use codex_exec_server::EnvironmentStatusKind;
|
||||
use codex_exec_server::ExecResponse;
|
||||
use codex_exec_server::InitializeParams;
|
||||
use codex_exec_server::InitializeResponse;
|
||||
@@ -16,11 +18,17 @@ use codex_exec_server_protocol::JSONRPCResponse;
|
||||
use codex_exec_server_protocol::ProcessSandboxType;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use common::exec_server::exec_server;
|
||||
use common::exec_server::exec_server_with_env;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {
|
||||
let mut server = exec_server().await?;
|
||||
let process_argv = if cfg!(windows) {
|
||||
vec!["cmd.exe", "/D", "/C", "exit 0"]
|
||||
} else {
|
||||
vec!["true"]
|
||||
};
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
@@ -48,7 +56,7 @@ async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-1",
|
||||
"argv": ["true"],
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": {},
|
||||
"tty": false,
|
||||
@@ -86,6 +94,20 @@ async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Result<()> {
|
||||
let mut server = exec_server().await?;
|
||||
let process_argv = if cfg!(windows) {
|
||||
vec!["cmd.exe", "/D", "/C", "ping -n 601 127.0.0.1 >NUL"]
|
||||
} else {
|
||||
vec![
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done",
|
||||
]
|
||||
};
|
||||
let process_env = if cfg!(windows) {
|
||||
serde_json::json!({ "PATH": std::env::var("PATH")? })
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
@@ -112,9 +134,9 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-serial-read",
|
||||
"argv": ["/bin/sh", "-c", "parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done"],
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": {},
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"pipeStdin": false,
|
||||
"arg0": null
|
||||
@@ -149,9 +171,9 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-serial-queued",
|
||||
"argv": ["/bin/sh", "-c", "parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done"],
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": {},
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"pipeStdin": false,
|
||||
"arg0": null
|
||||
@@ -159,18 +181,25 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
|
||||
)
|
||||
.await?;
|
||||
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = server.next_event().await? else {
|
||||
let response = server
|
||||
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
|
||||
.await?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = response else {
|
||||
panic!("expected the blocked process/read to finish before the queued process/start");
|
||||
};
|
||||
assert_eq!(id, read_id);
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await?
|
||||
else {
|
||||
let response = server
|
||||
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
|
||||
.await?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
|
||||
panic!("expected the queued environment/info response after process/read");
|
||||
};
|
||||
assert_eq!(id, queued_environment_info_id);
|
||||
let _: EnvironmentInfo = serde_json::from_value(result)?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await?
|
||||
else {
|
||||
let response = server
|
||||
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
|
||||
.await?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
|
||||
panic!("expected the queued process/start response after process/read");
|
||||
};
|
||||
assert_eq!(id, queued_start_id);
|
||||
@@ -182,6 +211,203 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
|
||||
}
|
||||
);
|
||||
|
||||
for process_id in ["proc-serial-read", "proc-serial-queued"] {
|
||||
let terminate_id = server
|
||||
.send_request(
|
||||
"process/terminate",
|
||||
serde_json::json!({ "processId": process_id }),
|
||||
)
|
||||
.await?;
|
||||
server
|
||||
.wait_for_event(|event| {
|
||||
matches!(
|
||||
event,
|
||||
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &terminate_id
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
server.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A long read cannot block health checks; saturated ordinary work cannot block health checks or cleanup.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_keeps_control_requests_live_during_long_reads_and_queued_requests()
|
||||
-> anyhow::Result<()> {
|
||||
let mut server = exec_server_with_env(
|
||||
std::iter::empty::<(&str, &str)>(),
|
||||
&["--concurrent-requests", "32"],
|
||||
)
|
||||
.await?;
|
||||
let process_argv = if cfg!(windows) {
|
||||
vec!["cmd.exe", "/D", "/C", "ping -n 601 127.0.0.1 >NUL"]
|
||||
} else {
|
||||
vec![
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done",
|
||||
]
|
||||
};
|
||||
let process_env = if cfg!(windows) {
|
||||
serde_json::json!({ "PATH": std::env::var("PATH")? })
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
serde_json::to_value(InitializeParams {
|
||||
client_name: "exec-server-test".to_string(),
|
||||
resume_session_id: None,
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
assert!(matches!(
|
||||
server.next_event().await?,
|
||||
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == initialize_id
|
||||
));
|
||||
server
|
||||
.send_notification("initialized", serde_json::json!({}))
|
||||
.await?;
|
||||
|
||||
let process_start_id = server
|
||||
.send_request(
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-capacity",
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"pipeStdin": false,
|
||||
"arg0": null
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
assert!(matches!(
|
||||
server.next_event().await?,
|
||||
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == process_start_id
|
||||
));
|
||||
|
||||
let read_params = serde_json::json!({
|
||||
"processId": "proc-capacity",
|
||||
"afterSeq": null,
|
||||
"maxBytes": null,
|
||||
"waitMs": 600_000
|
||||
});
|
||||
server
|
||||
.send_request("process/read", read_params.clone())
|
||||
.await?;
|
||||
let concurrent_start_id = server
|
||||
.send_request(
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-concurrent",
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"pipeStdin": false,
|
||||
"arg0": null
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await?
|
||||
else {
|
||||
panic!("expected process/start to finish before the pending process/read");
|
||||
};
|
||||
assert_eq!(id, concurrent_start_id);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<ExecResponse>(result)?,
|
||||
ExecResponse {
|
||||
process_id: ProcessId::from("proc-concurrent"),
|
||||
sandbox_type: Some(ProcessSandboxType::None),
|
||||
}
|
||||
);
|
||||
for _ in 1..32 {
|
||||
server
|
||||
.send_request("process/read", read_params.clone())
|
||||
.await?;
|
||||
}
|
||||
|
||||
let queued_read_id = server.send_request("process/read", read_params).await?;
|
||||
|
||||
let environment_info_id = server
|
||||
.send_request("environment/info", serde_json::json!({}))
|
||||
.await?;
|
||||
let response = server
|
||||
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
|
||||
.await?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
|
||||
panic!("expected environment/info response at regular request capacity");
|
||||
};
|
||||
assert_eq!(id, environment_info_id);
|
||||
let _: EnvironmentInfo = serde_json::from_value(result)?;
|
||||
|
||||
let environment_status_id = server
|
||||
.send_request("environment/status", serde_json::json!({}))
|
||||
.await?;
|
||||
let response = server
|
||||
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
|
||||
.await?;
|
||||
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
|
||||
panic!("expected environment/status response at regular request capacity");
|
||||
};
|
||||
assert_eq!(id, environment_status_id);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<EnvironmentStatus>(result)?,
|
||||
EnvironmentStatus {
|
||||
status: EnvironmentStatusKind::Ready,
|
||||
}
|
||||
);
|
||||
|
||||
let terminate_id = server
|
||||
.send_request(
|
||||
"process/terminate",
|
||||
serde_json::json!({ "processId": "proc-capacity" }),
|
||||
)
|
||||
.await?;
|
||||
let mut terminate_response = None;
|
||||
let mut queued_read_completed = false;
|
||||
while terminate_response.is_none() || !queued_read_completed {
|
||||
match server.next_event().await? {
|
||||
JSONRPCMessage::Response(JSONRPCResponse { id, result }) if id == terminate_id => {
|
||||
terminate_response = Some(serde_json::from_value::<TerminateResponse>(result)?);
|
||||
}
|
||||
JSONRPCMessage::Response(JSONRPCResponse { id, result }) if id == queued_read_id => {
|
||||
let _: ReadResponse = serde_json::from_value(result)?;
|
||||
queued_read_completed = true;
|
||||
}
|
||||
JSONRPCMessage::Error(error) => {
|
||||
anyhow::bail!("unexpected error while waiting for queued requests: {error:?}");
|
||||
}
|
||||
JSONRPCMessage::Request(_)
|
||||
| JSONRPCMessage::Response(_)
|
||||
| JSONRPCMessage::Notification(_) => {}
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
terminate_response,
|
||||
Some(TerminateResponse { running: true })
|
||||
);
|
||||
|
||||
let terminate_id = server
|
||||
.send_request(
|
||||
"process/terminate",
|
||||
serde_json::json!({ "processId": "proc-concurrent" }),
|
||||
)
|
||||
.await?;
|
||||
server
|
||||
.wait_for_event(|event| {
|
||||
matches!(
|
||||
event,
|
||||
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &terminate_id
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
server.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -189,6 +415,20 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Result<()> {
|
||||
let mut server = exec_server().await?;
|
||||
let process_argv = if cfg!(windows) {
|
||||
vec!["cmd.exe", "/D", "/C", "ping -n 2 127.0.0.1 >NUL"]
|
||||
} else {
|
||||
vec![
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"sleep 0.3; if IFS= read -r line; then printf 'read:%s\\n' \"$line\"; else printf 'eof\\n'; fi",
|
||||
]
|
||||
};
|
||||
let process_env = if cfg!(windows) {
|
||||
serde_json::json!({ "PATH": std::env::var("PATH")? })
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
@@ -216,13 +456,9 @@ async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Re
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-default-stdin",
|
||||
"argv": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"sleep 0.3; if IFS= read -r line; then printf 'read:%s\\n' \"$line\"; else printf 'eof\\n'; fi"
|
||||
],
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": {},
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"arg0": null
|
||||
}),
|
||||
@@ -284,6 +520,26 @@ async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Re
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
|
||||
let mut server = exec_server().await?;
|
||||
let process_argv = if cfg!(windows) {
|
||||
vec![
|
||||
"powershell.exe",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
"[Console]::Out.WriteLine('line:' + [Console]::In.ReadLine()); [Console]::Out.WriteLine('line:' + [Console]::In.ReadLine())",
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"IFS= read -r first; printf 'line:%s\\n' \"$first\"; IFS= read -r second; printf 'line:%s\\n' \"$second\"",
|
||||
]
|
||||
};
|
||||
let process_env = if cfg!(windows) {
|
||||
serde_json::to_value(std::env::vars().collect::<HashMap<_, _>>())?
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
@@ -311,13 +567,9 @@ async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-write-id",
|
||||
"argv": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"IFS= read -r first; printf 'line:%s\\n' \"$first\"; IFS= read -r second; printf 'line:%s\\n' \"$second\""
|
||||
],
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": {},
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"pipeStdin": true,
|
||||
"arg0": null
|
||||
@@ -401,13 +653,16 @@ async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
|
||||
.flat_map(|chunk| chunk.chunk.into_inner()),
|
||||
);
|
||||
after_seq = Some(read_response.next_seq.saturating_sub(1));
|
||||
if read_response.closed || output.ends_with(b"line:second\n") {
|
||||
if read_response.closed
|
||||
|| output.ends_with(b"line:second\n")
|
||||
|| output.ends_with(b"line:second\r\n")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(output)?,
|
||||
String::from_utf8(output)?.replace("\r\n", "\n"),
|
||||
"line:first\nline:second\n".to_string()
|
||||
);
|
||||
|
||||
@@ -418,6 +673,16 @@ async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exec_server_resumes_detached_session_without_killing_processes() -> anyhow::Result<()> {
|
||||
let mut server = exec_server().await?;
|
||||
let process_argv = if cfg!(windows) {
|
||||
vec!["cmd.exe", "/D", "/C", "ping -n 6 127.0.0.1 >NUL"]
|
||||
} else {
|
||||
vec!["/bin/sh", "-c", "sleep 5"]
|
||||
};
|
||||
let process_env = if cfg!(windows) {
|
||||
serde_json::json!({ "PATH": std::env::var("PATH")? })
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
let initialize_id = server
|
||||
.send_request(
|
||||
"initialize",
|
||||
@@ -449,9 +714,9 @@ async fn exec_server_resumes_detached_session_without_killing_processes() -> any
|
||||
"process/start",
|
||||
serde_json::json!({
|
||||
"processId": "proc-resume",
|
||||
"argv": ["/bin/sh", "-c", "sleep 5"],
|
||||
"argv": process_argv,
|
||||
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
|
||||
"env": {},
|
||||
"env": process_env,
|
||||
"tty": false,
|
||||
"pipeStdin": false,
|
||||
"arg0": null
|
||||
|
||||
Reference in New Issue
Block a user