mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-19 18:12:43 -05:00
Add mutex synchronization to the autoCompleteSuggestions struct to prevent concurrent modification of slice data during sorting operations. The sort() method now acquires a lock before performing any sorting operations, ensuring that only one goroutine can sort the suggestions at a time. This prevents the data races detected when sort() was called concurrently. Changes: - Add sync.Mutex field to autoCompleteSuggestions struct - Change sort() receiver from value to pointer to support mutex - Add Lock/Unlock calls around sorting operations The test TestAutocompleteSuggestions_ConcurrentSort now passes with the -race flag, and all existing tests continue to pass. Fixes #4711 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package interactive
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/c-bata/go-prompt"
|
|
)
|
|
|
|
const (
|
|
// Maximum number of schemas/connections to store in suggestion maps
|
|
maxSchemasInSuggestions = 100
|
|
// Maximum number of tables per schema in suggestions
|
|
maxTablesPerSchema = 500
|
|
// Maximum number of queries per mod in suggestions
|
|
maxQueriesPerMod = 500
|
|
)
|
|
|
|
type autoCompleteSuggestions struct {
|
|
mu sync.Mutex
|
|
schemas []prompt.Suggest
|
|
unqualifiedTables []prompt.Suggest
|
|
unqualifiedQueries []prompt.Suggest
|
|
tablesBySchema map[string][]prompt.Suggest
|
|
queriesByMod map[string][]prompt.Suggest
|
|
mods []prompt.Suggest
|
|
}
|
|
|
|
func newAutocompleteSuggestions() *autoCompleteSuggestions {
|
|
return &autoCompleteSuggestions{
|
|
tablesBySchema: make(map[string][]prompt.Suggest),
|
|
queriesByMod: make(map[string][]prompt.Suggest),
|
|
}
|
|
}
|
|
|
|
// setTablesForSchema adds tables for a schema with size limits to prevent unbounded growth.
|
|
// If the schema count exceeds maxSchemasInSuggestions, the oldest schema is removed.
|
|
// If the table count exceeds maxTablesPerSchema, only the first maxTablesPerSchema are kept.
|
|
func (s *autoCompleteSuggestions) setTablesForSchema(schemaName string, tables []prompt.Suggest) {
|
|
// Enforce per-schema table limit
|
|
if len(tables) > maxTablesPerSchema {
|
|
tables = tables[:maxTablesPerSchema]
|
|
}
|
|
|
|
// Enforce global schema limit
|
|
if len(s.tablesBySchema) >= maxSchemasInSuggestions {
|
|
// Remove one schema to make room (simple eviction - remove first key found)
|
|
for k := range s.tablesBySchema {
|
|
delete(s.tablesBySchema, k)
|
|
break
|
|
}
|
|
}
|
|
|
|
s.tablesBySchema[schemaName] = tables
|
|
}
|
|
|
|
// setQueriesForMod adds queries for a mod with size limits to prevent unbounded growth.
|
|
// If the mod count exceeds maxSchemasInSuggestions, the oldest mod is removed.
|
|
// If the query count exceeds maxQueriesPerMod, only the first maxQueriesPerMod are kept.
|
|
func (s *autoCompleteSuggestions) setQueriesForMod(modName string, queries []prompt.Suggest) {
|
|
// Enforce per-mod query limit
|
|
if len(queries) > maxQueriesPerMod {
|
|
queries = queries[:maxQueriesPerMod]
|
|
}
|
|
|
|
// Enforce global mod limit
|
|
if len(s.queriesByMod) >= maxSchemasInSuggestions {
|
|
// Remove one mod to make room (simple eviction - remove first key found)
|
|
for k := range s.queriesByMod {
|
|
delete(s.queriesByMod, k)
|
|
break
|
|
}
|
|
}
|
|
|
|
s.queriesByMod[modName] = queries
|
|
}
|
|
|
|
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
|
|
})
|
|
}
|
|
|
|
sortSuggestions(s.schemas)
|
|
sortSuggestions(s.unqualifiedTables)
|
|
sortSuggestions(s.unqualifiedQueries)
|
|
for _, tables := range s.tablesBySchema {
|
|
sortSuggestions(tables)
|
|
}
|
|
for _, queries := range s.queriesByMod {
|
|
sortSuggestions(queries)
|
|
}
|
|
}
|