diff --git a/brush-core/src/interp.rs b/brush-core/src/interp.rs index ddf637f11..668632cab 100644 --- a/brush-core/src/interp.rs +++ b/brush-core/src/interp.rs @@ -630,10 +630,26 @@ impl ExecuteInPipeline for ast::Command { } } - Ok(compound - .execute(&mut pipeline_context.shell, ¶ms) - .await? - .into()) + match pipeline_context.shell { + // When we own our shell, we're one of multiple stages in a pipeline; spawn so + // we run concurrently with the others. Running inline would block forever once + // we filled the pipe, because the stage that drains it isn't started until we + // return. + commands::ShellForCommand::OwnedShell { target, .. } => { + let mut shell = *target; + // N.B. We clone the command so the spawned task can own it. + let compound = compound.clone(); + Ok(ExecutionSpawnResult::StartedTask( + tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Handle::current(); + rt.block_on(compound.execute(&mut shell, ¶ms)) + }), + )) + } + commands::ShellForCommand::ParentShell(shell) => { + Ok(compound.execute(shell, ¶ms).await?.into()) + } + } } Self::Function(func) => Ok(func .execute(&mut pipeline_context.shell, ¶ms) diff --git a/brush-shell/tests/cases/compat/pipeline.yaml b/brush-shell/tests/cases/compat/pipeline.yaml index b7a4f555c..877c5a397 100644 --- a/brush-shell/tests/cases/compat/pipeline.yaml +++ b/brush-shell/tests/cases/compat/pipeline.yaml @@ -58,3 +58,8 @@ cases: stdin: | printf '%s\n' {0..10000} | x=1 echo "Last: $?, PIPESTATUS: ${PIPESTATUS[*]}" + + - name: "Compound command writing more than a pipe buffer to a later stage" + stdin: | + i=0 + while [ $i -lt 4096 ]; do printf '%s\n' 0123456789012345678901234567890; i=$((i+1)); done | wc -l