mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-30 00:33:56 +00:00
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:
parent
1db68a3976
commit
9dbf6b02d2
@ -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
|
||||||
})
|
})
|
||||||
|
22
src/item.go
22
src/item.go
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user