Cargo.lock 🔗
@@ -13521,6 +13521,7 @@ dependencies = [
"smol",
"sysinfo",
"telemetry_events",
+ "thiserror 2.0.12",
"toml 0.8.20",
"unindent",
"util",
Gwen Lg and Kirill Bulatov created
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>
Cargo.lock | 1
crates/remote_server/Cargo.toml | 1
crates/remote_server/src/main.rs | 90 +------------
crates/remote_server/src/remote_server.rs | 74 +++++++++++
crates/remote_server/src/unix.rs | 155 ++++++++++++++++++++----
5 files changed, 216 insertions(+), 105 deletions(-)
@@ -13521,6 +13521,7 @@ dependencies = [
"smol",
"sysinfo",
"telemetry_events",
+ "thiserror 2.0.12",
"toml 0.8.20",
"unindent",
"util",
@@ -65,6 +65,7 @@ telemetry_events.workspace = true
util.workspace = true
watch.workspace = true
worktree.workspace = true
+thiserror.workspace = true
[target.'cfg(not(windows))'.dependencies]
crashes.workspace = true
@@ -1,6 +1,7 @@
#![cfg_attr(target_os = "windows", allow(unused, dead_code))]
-use clap::{Parser, Subcommand};
+use clap::Parser;
+use remote_server::Commands;
use std::path::PathBuf;
#[derive(Parser)]
@@ -21,105 +22,34 @@ struct Cli {
printenv: bool,
}
-#[derive(Subcommand)]
-enum Commands {
- Run {
- #[arg(long)]
- log_file: PathBuf,
- #[arg(long)]
- pid_file: PathBuf,
- #[arg(long)]
- stdin_socket: PathBuf,
- #[arg(long)]
- stdout_socket: PathBuf,
- #[arg(long)]
- stderr_socket: PathBuf,
- },
- Proxy {
- #[arg(long)]
- reconnect: bool,
- #[arg(long)]
- identifier: String,
- },
- Version,
-}
-
#[cfg(windows)]
fn main() {
unimplemented!()
}
#[cfg(not(windows))]
-fn main() {
- use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
- use remote::proxy::ProxyLaunchError;
- use remote_server::unix::{execute_proxy, execute_run};
-
+fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
if let Some(socket_path) = &cli.askpass {
askpass::main(socket_path);
- return;
+ return Ok(());
}
if let Some(socket) = &cli.crash_handler {
crashes::crash_server(socket.as_path());
- return;
+ return Ok(());
}
if cli.printenv {
util::shell_env::print_env();
- return;
+ return Ok(());
}
- let result = match cli.command {
- Some(Commands::Run {
- log_file,
- pid_file,
- stdin_socket,
- stdout_socket,
- stderr_socket,
- }) => execute_run(
- log_file,
- pid_file,
- stdin_socket,
- stdout_socket,
- stderr_socket,
- ),
- Some(Commands::Proxy {
- identifier,
- reconnect,
- }) => match execute_proxy(identifier, reconnect) {
- Ok(_) => Ok(()),
- Err(err) => {
- if let Some(err) = err.downcast_ref::<ProxyLaunchError>() {
- std::process::exit(err.to_exit_code());
- }
- Err(err)
- }
- },
- Some(Commands::Version) => {
- let release_channel = *RELEASE_CHANNEL;
- match release_channel {
- ReleaseChannel::Stable | ReleaseChannel::Preview => {
- println!("{}", env!("ZED_PKG_VERSION"))
- }
- ReleaseChannel::Nightly | ReleaseChannel::Dev => {
- println!(
- "{}",
- option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name())
- )
- }
- };
- std::process::exit(0);
- }
- None => {
- eprintln!("usage: remote <run|proxy|version>");
- std::process::exit(1);
- }
- };
- if let Err(error) = result {
- log::error!("exiting due to error: {}", error);
+ if let Some(command) = cli.command {
+ remote_server::run(command)
+ } else {
+ eprintln!("usage: remote <run|proxy|version>");
std::process::exit(1);
}
}
@@ -6,4 +6,78 @@ pub mod unix;
#[cfg(test)]
mod remote_editing_tests;
+use clap::Subcommand;
+use std::path::PathBuf;
+
pub use headless_project::{HeadlessAppState, HeadlessProject};
+
+#[derive(Subcommand)]
+pub enum Commands {
+ Run {
+ #[arg(long)]
+ log_file: PathBuf,
+ #[arg(long)]
+ pid_file: PathBuf,
+ #[arg(long)]
+ stdin_socket: PathBuf,
+ #[arg(long)]
+ stdout_socket: PathBuf,
+ #[arg(long)]
+ stderr_socket: PathBuf,
+ },
+ Proxy {
+ #[arg(long)]
+ reconnect: bool,
+ #[arg(long)]
+ identifier: String,
+ },
+ Version,
+}
+
+#[cfg(not(windows))]
+pub fn run(command: Commands) -> anyhow::Result<()> {
+ use anyhow::Context;
+ use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
+ use unix::{ExecuteProxyError, execute_proxy, execute_run};
+
+ match command {
+ Commands::Run {
+ log_file,
+ pid_file,
+ stdin_socket,
+ stdout_socket,
+ stderr_socket,
+ } => execute_run(
+ log_file,
+ pid_file,
+ stdin_socket,
+ stdout_socket,
+ stderr_socket,
+ ),
+ Commands::Proxy {
+ identifier,
+ reconnect,
+ } => execute_proxy(identifier, reconnect)
+ .inspect_err(|err| {
+ if let ExecuteProxyError::ServerNotRunning(err) = err {
+ std::process::exit(err.to_exit_code());
+ }
+ })
+ .context("running proxy on the remote server"),
+ Commands::Version => {
+ let release_channel = *RELEASE_CHANNEL;
+ match release_channel {
+ ReleaseChannel::Stable | ReleaseChannel::Preview => {
+ println!("{}", env!("ZED_PKG_VERSION"))
+ }
+ ReleaseChannel::Nightly | ReleaseChannel::Dev => {
+ println!(
+ "{}",
+ option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name())
+ )
+ }
+ };
+ Ok(())
+ }
+ }
+}
@@ -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)
}
}