Bugfix/datarace prompt (#81)

* fix data race in PromptLen

* add lock for all operation in RuneBuffer

* add race test

* update travis
This commit is contained in:
chzyer 2016-09-15 18:11:25 +08:00 committed by GitHub
parent 820d6f2766
commit 283f5429f7
3 changed files with 65 additions and 6 deletions

View File

@ -1,10 +1,9 @@
language: go language: go
go: go:
- 1.5 - 1.5
before_install: - 1.7
- go get golang.org/x/crypto/ssh/terminal
script: script:
- GOOS=windows go install github.com/chzyer/readline/example/... - GOOS=windows go install github.com/chzyer/readline/example/...
- GOOS=linux go install github.com/chzyer/readline/example/... - GOOS=linux go install github.com/chzyer/readline/example/...
- GOOS=darwin go install github.com/chzyer/readline/example/... - GOOS=darwin go install github.com/chzyer/readline/example/...
- go test -v - go test -race -v

27
readline_test.go Normal file
View File

@ -0,0 +1,27 @@
package readline
import (
"testing"
"time"
)
func TestRace(t *testing.T) {
rl, err := NewEx(&Config{})
if err != nil {
t.Fatal(err)
return
}
go func() {
for range time.Tick(time.Millisecond) {
rl.SetPrompt("hello")
}
}()
go func() {
time.Sleep(100 * time.Millisecond)
rl.Close()
}()
rl.Readline()
}

View File

@ -33,11 +33,15 @@ type RuneBuffer struct {
} }
func (r *RuneBuffer) OnWidthChange(newWidth int) { func (r *RuneBuffer) OnWidthChange(newWidth int) {
r.Lock()
r.width = newWidth r.width = newWidth
r.Unlock()
} }
func (r *RuneBuffer) Backup() { func (r *RuneBuffer) Backup() {
r.Lock()
r.bck = &runeBufferBck{r.buf, r.idx} r.bck = &runeBufferBck{r.buf, r.idx}
r.Unlock()
} }
func (r *RuneBuffer) Restore() { func (r *RuneBuffer) Restore() {
@ -62,15 +66,21 @@ func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuff
} }
func (r *RuneBuffer) SetConfig(cfg *Config) { func (r *RuneBuffer) SetConfig(cfg *Config) {
r.Lock()
r.cfg = cfg r.cfg = cfg
r.interactive = cfg.useInteractive() r.interactive = cfg.useInteractive()
r.Unlock()
} }
func (r *RuneBuffer) SetMask(m rune) { func (r *RuneBuffer) SetMask(m rune) {
r.Lock()
r.cfg.MaskRune = m r.cfg.MaskRune = m
r.Unlock()
} }
func (r *RuneBuffer) CurrentWidth(x int) int { func (r *RuneBuffer) CurrentWidth(x int) int {
r.Lock()
defer r.Unlock()
return runes.WidthAll(r.buf[:x]) return runes.WidthAll(r.buf[:x])
} }
@ -86,6 +96,9 @@ func (r *RuneBuffer) promptLen() int {
} }
func (r *RuneBuffer) RuneSlice(i int) []rune { func (r *RuneBuffer) RuneSlice(i int) []rune {
r.Lock()
defer r.Unlock()
if i > 0 { if i > 0 {
rs := make([]rune, i) rs := make([]rune, i)
copy(rs, r.buf[r.idx:r.idx+i]) copy(rs, r.buf[r.idx:r.idx+i])
@ -97,16 +110,22 @@ func (r *RuneBuffer) RuneSlice(i int) []rune {
} }
func (r *RuneBuffer) Runes() []rune { func (r *RuneBuffer) Runes() []rune {
r.Lock()
newr := make([]rune, len(r.buf)) newr := make([]rune, len(r.buf))
copy(newr, r.buf) copy(newr, r.buf)
r.Unlock()
return newr return newr
} }
func (r *RuneBuffer) Pos() int { func (r *RuneBuffer) Pos() int {
r.Lock()
defer r.Unlock()
return r.idx return r.idx
} }
func (r *RuneBuffer) Len() int { func (r *RuneBuffer) Len() int {
r.Lock()
defer r.Unlock()
return len(r.buf) return len(r.buf)
} }
@ -154,6 +173,8 @@ func (r *RuneBuffer) MoveForward() {
} }
func (r *RuneBuffer) IsCursorInEnd() bool { func (r *RuneBuffer) IsCursorInEnd() bool {
r.Lock()
defer r.Unlock()
return r.idx == len(r.buf) return r.idx == len(r.buf)
} }
@ -382,6 +403,12 @@ func (r *RuneBuffer) getSplitByLine(rs []rune) []string {
} }
func (r *RuneBuffer) IdxLine(width int) int { func (r *RuneBuffer) IdxLine(width int) int {
r.Lock()
defer r.Unlock()
return r.idxLine(width)
}
func (r *RuneBuffer) idxLine(width int) int {
if width == 0 { if width == 0 {
return 0 return 0
} }
@ -404,7 +431,7 @@ func (r *RuneBuffer) Refresh(f func()) {
return return
} }
r.Clean() r.clean()
if f != nil { if f != nil {
f() f()
} }
@ -527,10 +554,16 @@ func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
} }
func (r *RuneBuffer) Clean() { func (r *RuneBuffer) Clean() {
r.clean(r.IdxLine(r.width)) r.Lock()
r.clean()
r.Unlock()
} }
func (r *RuneBuffer) clean(idxLine int) { func (r *RuneBuffer) clean() {
r.cleanWithIdxLine(r.idxLine(r.width))
}
func (r *RuneBuffer) cleanWithIdxLine(idxLine int) {
if r.hadClean || !r.interactive { if r.hadClean || !r.interactive {
return return
} }