aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordivens <divens@users.noreply.github.com>2026-03-19 08:58:13 +0100
committerGitHub <noreply@github.com>2026-03-19 08:58:13 +0100
commit62633f930d9970e3f2ef4b0afb924ff75dbba870 (patch)
tree3537a8637a1b482e91f0bcc0c347fb8e163c8f4a
parentFix Windows config/data paths and consolidate path helpers (#4852) (diff)
downloadsl0ppij-62633f930d9970e3f2ef4b0afb924ff75dbba870.tar.gz
sl0ppij-62633f930d9970e3f2ef4b0afb924ff75dbba870.tar.xz
sl0ppij-62633f930d9970e3f2ef4b0afb924ff75dbba870.zip
fix(windows): avoid hang when share plugin starts web server (#4863)
Use cmd.status() instead of cmd.output() on Windows to prevent pipe handle inheritance from blocking indefinitely. cmd.output() creates pipe handles that the grandchild web server process inherits via CreateProcess bInheritHandles, keeping them open and preventing EOF. Co-authored-by: divens <divens.dev@gmail.com>
-rw-r--r--zellij-client/src/lib.rs44
1 files changed, 43 insertions, 1 deletions
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index 95a0810f..bef96e25 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -221,7 +221,7 @@ impl ErrorInstruction for ClientInstruction {
}
}
-#[cfg(feature = "web_server_capability")]
+#[cfg(all(feature = "web_server_capability", not(windows)))]
fn spawn_web_server(cli_args: &CliArgs) -> Result<String, String> {
let mut cmd = Command::new(current_exe().map_err(|e| e.to_string())?);
if let Some(config_file_path) = Config::config_file_path(cli_args) {
@@ -252,6 +252,48 @@ fn spawn_web_server(cli_args: &CliArgs) -> Result<String, String> {
}
}
+/// On Windows, cmd.output() creates pipe handles for stdout/stderr. The child
+/// (zellij web -d) spawns a grandchild (the web server) which inherits these
+/// pipe handles. cmd.output() waits for EOF on the pipes, but the long-lived
+/// grandchild keeps them open — hanging forever.
+///
+/// Redirecting the grandchild's stdio to null is not sufficient: on Windows,
+/// CreateProcess with bInheritHandles=TRUE inherits ALL inheritable handles,
+/// not just the stdio handles specified in STARTUPINFO. The pipe handles leak
+/// through regardless of the grandchild's stdio configuration.
+///
+/// Use cmd.status() instead: no pipes are created, so nothing to hang on.
+#[cfg(all(feature = "web_server_capability", windows))]
+fn spawn_web_server(cli_args: &CliArgs) -> Result<String, String> {
+ let mut cmd = Command::new(current_exe().map_err(|e| e.to_string())?);
+ if let Some(config_file_path) = Config::config_file_path(cli_args) {
+ let config_file_path_exists = Path::new(&config_file_path).exists();
+ if !config_file_path_exists {
+ return Err(format!(
+ "Config file: {} does not exist",
+ config_file_path.display()
+ ));
+ }
+ cmd.arg("--config");
+ cmd.arg(format!("{}", config_file_path.display()));
+ }
+ cmd.arg("web");
+ cmd.arg("-d");
+ match cmd.status() {
+ Ok(status) => {
+ if status.success() {
+ Ok(String::new())
+ } else {
+ Err(format!(
+ "Web server process exited with code: {}",
+ status.code().unwrap_or(-1)
+ ))
+ }
+ },
+ Err(e) => Err(e.to_string()),
+ }
+}
+
#[cfg(not(feature = "web_server_capability"))]
fn spawn_web_server(_cli_args: &CliArgs) -> Result<String, String> {
log::error!(