diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a230703c..b858520a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Report which parallel task caused cancellation when `failfast` stops sibling + tasks. Cancelled tasks now log a clear message instead of failing silently + (#1226 by @20ns). - Considerably improve performance of fingerprinting on large repositories (monorepos). Fingerprinting is up to 86% faster and make up to 70% fewer memory allocations on the more advanced scenarios. Benchmarks were added as diff --git a/executor_test.go b/executor_test.go index 51fb1fce9d..0e093a3559 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1223,6 +1223,48 @@ func TestFailfast(t *testing.T) { }) } +func TestParallelFailfastCancellationReason(t *testing.T) { + t.Parallel() + + t.Run("CLIParallel", func(t *testing.T) { + t.Parallel() + + var buffer SyncBuffer + e := task.NewExecutor( + task.WithDir("testdata/parallel_failfast_cancel"), + task.WithStdout(&buffer), + task.WithStderr(&buffer), + task.WithSilent(true), + task.WithParallel(true), + task.WithFailfast(true), + ) + require.NoError(t, e.Setup()) + + err := e.Run(t.Context(), + &task.Call{Task: "slow"}, + &task.Call{Task: "fail"}, + ) + require.Error(t, err) + require.Contains(t, err.Error(), `Failed to run task "fail"`) + require.Contains(t, buffer.buf.String(), `task: Terminated "slow" because parallel running task "fail" failed.`) + require.NotContains(t, buffer.buf.String(), `Terminated "fail"`) + }) + + t.Run("Deps", func(t *testing.T) { + t.Parallel() + + NewExecutorTest(t, + WithName("default"), + WithExecutorOptions( + task.WithDir("testdata/parallel_failfast_cancel"), + task.WithSilent(true), + ), + WithPostProcessFn(PPSortedLines), + WithRunError(), + ) + }) +} + func TestIf(t *testing.T) { t.Parallel() diff --git a/task.go b/task.go index 98d340c976..b65a935388 100644 --- a/task.go +++ b/task.go @@ -90,7 +90,11 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error { } for _, c := range regularCalls { if e.Parallel { - g.Go(func() error { return e.RunTask(ctx, c) }) + g.Go(func() error { + err := e.RunTask(ctx, c) + e.reportParallelCancellation(ctx, c.Task, err) + return err + }) } else { if err := e.RunTask(ctx, c); err != nil { return err @@ -327,6 +331,7 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error { for _, d := range t.Deps { g.Go(func() error { err := e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true}) + e.reportParallelCancellation(ctx, d.Task, err) if err != nil { return err } @@ -337,6 +342,35 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error { return g.Wait() } +// reportParallelCancellation prints why a parallel task was stopped when +// failfast cancelled it because another parallel task failed. +func (e *Executor) reportParallelCancellation(ctx context.Context, taskName string, err error) { + if err == nil || !errors.Is(err, context.Canceled) { + return + } + + cause := context.Cause(ctx) + if cause == nil || errors.Is(cause, context.Canceled) { + return + } + + var causeRunErr *errors.TaskRunError + if !errors.As(cause, &causeRunErr) { + e.Logger.Errf(logger.Red, "task: Terminated %q because a parallel running task failed.\n", taskName) + return + } + if causeRunErr.TaskName == taskName { + return + } + + e.Logger.Errf( + logger.Red, + "task: Terminated %q because parallel running task %q failed.\n", + taskName, + causeRunErr.TaskName, + ) +} + func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, deferredExitCode *uint8) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/testdata/failfast/default/testdata/TestFailfast-Option-default.golden b/testdata/failfast/default/testdata/TestFailfast-Option-default.golden index 8b13789179..d22c408327 100644 --- a/testdata/failfast/default/testdata/TestFailfast-Option-default.golden +++ b/testdata/failfast/default/testdata/TestFailfast-Option-default.golden @@ -1 +1,3 @@ - +task: Terminated "dep1" because parallel running task "dep4" failed. +task: Terminated "dep2" because parallel running task "dep4" failed. +task: Terminated "dep3" because parallel running task "dep4" failed. diff --git a/testdata/failfast/task/testdata/TestFailfast-Task-task.golden b/testdata/failfast/task/testdata/TestFailfast-Task-task.golden index 8b13789179..d22c408327 100644 --- a/testdata/failfast/task/testdata/TestFailfast-Task-task.golden +++ b/testdata/failfast/task/testdata/TestFailfast-Task-task.golden @@ -1 +1,3 @@ - +task: Terminated "dep1" because parallel running task "dep4" failed. +task: Terminated "dep2" because parallel running task "dep4" failed. +task: Terminated "dep3" because parallel running task "dep4" failed. diff --git a/testdata/parallel_failfast_cancel/Taskfile.yaml b/testdata/parallel_failfast_cancel/Taskfile.yaml new file mode 100644 index 0000000000..357b2cb827 --- /dev/null +++ b/testdata/parallel_failfast_cancel/Taskfile.yaml @@ -0,0 +1,22 @@ +version: '3' + +tasks: + slow: + cmds: + - sleep 5 + fail: + cmds: + - exit 1 + + default: + failfast: true + deps: + - slow-dep + - fail-dep + + slow-dep: + cmds: + - sleep 5 + fail-dep: + cmds: + - exit 1 diff --git a/testdata/parallel_failfast_cancel/testdata/TestParallelFailfastCancellationReason-Deps-default-err-run.golden b/testdata/parallel_failfast_cancel/testdata/TestParallelFailfastCancellationReason-Deps-default-err-run.golden new file mode 100644 index 0000000000..cc3bf59eb6 --- /dev/null +++ b/testdata/parallel_failfast_cancel/testdata/TestParallelFailfastCancellationReason-Deps-default-err-run.golden @@ -0,0 +1 @@ +task: Failed to run task "default": task: Failed to run task "fail-dep": exit status 1 diff --git a/testdata/parallel_failfast_cancel/testdata/TestParallelFailfastCancellationReason-Deps-default.golden b/testdata/parallel_failfast_cancel/testdata/TestParallelFailfastCancellationReason-Deps-default.golden new file mode 100644 index 0000000000..9c2dd2165d --- /dev/null +++ b/testdata/parallel_failfast_cancel/testdata/TestParallelFailfastCancellationReason-Deps-default.golden @@ -0,0 +1 @@ +task: Terminated "slow-dep" because parallel running task "fail-dep" failed.