Skip to content

Commit

Permalink
Join input and wait tasks in external formatter Tokio command
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
the-mikedavis committed Feb 1, 2025
1 parent e9c16b7 commit c3620b7
Showing 1 changed file with 14 additions and 10 deletions.
24 changes: 14 additions & 10 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit c3620b7

Please sign in to comment.