Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
36 changes: 35 additions & 1 deletion task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 22 additions & 0 deletions testdata/parallel_failfast_cancel/Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: Failed to run task "default": task: Failed to run task "fail-dep": exit status 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: Terminated "slow-dep" because parallel running task "fail-dep" failed.