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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package fzf package fzf
import ( import (
"fmt"
"math/rand" "math/rand"
"sort" "sort"
"testing" "testing"
@ -13,8 +14,17 @@ func assert(t *testing.T, cond bool, msg ...string) {
} }
func randItem() *Item { 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{ return &Item{
rank: Rank{uint16(rand.Uint32()), uint16(rand.Uint32()), rand.Uint32()}} text: &str,
index: rand.Uint32(),
offsets: offsets}
} }
func TestEmptyMerger(t *testing.T) { func TestEmptyMerger(t *testing.T) {

View File

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