Capture suggestions inputs on the UI thread

RefreshSuggestions dispatched to an AsyncHandler worker that read
State.FindSuggestions and the prompt's TextArea (via GetPromptInput)
from the worker goroutine. The main thread rewrites both in
preparePromptPanel when it (re)creates a prompt panel, so an in-flight
suggestions worker races those writes -- two data races surfaced under
-race (filter_by_path/reword_commit_in_filtering_mode).

Capture both on the UI thread (RefreshSuggestions is only ever called
from UI-thread handlers) before dispatching to the worker. This is also
more correct: we search for the input as it was when dispatched, which
is what this request's AsyncHandler id corresponds to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-10 21:40:51 +02:00
parent 1b0cc02e1e
commit d36ce51559
2 changed files with 14 additions and 4 deletions

View file

@ -81,10 +81,17 @@ func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion)
}
func (self *SuggestionsContext) RefreshSuggestions() {
// Capture the suggestions function and the prompt input here, on the UI
// thread, rather than inside the worker below: the main thread rewrites both
// (State.FindSuggestions and the prompt's TextArea) when it (re)creates a
// prompt panel, so reading them from the worker races those writes. It's
// also more correct -- we search for the input as it was when dispatched,
// which is what this request's AsyncHandler id corresponds to.
findSuggestionsFn := self.State.FindSuggestions
promptInput := self.c.GetPromptInput()
self.State.AsyncHandler.Do(func() func() {
findSuggestionsFn := self.State.FindSuggestions
if findSuggestionsFn != nil {
suggestions := findSuggestionsFn(self.c.GetPromptInput())
suggestions := findSuggestionsFn(promptInput)
return func() { self.SetSuggestions(suggestions) }
}
return func() {}

View file

@ -35,10 +35,13 @@ func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key) bool {
v.RenderTextArea()
suggestionsContext := gui.State.Contexts.Suggestions
if suggestionsContext.State.FindSuggestions != nil {
// Capture the suggestions function and the input here, on the UI thread; the
// main thread rewrites State.FindSuggestions when it (re)creates a prompt
// panel, so reading it from the worker below would race that write.
if findSuggestions := suggestionsContext.State.FindSuggestions; findSuggestions != nil {
input := v.TextArea.GetContent()
suggestionsContext.State.AsyncHandler.Do(func() func() {
suggestions := suggestionsContext.State.FindSuggestions(input)
suggestions := findSuggestions(input)
return func() { suggestionsContext.SetSuggestions(suggestions) }
})
}