From 5ac311e2a91e381e2f52234668b49ba670aa0fe5 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 3 May 2017 16:25:41 -0700 Subject: [PATCH] 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. --- main.go | 9 +++++++++ synchronized_writers.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 synchronized_writers.go 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) +}