Fix race conditions

- Wait for completions of goroutines when cancelling a search
- Remove shared access to rank field of Item
This commit is contained in:
Junegunn Choi 2015-01-11 23:49:12 +09:00
parent 1db68a3976
commit 9dbf6b02d2
7 changed files with 49 additions and 23 deletions

View File

@ -64,7 +64,10 @@ func Run(options *Options) {
var chunkList *ChunkList
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(data *string, index int) *Item {
return &Item{text: data, rank: Rank{0, 0, uint32(index)}}
return &Item{
text: data,
index: uint32(index),
rank: Rank{0, 0, uint32(index)}}
})
} else {
chunkList = NewChunkList(func(data *string, index int) *Item {
@ -72,6 +75,7 @@ func Run(options *Options) {
item := Item{
text: Transform(tokens, opts.WithNth).whole,
origText: data,
index: uint32(index),
rank: Rank{0, 0, uint32(index)}}
return &item
})

View File

@ -1,9 +1,6 @@
package fzf
import (
"fmt"
"sort"
)
import "fmt"
type Offset [2]int32
@ -11,6 +8,7 @@ type Item struct {
text *string
origText *string
transformed *Transformed
index uint32
offsets []Offset
rank Rank
}
@ -21,11 +19,10 @@ type Rank struct {
index uint32
}
func (i *Item) Rank() Rank {
if i.rank.matchlen > 0 || i.rank.strlen > 0 {
func (i *Item) Rank(cache bool) Rank {
if cache && (i.rank.matchlen > 0 || i.rank.strlen > 0) {
return i.rank
}
sort.Sort(ByOrder(i.offsets))
matchlen := 0
prevEnd := 0
for _, offset := range i.offsets {
@ -41,8 +38,11 @@ func (i *Item) Rank() Rank {
matchlen += end - begin
}
}
i.rank = Rank{uint16(matchlen), uint16(len(*i.text)), i.rank.index}
return i.rank
rank := Rank{uint16(matchlen), uint16(len(*i.text)), i.index}
if cache {
i.rank = rank
}
return rank
}
func (i *Item) Print() {
@ -80,8 +80,8 @@ func (a ByRelevance) Swap(i, j int) {
}
func (a ByRelevance) Less(i, j int) bool {
irank := a[i].Rank()
jrank := a[j].Rank()
irank := a[i].Rank(true)
jrank := a[j].Rank(true)
return compareRanks(irank, jrank)
}

View File

@ -31,13 +31,13 @@ func TestRankComparison(t *testing.T) {
// Match length, string length, index
func TestItemRank(t *testing.T) {
strs := []string{"foo", "foobar", "bar", "baz"}
item1 := Item{text: &strs[0], rank: Rank{0, 0, 1}, offsets: []Offset{}}
rank1 := item1.Rank()
item1 := Item{text: &strs[0], index: 1, offsets: []Offset{}}
rank1 := item1.Rank(true)
if rank1.matchlen != 0 || rank1.strlen != 3 || rank1.index != 1 {
t.Error(item1.Rank())
t.Error(item1.Rank(true))
}
// Only differ in index
item2 := Item{text: &strs[0], rank: Rank{0, 0, 0}, offsets: []Offset{}}
item2 := Item{text: &strs[0], index: 0, offsets: []Offset{}}
items := []*Item{&item1, &item2}
sort.Sort(ByRelevance(items))

View File

@ -4,6 +4,7 @@ import (
"fmt"
"runtime"
"sort"
"sync"
"time"
)
@ -134,10 +135,13 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
slices := m.sliceChunks(request.chunks)
numSlices := len(slices)
resultChan := make(chan partialResult, numSlices)
countChan := make(chan int, numSlices)
countChan := make(chan int, numChunks)
waitGroup := sync.WaitGroup{}
for idx, chunks := range slices {
waitGroup.Add(1)
go func(idx int, chunks []*Chunk) {
defer func() { waitGroup.Done() }()
sliceMatches := []*Item{}
for _, chunk := range chunks {
var matches []*Item
@ -159,6 +163,12 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
}(idx, chunks)
}
wait := func() bool {
cancelled.Set(true)
waitGroup.Wait()
return true
}
count := 0
matchCount := 0
for matchesInChunk := range countChan {
@ -166,7 +176,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
matchCount += matchesInChunk
if limit > 0 && matchCount > limit {
return nil, true // For --select-1 and --exit-0
return nil, wait() // For --select-1 and --exit-0
}
if count == numChunks {
@ -174,8 +184,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
}
if !empty && m.reqBox.Peak(REQ_RESET) {
cancelled.Set(true)
return nil, true
return nil, wait()
}
if time.Now().Sub(startedAt) > PROGRESS_MIN_DURATION {

View File

@ -57,7 +57,7 @@ func (mg *Merger) mergedGet(idx int) *Item {
continue
}
if cursor >= 0 {
rank := list[cursor].Rank()
rank := list[cursor].Rank(false)
if minIdx < 0 || compareRanks(rank, minRank) {
minRank = rank
minIdx = listIdx

View File

@ -1,6 +1,7 @@
package fzf
import (
"fmt"
"math/rand"
"sort"
"testing"
@ -13,8 +14,17 @@ func assert(t *testing.T, cond bool, msg ...string) {
}
func randItem() *Item {
str := fmt.Sprintf("%d", rand.Uint32())
offsets := make([]Offset, rand.Int()%3)
for idx := range offsets {
sidx := int32(rand.Uint32() % 20)
eidx := sidx + int32(rand.Uint32()%20)
offsets[idx] = Offset{sidx, eidx}
}
return &Item{
rank: Rank{uint16(rand.Uint32()), uint16(rand.Uint32()), rand.Uint32()}}
text: &str,
index: rand.Uint32(),
offsets: offsets}
}
func TestEmptyMerger(t *testing.T) {

View File

@ -2,6 +2,7 @@ package fzf
import (
"regexp"
"sort"
"strings"
)
@ -225,12 +226,14 @@ Loop:
}
func dupItem(item *Item, offsets []Offset) *Item {
sort.Sort(ByOrder(offsets))
return &Item{
text: item.text,
origText: item.origText,
transformed: item.transformed,
index: item.index,
offsets: offsets,
rank: Rank{0, 0, item.rank.index}}
rank: Rank{0, 0, item.index}}
}
func (p *Pattern) fuzzyMatch(chunk *Chunk) []*Item {