diff --git a/main.go b/main.go index b94de2ebc..237581200 100644 --- a/main.go +++ b/main.go @@ -258,6 +258,15 @@ func copyOutput(r io.Reader, doneCh chan<- struct{}) { if runtime.GOOS == "windows" { stdout = colorable.NewColorableStdout() stderr = colorable.NewColorableStderr() + + // colorable is not concurrency-safe when stdout and stderr are the + // same console, so we need to add some synchronization to ensure that + // we can't be concurrently writing to both stderr and stdout at + // once, or else we get intermingled writes that create gibberish + // in the console. + wrapped := synchronizedWriters(stdout, stderr) + stdout = wrapped[0] + stderr = wrapped[1] } var wg sync.WaitGroup diff --git a/synchronized_writers.go b/synchronized_writers.go new file mode 100644 index 000000000..2533d1316 --- /dev/null +++ b/synchronized_writers.go @@ -0,0 +1,31 @@ +package main + +import ( + "io" + "sync" +) + +type synchronizedWriter struct { + io.Writer + mutex *sync.Mutex +} + +// synchronizedWriters takes a set of writers and returns wrappers that ensure +// that only one write can be outstanding at a time across the whole set. +func synchronizedWriters(targets ...io.Writer) []io.Writer { + mutex := &sync.Mutex{} + ret := make([]io.Writer, len(targets)) + for i, target := range targets { + ret[i] = &synchronizedWriter{ + Writer: target, + mutex: mutex, + } + } + return ret +} + +func (w *synchronizedWriter) Write(p []byte) (int, error) { + w.mutex.Lock() + defer w.mutex.Unlock() + return w.Writer.Write(p) +}