Fix a bunch of race conditions

This commit is contained in:
Daisuke Maki 2014-10-08 20:50:57 +09:00
parent 4952013023
commit 2f1f4cbb63
9 changed files with 199 additions and 85 deletions

View file

@ -151,10 +151,10 @@ func doAcceptChar(i *Input, ev termbox.Event) {
}
if ev.Ch > 0 {
if i.QueryLen() == i.CaretPos().Int() {
if i.QueryLen() == i.CaretPos() {
i.AppendQuery(ev.Ch)
} else {
i.InsertQueryAt(ev.Ch, i.CaretPos().Int())
i.InsertQueryAt(ev.Ch, i.CaretPos())
}
i.MoveCaretPos(1)
i.DrawPrompt() // Update prompt before running query
@ -180,7 +180,7 @@ func doToggleSelection(i *Input, _ termbox.Event) {
func doToggleRangeMode(i *Input, _ termbox.Event) {
if i.IsRangeMode() {
for _, line := range i.SelectedRange() {
for _, line := range i.SelectedRange().GetSelection() {
i.selection.Add(line)
}
i.selection.Add(i.currentLine)
@ -225,9 +225,9 @@ func doFinish(i *Input, _ termbox.Event) {
}
i.result = []Match{}
for _, lineno := range append(i.selection, i.SelectedRange()...) {
if lineno <= len(i.current) {
i.result = append(i.result, i.current[lineno-1])
for _, lineno := range append(i.selection.GetSelection(), i.SelectedRange().GetSelection()...) {
if lineno <= i.GetCurrentLen() {
i.result = append(i.result, i.GetCurrentAt(lineno-1))
}
}
i.ExitWith(0)
@ -286,7 +286,7 @@ func doDeleteBackwardWord(i *Input, _ termbox.Event) {
}
q := i.Query()
start := i.CaretPos().Int()
start := i.CaretPos()
if l := len(q); l <= start {
start = l
}
@ -299,7 +299,7 @@ func doDeleteBackwardWord(i *Input, _ termbox.Event) {
found := false
for pos := start - 1; pos >= 0; pos-- {
if sepFunc(q[pos]) {
buf := make([]rune, q.QueryLen()-(start-pos-1))
buf := make([]rune, len(q)-(start-pos-1))
copy(buf, q[:pos+1])
copy(buf[pos+1:], q[start:])
i.SetQuery(buf)
@ -322,12 +322,12 @@ func doDeleteBackwardWord(i *Input, _ termbox.Event) {
}
func doForwardWord(i *Input, _ termbox.Event) {
if i.CaretPos().Int() >= i.QueryLen() {
if i.CaretPos() >= i.QueryLen() {
return
}
foundSpace := false
for pos := i.CaretPos().Int(); pos < i.QueryLen(); pos++ {
for pos := i.CaretPos(); pos < i.QueryLen(); pos++ {
r := i.Query()[pos]
if foundSpace {
if !unicode.IsSpace(r) {
@ -349,11 +349,11 @@ func doForwardWord(i *Input, _ termbox.Event) {
}
func doBackwardWord(i *Input, _ termbox.Event) {
if i.CaretPos().Int() == 0 {
if i.CaretPos() == 0 {
return
}
if i.CaretPos().Int() >= i.QueryLen() {
if i.CaretPos() >= i.QueryLen() {
i.MoveCaretPos(-1)
}
@ -361,8 +361,8 @@ func doBackwardWord(i *Input, _ termbox.Event) {
// rewind to the end of the previous word, and then do the
// search all over again
SEARCH_PREV_WORD:
if unicode.IsSpace(i.Query()[i.CaretPos().Int()]) {
for pos := i.CaretPos().Int(); pos > 0; pos-- {
if unicode.IsSpace(i.Query()[i.CaretPos()]) {
for pos := i.CaretPos(); pos > 0; pos-- {
if !unicode.IsSpace(i.Query()[pos]) {
i.SetCaretPos(pos)
break
@ -392,7 +392,7 @@ SEARCH_PREV_WORD:
}
func doForwardChar(i *Input, _ termbox.Event) {
if i.CaretPos().Int() >= i.QueryLen() {
if i.CaretPos() >= i.QueryLen() {
return
}
i.MoveCaretPos(1)
@ -408,11 +408,11 @@ func doBackwardChar(i *Input, _ termbox.Event) {
}
func doDeleteForwardWord(i *Input, _ termbox.Event) {
if i.QueryLen() <= i.CaretPos().Int() {
if i.QueryLen() <= i.CaretPos() {
return
}
start := i.CaretPos().Int()
start := i.CaretPos()
// If we are on a word (non-Space, delete till the end of the word.
// If we are on a space, delete till the end of space.
@ -477,7 +477,7 @@ func doKillBeginningOfLine(i *Input, _ termbox.Event) {
}
func doKillEndOfLine(i *Input, _ termbox.Event) {
if i.QueryLen() <= i.CaretPos().Int() {
if i.QueryLen() <= i.CaretPos() {
return
}
@ -496,11 +496,11 @@ func doDeleteAll(i *Input, _ termbox.Event) {
}
func doDeleteForwardChar(i *Input, _ termbox.Event) {
if i.QueryLen() <= i.CaretPos().Int() {
if i.QueryLen() <= i.CaretPos() {
return
}
pos := i.CaretPos().Int()
pos := i.CaretPos()
buf := make([]rune, i.QueryLen()-1)
copy(buf, i.Query()[:i.CaretPos()])
copy(buf[i.CaretPos():], i.Query()[i.CaretPos()+1:])
@ -520,7 +520,7 @@ func doDeleteBackwardChar(i *Input, ev termbox.Event) {
return
}
pos := i.CaretPos().Int()
pos := i.CaretPos()
switch pos {
case 0:
// No op

View file

@ -41,8 +41,8 @@ func TestActionNames(t *testing.T) {
}
func expectCaretPos(t *testing.T, c interface {
CaretPos() CaretPosition
}, expect CaretPosition) bool {
CaretPos() int
}, expect int) bool {
if c.CaretPos() != expect {
t.Errorf("Expected caret position %d, got %d", expect, c.CaretPos())
return false
@ -75,7 +75,7 @@ func TestDoDeleteForwardChar(t *testing.T) {
doDeleteForwardChar(input, termbox.Event{})
expectQueryString(t, ctx, "Hello World!")
expectCaretPos(t, ctx, CaretPosition(runewidth.StringWidth(ctx.QueryString())))
expectCaretPos(t, ctx, runewidth.StringWidth(ctx.QueryString()))
ctx.SetCaretPos(0)
doDeleteForwardChar(input, termbox.Event{})
@ -99,7 +99,7 @@ func TestDoDeleteForwardWord(t *testing.T) {
doDeleteForwardWord(input, termbox.Event{})
expectQueryString(t, ctx, "Hello World!")
expectCaretPos(t, ctx, CaretPosition(runewidth.StringWidth(ctx.QueryString())))
expectCaretPos(t, ctx, runewidth.StringWidth(ctx.QueryString()))
ctx.SetCaretPos(0)
doDeleteForwardWord(input, termbox.Event{})
@ -128,7 +128,7 @@ func TestDoDeleteBackwardChar(t *testing.T) {
doDeleteBackwardChar(input, termbox.Event{})
expectQueryString(t, ctx, "Hell, World")
expectCaretPos(t, ctx, CaretPosition(runewidth.StringWidth(ctx.QueryString())))
expectCaretPos(t, ctx, runewidth.StringWidth(ctx.QueryString()))
ctx.SetCaretPos(0)
doDeleteBackwardChar(input, termbox.Event{})

View file

@ -37,7 +37,7 @@ func main() {
func setupDeps() {
deps := map[string]string{
"github.com/jessevdk/go-flags": "8ec9564882e7923e632f012761c81c46dcf5bec1",
"github.com/mattn/go-runewidth": "36f63b8223e701c16f36010094fb6e84ffbaf8e0",
"github.com/mattn/go-runewidth": "63c378b851290989b19ca955468386485f118c65",
"github.com/nsf/termbox-go": "bb19a81afd4bc2729799d1fedb19f7bd7ee284cf",
}

138
ctx.go
View file

@ -37,66 +37,90 @@ type PageInfo struct {
perPage int
}
type CaretPosition int
func (p CaretPosition) Int() int {
return int(p)
type CaretPosition struct {
pos int
mutex *sync.Mutex
}
func (p CaretPosition) CaretPos() CaretPosition {
return p
func (p CaretPosition) Int() int {
p.mutex.Lock()
defer p.mutex.Unlock()
return int(p.pos)
}
func (p CaretPosition) CaretPos() int {
p.mutex.Lock()
defer p.mutex.Unlock()
return p.pos
}
func (p *CaretPosition) SetCaretPos(where int) {
*p = CaretPosition(where)
p.mutex.Lock()
defer p.mutex.Unlock()
p.pos = where
}
func (p *CaretPosition) MoveCaretPos(offset int) {
*p = CaretPosition(p.Int() + offset)
p.SetCaretPos(p.Int() + offset)
}
type FilterQuery []rune
type FilterQuery struct {
query []rune
mutex *sync.Mutex
}
func (q FilterQuery) Query() FilterQuery {
return q
func (q FilterQuery) Query() []rune {
q.mutex.Lock()
defer q.mutex.Unlock()
return q.query[:]
}
func (q FilterQuery) QueryString() string {
return string(q)
qbytes := q.Query()
return string(qbytes)
}
func (q FilterQuery) QueryLen() int {
return len(q)
q.mutex.Lock()
defer q.mutex.Unlock()
return len(q.query)
}
func (q *FilterQuery) AppendQuery(r rune) {
*q = FilterQuery(append([]rune(*q), r))
q.mutex.Lock()
defer q.mutex.Unlock()
q.query = append(q.query, r)
}
func (q *FilterQuery) InsertQueryAt(ch rune, where int) {
sq := []rune(*q)
buf := make([]rune, q.QueryLen()+1)
q.mutex.Lock()
defer q.mutex.Unlock()
sq := q.query
buf := make([]rune, len(sq)+1)
copy(buf, sq[:where])
buf[where] = ch
copy(buf[where+1:], sq[where:])
*q = FilterQuery(buf)
q.query = buf
}
// Ctx contains all the important data. while you can easily access
// data in this struct from anwyehre, only do so via channels
type Ctx struct {
*Hub
CaretPosition
FilterQuery
*CaretPosition
*FilterQuery
enableSep bool
result []Match
mutex sync.Mutex
mutex *sync.Mutex
currentLine int
currentPage *PageInfo
maxPage int
selection Selection
selection *Selection
lines []Match
linesMutex *sync.Mutex
current []Match
currentMutex *sync.Mutex
bufferSize int
config *Config
Matchers []Matcher
@ -111,15 +135,17 @@ type Ctx struct {
func NewCtx(o CtxOptions) *Ctx {
c := &Ctx{
Hub: NewHub(),
CaretPosition: 0,
FilterQuery: FilterQuery{},
CaretPosition: &CaretPosition{0, &sync.Mutex{}},
FilterQuery: &FilterQuery{[]rune{}, &sync.Mutex{}},
result: []Match{},
mutex: sync.Mutex{},
mutex: &sync.Mutex{},
currentPage: &PageInfo{0, 1, 0},
maxPage: 0,
selection: Selection([]int{}),
selection: NewSelection(),
lines: []Match{},
linesMutex: &sync.Mutex{},
current: nil,
currentMutex: &sync.Mutex{},
config: NewConfig(),
Matchers: nil,
currentMatcher: 0,
@ -173,6 +199,24 @@ func (c *Ctx) ReadConfig(file string) error {
return nil
}
func (c *Ctx) SetLines(newLines []Match) {
c.linesMutex.Lock()
defer c.linesMutex.Unlock()
c.lines = newLines
}
func (c *Ctx) GetLines() []Match {
c.linesMutex.Lock()
defer c.linesMutex.Unlock()
return c.lines[:]
}
func (c *Ctx) GetLinesCount() int {
c.linesMutex.Lock()
defer c.linesMutex.Unlock()
return len(c.lines)
}
func (c *Ctx) IsBufferOverflowing() bool {
if c.bufferSize <= 0 {
return false
@ -185,9 +229,17 @@ func (c *Ctx) IsRangeMode() bool {
return c.selectionRangeStart != invalidSelectionRange
}
func (c *Ctx) SelectedRange() Selection {
func (c *Ctx) SelectionClear() {
c.selection.Clear()
}
func (c *Ctx) SelectionContains(n int) bool {
return c.selection.Has(n)
}
func (c *Ctx) SelectedRange() *Selection {
if !c.IsRangeMode() {
return Selection{}
return NewSelection()
}
selectedLines := []int{}
@ -200,7 +252,33 @@ func (c *Ctx) SelectedRange() Selection {
selectedLines = append(selectedLines, i)
}
}
return Selection(selectedLines)
s := NewSelection()
s.selection = selectedLines
return s
}
func (c *Ctx) GetCurrent() []Match {
c.currentMutex.Lock()
defer c.currentMutex.Unlock()
return c.current[:]
}
func (c *Ctx) GetCurrentLen() int {
c.currentMutex.Lock()
defer c.currentMutex.Unlock()
return len(c.current)
}
func (c *Ctx) SetCurrent(newMatches []Match) {
c.currentMutex.Lock()
defer c.currentMutex.Unlock()
c.current = newMatches
}
func (c *Ctx) GetCurrentAt(i int) Match {
c.currentMutex.Lock()
defer c.currentMutex.Unlock()
return c.current[i]
}
func (c *Ctx) Result() []Match {
@ -273,7 +351,9 @@ func (c *Ctx) NewInput() *Input {
}
func (c *Ctx) SetQuery(q []rune) {
c.FilterQuery = FilterQuery(q)
c.FilterQuery.mutex.Lock()
c.FilterQuery.query = q
c.FilterQuery.mutex.Unlock()
c.SetCaretPos(c.QueryLen())
}

View file

@ -15,9 +15,9 @@ func (f *Filter) Work(cancel chan struct{}, q HubReq) {
f.DrawMatches(nil)
return
}
f.current = f.Matcher().Match(cancel, query, f.Buffer())
f.SetCurrent(f.Matcher().Match(cancel, query, f.Buffer()))
f.SendStatusMsg("")
f.selection.Clear()
f.SelectionClear()
f.DrawMatches(nil)
}

View file

@ -149,11 +149,11 @@ func (u UserPrompt) Draw() {
u.SetCaretPos(0) // sanity
}
if u.CaretPos().Int() > u.QueryLen() { // XXX Do we really need this?
if u.CaretPos() > u.QueryLen() { // XXX Do we really need this?
u.SetCaretPos(u.QueryLen())
}
if u.CaretPos().Int() == u.QueryLen() {
if u.CaretPos() == u.QueryLen() {
// the entire string + the caret after the string
fg := u.config.Style.QueryFG()
bg := u.config.Style.QueryBG()
@ -168,7 +168,7 @@ func (u UserPrompt) Draw() {
for i, r := range []rune(u.Query()) {
fg := u.config.Style.QueryFG()
bg := u.config.Style.QueryBG()
if i == u.CaretPos().Int() {
if i == u.CaretPos() {
fg |= termbox.AttrReverse
bg |= termbox.AttrReverse
}
@ -210,6 +210,12 @@ func (s *StatusBar) stopTimer() {
}
}
func (s *StatusBar) setClearTimer(t *time.Timer) {
s.timerMutex.Lock()
defer s.timerMutex.Unlock()
s.clearTimer = t
}
// PrintStatus prints a new status message. This also resets the
// timer created by ClearStatus()
func (s *StatusBar) PrintStatus(msg string, clearDelay time.Duration) {
@ -252,9 +258,9 @@ func (s *StatusBar) PrintStatus(msg string, clearDelay time.Duration) {
// if everything is successful AND the clearDelay timer is specified,
// then set a timer to clear the status
if clearDelay != 0 {
s.clearTimer = time.AfterFunc(clearDelay, func() {
s.setClearTimer(time.AfterFunc(clearDelay, func() {
s.PrintStatus("", 0)
})
}))
}
}
@ -288,7 +294,7 @@ func (l *ListArea) Draw(targets []Match, perPage int) {
case n+currentPage.offset == l.currentLine-1:
fgAttr = l.config.Style.SelectedFG()
bgAttr = l.config.Style.SelectedBG()
case l.selection.Has(n+currentPage.offset+1) || l.SelectedRange().Has(n+currentPage.offset+1):
case l.SelectionContains(n+currentPage.offset+1) || l.SelectedRange().Has(n+currentPage.offset+1):
fgAttr = l.config.Style.SavedSelectionFG()
bgAttr = l.config.Style.SavedSelectionBG()
default:

View file

@ -67,9 +67,11 @@ func (b *BufferReader) Loop() {
// Make sure we lock access to b.lines
m.Lock()
b.lines = append(b.lines, NewNoMatch(line, b.enableSep))
b.SetLines(append(b.GetLines(), NewNoMatch(line, b.enableSep)))
if b.IsBufferOverflowing() {
b.lines = b.lines[1:]
lines := b.GetLines()
b.SetLines(lines[1:])
}
m.Unlock()
}
@ -78,7 +80,7 @@ func (b *BufferReader) Loop() {
if refresh == nil {
refresh = time.AfterFunc(100*time.Millisecond, func() {
if !b.ExecQuery() {
b.DrawMatches(b.lines)
b.DrawMatches(b.GetLines())
}
m.Lock()
refresh = nil
@ -93,7 +95,7 @@ func (b *BufferReader) Loop() {
// Out of the reader loop. If at this point we have no buffer,
// that means we have no buffer, so we should quit.
if len(b.lines) == 0 {
if b.GetLinesCount() == 0 {
b.ExitWith(1)
fmt.Fprintf(os.Stderr, "No buffer to work with was available")
}

View file

@ -1,15 +1,34 @@
package peco
import "sort"
import (
"sort"
"sync"
)
// Selection stores the line numbers that were selected by the user.
// The contents of the Selection is always sorted from smallest to
// largest line number
type Selection []int
type Selection struct {
selection []int
mutex *sync.Mutex
}
func NewSelection() *Selection {
return &Selection{nil,&sync.Mutex{}}
}
func (s *Selection) GetSelection() []int {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.selection[:]
}
// Has returns true if line `v` is in the selection
func (s Selection) Has(v int) bool {
for _, i := range []int(s) {
s.mutex.Lock()
defer s.mutex.Unlock()
for _, i := range s.selection {
if i == v {
return true
}
@ -23,18 +42,23 @@ func (s *Selection) Add(v int) {
if s.Has(v) {
return
}
*s = Selection(append([]int(*s), v))
s.mutex.Lock()
defer s.mutex.Unlock()
s.selection = append(s.selection, v)
sort.Sort(s)
}
// Remove removes the specified line number from the selection
func (s *Selection) Remove(v int) {
a := []int(*s)
for k, i := range a {
s.mutex.Lock()
defer s.mutex.Unlock()
for k, i := range s.selection {
if i == v {
tmp := a[:k]
tmp = append(tmp, a[k+1:]...)
*s = Selection(tmp)
tmp := s.selection[:k]
tmp = append(tmp, s.selection[k+1:]...)
s.selection = tmp
return
}
}
@ -42,22 +66,25 @@ func (s *Selection) Remove(v int) {
// Clear empties the selection
func (s *Selection) Clear() {
*s = Selection([]int{})
s.mutex.Lock()
defer s.mutex.Unlock()
s.selection = []int{}
}
// Len returns the number of elements in the selection. Satisfies
// sort.Interface
func (s Selection) Len() int {
return len(s)
return len(s.selection)
}
// Swap swaps the elements in indices i and j. Satisfies sort.Interface
func (s Selection) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
func (s *Selection) Swap(i, j int) {
s.selection[i], s.selection[j] = s.selection[j], s.selection[i]
}
// Less returns true if element at index i is less than the element at
// index j. Satisfies sort.Interface
func (s Selection) Less(i, j int) bool {
return s[i] < s[j]
return s.selection[i] < s.selection[j]
}

View file

@ -68,13 +68,12 @@ func (v *View) drawScreenNoLock(targets []Match) {
if current := v.current; current != nil {
targets = v.current
} else {
targets = v.lines
targets = v.GetLines()
}
}
v.layout.DrawScreen(targets)
// FIXME
v.current = targets
v.SetCurrent(targets)
}
func (v *View) drawScreen(targets []Match) {