ssh remoting: Daemonize server process properly by forking and closing stdio (#19544)
This fixes the `ssh` proxy process not being notified when the proxy process dies. Turns out that the server would have stdout/stderr/stdin connected to the grand-parent ssh process connected to it and as long as the server kept running (even once it was daemonized into the background) the grand-parent ssh process wouldn't exit. That in turn meant that the Zed client wasn't notified when the proxy process died. Release Notes: - N/A --------- Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
parent
f16461d7d0
commit
b3aa7055e4
3 changed files with 63 additions and 10 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -9159,6 +9159,7 @@ dependencies = [
|
|||
"client",
|
||||
"clock",
|
||||
"env_logger",
|
||||
"fork",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"git",
|
||||
|
@ -9167,6 +9168,7 @@ dependencies = [
|
|||
"http_client",
|
||||
"language",
|
||||
"languages",
|
||||
"libc",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
|
|
|
@ -53,6 +53,10 @@ smol.workspace = true
|
|||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
fork.workspace = true
|
||||
libc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -27,6 +27,7 @@ use smol::io::AsyncReadExt;
|
|||
|
||||
use smol::Async;
|
||||
use smol::{net::unix::UnixListener, stream::StreamExt as _};
|
||||
use std::ops::ControlFlow;
|
||||
use std::{
|
||||
io::Write,
|
||||
mem,
|
||||
|
@ -305,10 +306,15 @@ pub fn execute_run(
|
|||
stdout_socket: PathBuf,
|
||||
stderr_socket: PathBuf,
|
||||
) -> Result<()> {
|
||||
let log_rx = init_logging_server(log_file)?;
|
||||
init_panic_hook();
|
||||
init_paths()?;
|
||||
|
||||
match daemonize()? {
|
||||
ControlFlow::Break(_) => return Ok(()),
|
||||
ControlFlow::Continue(_) => {}
|
||||
}
|
||||
|
||||
init_panic_hook();
|
||||
let log_rx = init_logging_server(log_file)?;
|
||||
log::info!(
|
||||
"starting up. pid_file: {:?}, stdin_socket: {:?}, stdout_socket: {:?}, stderr_socket: {:?}",
|
||||
pid_file,
|
||||
|
@ -322,8 +328,6 @@ pub fn execute_run(
|
|||
|
||||
let listeners = ServerListeners::new(stdin_socket, stdout_socket, stderr_socket)?;
|
||||
|
||||
log::info!("starting headless gpui app");
|
||||
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
gpui::App::headless().run(move |cx| {
|
||||
settings::init(cx);
|
||||
|
@ -523,7 +527,8 @@ fn spawn_server(paths: &ServerPaths) -> Result<()> {
|
|||
}
|
||||
|
||||
let binary_name = std::env::current_exe()?;
|
||||
let server_process = smol::process::Command::new(binary_name)
|
||||
let mut server_process = std::process::Command::new(binary_name);
|
||||
server_process
|
||||
.arg("run")
|
||||
.arg("--log-file")
|
||||
.arg(&paths.log_file)
|
||||
|
@ -534,12 +539,14 @@ fn spawn_server(paths: &ServerPaths) -> Result<()> {
|
|||
.arg("--stdout-socket")
|
||||
.arg(&paths.stdout_socket)
|
||||
.arg("--stderr-socket")
|
||||
.arg(&paths.stderr_socket)
|
||||
.spawn()?;
|
||||
.arg(&paths.stderr_socket);
|
||||
|
||||
log::info!(
|
||||
"proxy spawned server process. PID: {:?}",
|
||||
server_process.id()
|
||||
let status = server_process
|
||||
.status()
|
||||
.context("failed to launch server process")?;
|
||||
anyhow::ensure!(
|
||||
status.success(),
|
||||
"failed to launch and detach server process"
|
||||
);
|
||||
|
||||
let mut total_time_waited = std::time::Duration::from_secs(0);
|
||||
|
@ -745,3 +752,43 @@ fn read_proxy_settings(cx: &mut ModelContext<'_, HeadlessProject>) -> Option<Uri
|
|||
.or_else(read_proxy_from_env);
|
||||
proxy_url
|
||||
}
|
||||
|
||||
fn daemonize() -> Result<ControlFlow<()>> {
|
||||
match fork::fork().map_err(|e| anyhow::anyhow!("failed to call fork with error code {}", e))? {
|
||||
fork::Fork::Parent(_) => {
|
||||
return Ok(ControlFlow::Break(()));
|
||||
}
|
||||
fork::Fork::Child => {}
|
||||
}
|
||||
|
||||
// Once we've detached from the parent, we want to close stdout/stderr/stdin
|
||||
// so that the outer SSH process is not attached to us in any way anymore.
|
||||
unsafe { redirect_standard_streams() }?;
|
||||
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
|
||||
unsafe fn redirect_standard_streams() -> Result<()> {
|
||||
let devnull_fd = libc::open(b"/dev/null\0" as *const [u8; 10] as _, libc::O_RDWR);
|
||||
anyhow::ensure!(devnull_fd != -1, "failed to open /dev/null");
|
||||
|
||||
let process_stdio = |name, fd| {
|
||||
let reopened_fd = libc::dup2(devnull_fd, fd);
|
||||
anyhow::ensure!(
|
||||
reopened_fd != -1,
|
||||
format!("failed to redirect {} to /dev/null", name)
|
||||
);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
process_stdio("stdin", libc::STDIN_FILENO)?;
|
||||
process_stdio("stdout", libc::STDOUT_FILENO)?;
|
||||
process_stdio("stderr", libc::STDERR_FILENO)?;
|
||||
|
||||
anyhow::ensure!(
|
||||
libc::close(devnull_fd) != -1,
|
||||
"failed to close /dev/null fd after redirecting"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue