remote_server: Improve error reporting (#33770)

Closes #33736

Use `thiserror` to implement error stack and `anyhow` to report is to
user.
Also move some code from main to remote_server to have better crate
isolation.

Release Notes:

- N/A

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Gwen Lg 2025-08-25 22:23:29 +02:00 committed by GitHub
parent 99cee8778c
commit ad25aba990
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 216 additions and 105 deletions

View file

@ -36,6 +36,7 @@ use smol::Async;
use smol::{net::unix::UnixListener, stream::StreamExt as _};
use std::ffi::OsStr;
use std::ops::ControlFlow;
use std::process::ExitStatus;
use std::str::FromStr;
use std::sync::LazyLock;
use std::{env, thread};
@ -46,6 +47,7 @@ use std::{
sync::Arc,
};
use telemetry_events::LocationData;
use thiserror::Error;
use util::ResultExt;
pub static VERSION: LazyLock<&str> = LazyLock::new(|| match *RELEASE_CHANNEL {
@ -526,7 +528,23 @@ pub fn execute_run(
Ok(())
}
#[derive(Clone)]
#[derive(Debug, Error)]
pub(crate) enum ServerPathError {
#[error("Failed to create server_dir `{path}`")]
CreateServerDir {
#[source]
source: std::io::Error,
path: PathBuf,
},
#[error("Failed to create logs_dir `{path}`")]
CreateLogsDir {
#[source]
source: std::io::Error,
path: PathBuf,
},
}
#[derive(Clone, Debug)]
struct ServerPaths {
log_file: PathBuf,
pid_file: PathBuf,
@ -536,10 +554,19 @@ struct ServerPaths {
}
impl ServerPaths {
fn new(identifier: &str) -> Result<Self> {
fn new(identifier: &str) -> Result<Self, ServerPathError> {
let server_dir = paths::remote_server_state_dir().join(identifier);
std::fs::create_dir_all(&server_dir)?;
std::fs::create_dir_all(&logs_dir())?;
std::fs::create_dir_all(&server_dir).map_err(|source| {
ServerPathError::CreateServerDir {
source,
path: server_dir.clone(),
}
})?;
let log_dir = logs_dir();
std::fs::create_dir_all(log_dir).map_err(|source| ServerPathError::CreateLogsDir {
source: source,
path: log_dir.clone(),
})?;
let pid_file = server_dir.join("server.pid");
let stdin_socket = server_dir.join("stdin.sock");
@ -557,7 +584,43 @@ impl ServerPaths {
}
}
pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
#[derive(Debug, Error)]
pub(crate) enum ExecuteProxyError {
#[error("Failed to init server paths")]
ServerPath(#[from] ServerPathError),
#[error(transparent)]
ServerNotRunning(#[from] ProxyLaunchError),
#[error("Failed to check PidFile '{path}'")]
CheckPidFile {
#[source]
source: CheckPidError,
path: PathBuf,
},
#[error("Failed to kill existing server with pid '{pid}'")]
KillRunningServer {
#[source]
source: std::io::Error,
pid: u32,
},
#[error("failed to spawn server")]
SpawnServer(#[source] SpawnServerError),
#[error("stdin_task failed")]
StdinTask(#[source] anyhow::Error),
#[error("stdout_task failed")]
StdoutTask(#[source] anyhow::Error),
#[error("stderr_task failed")]
StderrTask(#[source] anyhow::Error),
}
pub(crate) fn execute_proxy(
identifier: String,
is_reconnecting: bool,
) -> Result<(), ExecuteProxyError> {
init_logging_proxy();
let server_paths = ServerPaths::new(&identifier)?;
@ -574,12 +637,19 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
log::info!("starting proxy process. PID: {}", std::process::id());
let server_pid = check_pid_file(&server_paths.pid_file)?;
let server_pid = check_pid_file(&server_paths.pid_file).map_err(|source| {
ExecuteProxyError::CheckPidFile {
source,
path: server_paths.pid_file.clone(),
}
})?;
let server_running = server_pid.is_some();
if is_reconnecting {
if !server_running {
log::error!("attempted to reconnect, but no server running");
anyhow::bail!(ProxyLaunchError::ServerNotRunning);
return Err(ExecuteProxyError::ServerNotRunning(
ProxyLaunchError::ServerNotRunning,
));
}
} else {
if let Some(pid) = server_pid {
@ -590,7 +660,7 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
kill_running_server(pid, &server_paths)?;
}
spawn_server(&server_paths)?;
spawn_server(&server_paths).map_err(ExecuteProxyError::SpawnServer)?;
};
let stdin_task = smol::spawn(async move {
@ -630,9 +700,9 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
if let Err(forwarding_result) = smol::block_on(async move {
futures::select! {
result = stdin_task.fuse() => result.context("stdin_task failed"),
result = stdout_task.fuse() => result.context("stdout_task failed"),
result = stderr_task.fuse() => result.context("stderr_task failed"),
result = stdin_task.fuse() => result.map_err(ExecuteProxyError::StdinTask),
result = stdout_task.fuse() => result.map_err(ExecuteProxyError::StdoutTask),
result = stderr_task.fuse() => result.map_err(ExecuteProxyError::StderrTask),
}
}) {
log::error!(
@ -645,12 +715,12 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
Ok(())
}
fn kill_running_server(pid: u32, paths: &ServerPaths) -> Result<()> {
fn kill_running_server(pid: u32, paths: &ServerPaths) -> Result<(), ExecuteProxyError> {
log::info!("killing existing server with PID {}", pid);
std::process::Command::new("kill")
.arg(pid.to_string())
.output()
.context("failed to kill existing server")?;
.map_err(|source| ExecuteProxyError::KillRunningServer { source, pid })?;
for file in [
&paths.pid_file,
@ -664,18 +734,39 @@ fn kill_running_server(pid: u32, paths: &ServerPaths) -> Result<()> {
Ok(())
}
fn spawn_server(paths: &ServerPaths) -> Result<()> {
#[derive(Debug, Error)]
pub(crate) enum SpawnServerError {
#[error("failed to remove stdin socket")]
RemoveStdinSocket(#[source] std::io::Error),
#[error("failed to remove stdout socket")]
RemoveStdoutSocket(#[source] std::io::Error),
#[error("failed to remove stderr socket")]
RemoveStderrSocket(#[source] std::io::Error),
#[error("failed to get current_exe")]
CurrentExe(#[source] std::io::Error),
#[error("failed to launch server process")]
ProcessStatus(#[source] std::io::Error),
#[error("failed to launch and detach server process: {status}\n{paths}")]
LaunchStatus { status: ExitStatus, paths: String },
}
fn spawn_server(paths: &ServerPaths) -> Result<(), SpawnServerError> {
if paths.stdin_socket.exists() {
std::fs::remove_file(&paths.stdin_socket)?;
std::fs::remove_file(&paths.stdin_socket).map_err(SpawnServerError::RemoveStdinSocket)?;
}
if paths.stdout_socket.exists() {
std::fs::remove_file(&paths.stdout_socket)?;
std::fs::remove_file(&paths.stdout_socket).map_err(SpawnServerError::RemoveStdoutSocket)?;
}
if paths.stderr_socket.exists() {
std::fs::remove_file(&paths.stderr_socket)?;
std::fs::remove_file(&paths.stderr_socket).map_err(SpawnServerError::RemoveStderrSocket)?;
}
let binary_name = std::env::current_exe()?;
let binary_name = std::env::current_exe().map_err(SpawnServerError::CurrentExe)?;
let mut server_process = std::process::Command::new(binary_name);
server_process
.arg("run")
@ -692,11 +783,17 @@ fn spawn_server(paths: &ServerPaths) -> Result<()> {
let status = server_process
.status()
.context("failed to launch server process")?;
anyhow::ensure!(
status.success(),
"failed to launch and detach server process"
);
.map_err(SpawnServerError::ProcessStatus)?;
if !status.success() {
return Err(SpawnServerError::LaunchStatus {
status,
paths: format!(
"log file: {:?}, pid file: {:?}",
paths.log_file, paths.pid_file,
),
});
}
let mut total_time_waited = std::time::Duration::from_secs(0);
let wait_duration = std::time::Duration::from_millis(20);
@ -717,7 +814,15 @@ fn spawn_server(paths: &ServerPaths) -> Result<()> {
Ok(())
}
fn check_pid_file(path: &Path) -> Result<Option<u32>> {
#[derive(Debug, Error)]
#[error("Failed to remove PID file for missing process (pid `{pid}`")]
pub(crate) struct CheckPidError {
#[source]
source: std::io::Error,
pid: u32,
}
fn check_pid_file(path: &Path) -> Result<Option<u32>, CheckPidError> {
let Some(pid) = std::fs::read_to_string(&path)
.ok()
.and_then(|contents| contents.parse::<u32>().ok())
@ -742,7 +847,7 @@ fn check_pid_file(path: &Path) -> Result<Option<u32>> {
log::debug!(
"Found PID file, but process with that PID does not exist. Removing PID file."
);
std::fs::remove_file(&path).context("Failed to remove PID file")?;
std::fs::remove_file(&path).map_err(|source| CheckPidError { source, pid })?;
Ok(None)
}
}