Skip to content

panic in prefixed output when stdout and stderr are written concurrently #2945

Description

@zhangzqs

Description

When the prefixed output mode is enabled, Task can panic while a command writes to stdout and stderr concurrently.

I observed the following panic with Task v3.43.3:

panic: runtime error: slice bounds out of range [2687:2453]

goroutine 178 [running]:
bytes.(*Buffer).readSlice(...)
        bytes/buffer.go:440
bytes.(*Buffer).ReadString(...)
        bytes/buffer.go:459
github.com/go-task/task/v3/internal/output.(*prefixWriter).writeOutputLines(0xc0001bab90, 0x0)
        github.com/go-task/task/v3@v3.43.3/internal/output/prefixed.go:58 +0x3a
github.com/go-task/task/v3/internal/output.(*prefixWriter).Write(...)
        github.com/go-task/task/v3@v3.43.3/internal/output/prefixed.go:49 +0x45
io.copyBuffer(...)
        io/io.go:431
os/exec.(*Cmd).writerDescriptor.func1()
        os/exec/exec.go:580

A second goroutine panicked at the same time with the same prefixWriter address.

Prefixed.WrapWriter returns one prefixWriter instance for both stdout and stderr:

pw := &prefixWriter{writer: stdOut, prefix: prefix, prefixed: p}
return pw, pw, func(error) error { return pw.close() }

os/exec copies stdout and stderr in separate goroutines, so both goroutines may call prefixWriter.Write concurrently. Write and writeOutputLines access pw.buff (bytes.Buffer) without synchronization. bytes.Buffer is not safe for concurrent use.

PR #1974 protects the shared prefix map and counter, but it does not protect each prefixWriter's buffer.

Expected behavior: concurrent stdout/stderr output should be prefixed without a data race or panic.

Version

v3.43.3. The relevant prefixWriter implementation appears unchanged on the current main branch and in v3.52.0.

Operating system

Linux 5.15.167.4-microsoft-standard-WSL2 x86_64 GNU/Linux

Experiments Enabled

None.

Example Taskfile

version: "3"

output: prefixed

tasks:
  default:
    cmds:
      - |
        for i in $(seq 1 10000); do
          echo "stdout line ${i}" &
          echo "stderr line ${i}" >&2 &
        done
        wait

Because this is a race, multiple runs or go test -race around prefixWriter may be needed for a deterministic signal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions