Skip to content

Commit 27addc5

Browse files
committed
fix!: output race condition
Use a mutex to guard setting/getting the color profile and fg/bg colors. Use `SetColorProfile` to change the output color profile. Use pointer receiver since we have a lock in the struct. Fixes: charmbracelet/lipgloss#210 Fixes: #145
1 parent 7165365 commit 27addc5

File tree

10 files changed

+118
-70
lines changed

10 files changed

+118
-70
lines changed

copy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
// Copy copies text to clipboard using OSC 52 escape sequence.
10-
func (o Output) Copy(str string) {
10+
func (o *Output) Copy(str string) {
1111
s := osc52.New(str)
1212
if strings.HasPrefix(o.environ.Getenv("TERM"), "screen") {
1313
s = s.Screen()
@@ -17,7 +17,7 @@ func (o Output) Copy(str string) {
1717

1818
// CopyPrimary copies text to primary clipboard (X11) using OSC 52 escape
1919
// sequence.
20-
func (o Output) CopyPrimary(str string) {
20+
func (o *Output) CopyPrimary(str string) {
2121
s := osc52.New(str).Primary()
2222
if strings.HasPrefix(o.environ.Getenv("TERM"), "screen") {
2323
s = s.Screen()

output.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type OutputOption = func(*Output)
2222

2323
// Output is a terminal output.
2424
type Output struct {
25-
Profile
25+
profile Profile
2626
tty io.Writer
2727
environ Environ
2828

@@ -33,6 +33,8 @@ type Output struct {
3333
fgColor Color
3434
bgSync *sync.Once
3535
bgColor Color
36+
37+
mtx sync.RWMutex
3638
}
3739

3840
// Environ is an interface for getting environment variables.
@@ -66,7 +68,7 @@ func NewOutput(tty io.Writer, opts ...OutputOption) *Output {
6668
o := &Output{
6769
tty: tty,
6870
environ: &osEnviron{},
69-
Profile: -1,
71+
profile: -1,
7072
fgSync: &sync.Once{},
7173
fgColor: NoColor{},
7274
bgSync: &sync.Once{},
@@ -79,8 +81,8 @@ func NewOutput(tty io.Writer, opts ...OutputOption) *Output {
7981
for _, opt := range opts {
8082
opt(o)
8183
}
82-
if o.Profile < 0 {
83-
o.Profile = o.EnvColorProfile()
84+
if o.profile < 0 {
85+
o.profile = o.EnvColorProfile()
8486
}
8587

8688
return o
@@ -96,7 +98,7 @@ func WithEnvironment(environ Environ) OutputOption {
9698
// WithProfile returns a new OutputOption for the given profile.
9799
func WithProfile(profile Profile) OutputOption {
98100
return func(o *Output) {
99-
o.Profile = profile
101+
o.profile = profile
100102
}
101103
}
102104

@@ -135,7 +137,16 @@ func WithUnsafe() OutputOption {
135137

136138
// ColorProfile returns the supported color profile:
137139
func (o Output) ColorProfile() Profile {
138-
return o.Profile
140+
o.mtx.RLock()
141+
defer o.mtx.RUnlock()
142+
return o.profile
143+
}
144+
145+
// SetColorProfile sets the color profile.
146+
func (o *Output) SetColorProfile(profile Profile) {
147+
o.mtx.Lock()
148+
defer o.mtx.Unlock()
149+
o.profile = profile
139150
}
140151

141152
// ForegroundColor returns the terminal's default foreground color.
@@ -145,7 +156,9 @@ func (o *Output) ForegroundColor() Color {
145156
return
146157
}
147158

159+
o.mtx.Lock()
148160
o.fgColor = o.foregroundColor()
161+
o.mtx.Unlock()
149162
}
150163

151164
if o.cache {
@@ -154,6 +167,8 @@ func (o *Output) ForegroundColor() Color {
154167
f()
155168
}
156169

170+
o.mtx.RLock()
171+
defer o.mtx.RUnlock()
157172
return o.fgColor
158173
}
159174

@@ -164,7 +179,9 @@ func (o *Output) BackgroundColor() Color {
164179
return
165180
}
166181

182+
o.mtx.Lock()
167183
o.bgColor = o.backgroundColor()
184+
o.mtx.Unlock()
168185
}
169186

170187
if o.cache {
@@ -173,6 +190,8 @@ func (o *Output) BackgroundColor() Color {
173190
f()
174191
}
175192

193+
o.mtx.RLock()
194+
defer o.mtx.RUnlock()
176195
return o.bgColor
177196
}
178197

@@ -185,18 +204,18 @@ func (o *Output) HasDarkBackground() bool {
185204

186205
// TTY returns the terminal's file descriptor. This may be nil if the output is
187206
// not a terminal.
188-
func (o Output) TTY() File {
207+
func (o *Output) TTY() File {
189208
if f, ok := o.tty.(File); ok {
190209
return f
191210
}
192211
return nil
193212
}
194213

195-
func (o Output) Write(p []byte) (int, error) {
214+
func (o *Output) Write(p []byte) (int, error) {
196215
return o.tty.Write(p)
197216
}
198217

199218
// WriteString writes the given string to the output.
200-
func (o Output) WriteString(s string) (int, error) {
219+
func (o *Output) WriteString(s string) (int, error) {
201220
return o.Write([]byte(s))
202221
}

output_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package termenv
2+
3+
import (
4+
"io"
5+
"testing"
6+
)
7+
8+
func TestOutputRace(t *testing.T) {
9+
o := NewOutput(io.Discard)
10+
for i := 0; i < 100; i++ {
11+
t.Run("Test race", func(t *testing.T) {
12+
t.Parallel()
13+
o.Write([]byte("test"))
14+
o.SetColorProfile(ANSI)
15+
o.ColorProfile()
16+
o.HasDarkBackground()
17+
o.TTY()
18+
o.ForegroundColor()
19+
o.BackgroundColor()
20+
})
21+
}
22+
}

0 commit comments

Comments
 (0)