From 114ac22dea57299901632ba5409c12e13a5b3e95 Mon Sep 17 00:00:00 2001 From: Nathan Wallace Date: Sun, 16 Nov 2025 13:59:43 -0500 Subject: [PATCH] Fix #4716: Add synchronization to autoCompleteSuggestions.sort() (#4737) * Add test for #4716: sort() should be safe for concurrent calls * Fix #4716: Add mutex protection to autoCompleteSuggestions.sort() Adds sync.RWMutex to prevent data race during concurrent sort() calls. Changes sort() from value receiver to pointer receiver to support locking. The mutex ensures thread-safe access when multiple goroutines call sort() simultaneously during autocomplete initialization. --- pkg/interactive/autocomplete_suggestions.go | 10 ++- .../autocomplete_suggestions_test.go | 64 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 pkg/interactive/autocomplete_suggestions_test.go diff --git a/pkg/interactive/autocomplete_suggestions.go b/pkg/interactive/autocomplete_suggestions.go index cf467eb6e..2aafb6b02 100644 --- a/pkg/interactive/autocomplete_suggestions.go +++ b/pkg/interactive/autocomplete_suggestions.go @@ -1,8 +1,10 @@ package interactive import ( - "github.com/c-bata/go-prompt" "sort" + "sync" + + "github.com/c-bata/go-prompt" ) const ( @@ -15,6 +17,7 @@ const ( ) type autoCompleteSuggestions struct { + mu sync.RWMutex schemas []prompt.Suggest unqualifiedTables []prompt.Suggest unqualifiedQueries []prompt.Suggest @@ -72,7 +75,10 @@ func (s *autoCompleteSuggestions) setQueriesForMod(modName string, queries []pro s.queriesByMod[modName] = queries } -func (s autoCompleteSuggestions) sort() { +func (s *autoCompleteSuggestions) sort() { + s.mu.Lock() + defer s.mu.Unlock() + sortSuggestions := func(s []prompt.Suggest) { sort.Slice(s, func(i, j int) bool { return s[i].Text < s[j].Text diff --git a/pkg/interactive/autocomplete_suggestions_test.go b/pkg/interactive/autocomplete_suggestions_test.go new file mode 100644 index 000000000..874188a73 --- /dev/null +++ b/pkg/interactive/autocomplete_suggestions_test.go @@ -0,0 +1,64 @@ +package interactive + +import ( + "sync" + "testing" + + "github.com/c-bata/go-prompt" +) + +// TestAutoCompleteSuggestions_ConcurrentSort tests that sort() can be called +// concurrently without triggering data races. +// This test reproduces the race condition reported in issue #4716. +func TestAutoCompleteSuggestions_ConcurrentSort(t *testing.T) { + // Create a populated autoCompleteSuggestions instance + suggestions := newAutocompleteSuggestions() + + // Populate with test data + suggestions.schemas = []prompt.Suggest{ + {Text: "public"}, + {Text: "aws"}, + {Text: "github"}, + } + + suggestions.unqualifiedTables = []prompt.Suggest{ + {Text: "table1"}, + {Text: "table2"}, + {Text: "table3"}, + } + + suggestions.unqualifiedQueries = []prompt.Suggest{ + {Text: "query1"}, + {Text: "query2"}, + {Text: "query3"}, + } + + suggestions.tablesBySchema["public"] = []prompt.Suggest{ + {Text: "users"}, + {Text: "accounts"}, + } + + suggestions.queriesByMod["aws"] = []prompt.Suggest{ + {Text: "aws_query1"}, + {Text: "aws_query2"}, + } + + // Call sort() concurrently from multiple goroutines + // This should trigger a race condition if the sort() method is not thread-safe + var wg sync.WaitGroup + numGoroutines := 10 + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + suggestions.sort() + }() + } + + // Wait for all goroutines to complete + wg.Wait() + + // If we get here without panicking or race detector errors, the test passes + // Note: This test will fail when run with -race flag if sort() is not thread-safe +}