main: synchronize writes to VT100-faker on Windows

We use a third-party library "colorable" to translate VT100 color
sequences into Windows console attribute-setting calls when Terraform is
running on Windows.

colorable is not concurrency-safe for multiple writes to the same console,
because it writes to the console one character at a time and so two
concurrent writers get their characters interleaved, creating unreadable
garble.

Here we wrap around it a synchronization mechanism to ensure that there
can be only one Write call outstanding across both stderr and stdout,
mimicking the usual behavior we expect (when stderr/stdout are a normal
file handle) of each Write being completed atomically.
This commit is contained in:
Martin Atkins 2017-05-03 16:25:41 -07:00
parent e76654af01
commit 5ac311e2a9
2 changed files with 40 additions and 0 deletions

View File

@ -258,6 +258,15 @@ func copyOutput(r io.Reader, doneCh chan<- struct{}) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
stdout = colorable.NewColorableStdout() stdout = colorable.NewColorableStdout()
stderr = colorable.NewColorableStderr() 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 var wg sync.WaitGroup

31
synchronized_writers.go Normal file
View File

@ -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)
}