changeset 6501:4e371d250f73

Join input and wait tasks in external formatter Tokio command This matches the layout of `shell_impl_async` in `commands.rs` and avoids a hang or maybe deadlock in `to_writer`'s calls to `tokio::io::AsyncWriteExt::write_all`. I don't really understand the underlying cause of the hang but it seems it's necessary to spawn a new tokio task to provide input to stdin. This is shown in an example in `tokio::process::Child::wait` but not documented explicitly.
author Michael Davis <mcarsondavis@gmail.com>
date Sat, 01 Feb 2025 10:31:42 -0500
parents 9be91b29148d
children 52004cac7fd4
files helix-view/src/document.rs
diffstat 1 files changed, 14 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/helix-view/src/document.rs	Sat Feb 01 09:10:04 2025 -0500
+++ b/helix-view/src/document.rs	Sat Feb 01 10:31:42 2025 -0500
@@ -796,17 +796,21 @@
                         command: fmt_cmd.to_string_lossy().into(),
                         error: e.kind(),
                     })?;
-                {
-                    let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
-                    to_writer(&mut stdin, (encoding::UTF_8, false), &text)
-                        .await
-                        .map_err(|_| FormatterError::BrokenStdin)?;
-                }
 
-                let output = process
-                    .wait_with_output()
-                    .await
-                    .map_err(|_| FormatterError::WaitForOutputFailed)?;
+                let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
+                let input_text = text.clone();
+                let input_task = tokio::spawn(async move {
+                    to_writer(&mut stdin, (encoding::UTF_8, false), &input_text).await
+                    // Note that `stdin` is dropped here, causing the pipe to close. This can
+                    // avoid a deadlock with `wait_with_output` below if the process is waiting on
+                    // stdin to close before exiting.
+                });
+                let (input_result, output_result) = tokio::join! {
+                    input_task,
+                    process.wait_with_output(),
+                };
+                let _ = input_result.map_err(|_| FormatterError::BrokenStdin)?;
+                let output = output_result.map_err(|_| FormatterError::WaitForOutputFailed)?;
 
                 if !output.status.success() {
                     if !output.stderr.is_empty() {