Fix data race
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
build / build (push) Has been cancelled
Test fzf on macOS / build (push) Has been cancelled

This commit is contained in:
Junegunn Choi
2026-02-25 00:41:08 +09:00
parent e9a5296a11
commit 9ba485290b
3 changed files with 39 additions and 2 deletions

View File

@@ -460,6 +460,11 @@ func Run(opts *Options) (int, error) {
}
if val.withNth != nil {
newTransformer := val.withNth.fn
// Cancel any in-flight scan and block the terminal from reading
// items before mutating them in-place. Snapshot shares middle
// chunk pointers, so the matcher and terminal can race with us.
matcher.CancelScan()
terminal.PauseRendering()
// Reset cross-line ANSI state before re-processing all items
lineAnsiState = nil
prevLineAnsiState = nil
@@ -476,6 +481,8 @@ func Run(opts *Options) (int, error) {
}, func() {
nthTransformer = newTransformer
})
terminal.ResumeRendering()
matcher.ResumeScan()
withNthChanged = true
bump = true
}

View File

@@ -45,6 +45,8 @@ type Matcher struct {
slab []*util.Slab
mergerCache map[string]MatchResult
revision revision
scanMutex sync.Mutex
cancelScan *util.AtomicBool
}
const (
@@ -66,7 +68,8 @@ func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
partitions: partitions,
slab: make([]*util.Slab, partitions),
mergerCache: make(map[string]MatchResult),
revision: revision}
revision: revision,
cancelScan: util.NewAtomicBool(false)}
}
// Loop puts Matcher in action
@@ -126,7 +129,9 @@ func (m *Matcher) Loop() {
}
if result.merger == nil {
m.scanMutex.Lock()
result = m.scan(request)
m.scanMutex.Unlock()
}
if !result.cancelled {
@@ -238,7 +243,7 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
break
}
if m.reqBox.Peek(reqReset) {
if m.cancelScan.Get() || m.reqBox.Peek(reqReset) {
return MatchResult{nil, nil, wait()}
}
@@ -269,6 +274,20 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})
}
// CancelScan cancels any in-flight scan, waits for it to finish,
// and prevents new scans from starting until ResumeScan is called.
// This is used to safely mutate shared items (e.g., during with-nth changes).
func (m *Matcher) CancelScan() {
m.cancelScan.Set(true)
m.scanMutex.Lock()
m.cancelScan.Set(false)
}
// ResumeScan allows scans to proceed again after CancelScan.
func (m *Matcher) ResumeScan() {
m.scanMutex.Unlock()
}
func (m *Matcher) Stop() {
m.reqBox.Set(reqQuit, nil)
}

View File

@@ -1738,6 +1738,17 @@ func (t *Terminal) Input() (bool, []rune) {
return paused, copySlice(src)
}
// PauseRendering blocks the terminal from reading items.
// Must be paired with ResumeRendering.
func (t *Terminal) PauseRendering() {
t.mutex.Lock()
}
// ResumeRendering releases the lock acquired by PauseRendering.
func (t *Terminal) ResumeRendering() {
t.mutex.Unlock()
}
// UpdateCount updates the count information
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
t.mutex.Lock()