Unnamed repository; edit this file 'description' to name the repository.
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.
Michael Davis 2025-02-01
parent e9c16b7 · commit c3620b7
-rw-r--r--helix-view/src/document.rs24
1 files changed, 14 insertions, 10 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 0ffa2c5b..277b582e 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -796,17 +796,21 @@ impl Document {
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() {