Fix race condition

This commit is contained in:
David Dworken 2024-10-05 14:12:35 -07:00
parent 82f470f892
commit 49ccce74e1
No known key found for this signature in database
2 changed files with 54 additions and 38 deletions

View File

@ -1,29 +1,27 @@
Warning: failed to search: search query contains unknown search atom 'foo' that doesn't match any column names Search Query: > ls
Search Query: > foo:ls
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 ls ~/ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
hiSHtory: Search your shell history • ctrl+h help hiSHtory: Search your shell history • ctrl+h help

View File

@ -101,6 +101,9 @@ type model struct {
// The currently executing shell. Defaults to bash if not specified. Used for more precise AI suggestions. // The currently executing shell. Defaults to bash if not specified. Used for more precise AI suggestions.
shellName string shellName string
// Whether we've finished the first load of results. If we haven't, we refuse to run additional queries to avoid race conditions with how we handle invalid initial queries.
hasFinishedFirstLoad bool
} }
type ( type (
@ -125,6 +128,8 @@ type asyncQueryFinishedMsg struct {
maintainCursor bool maintainCursor bool
// An updated search query. May be used for initial queries when they're invalid. // An updated search query. May be used for initial queries when they're invalid.
overriddenSearchQuery *string overriddenSearchQuery *string
isFirstQuery bool
} }
func initialModel(ctx context.Context, shellName, initialQuery string) model { func initialModel(ctx context.Context, shellName, initialQuery string) model {
@ -154,7 +159,7 @@ func initialModel(ctx context.Context, shellName, initialQuery string) model {
queryInput.SetValue(initialQuery) queryInput.SetValue(initialQuery)
} }
CURRENT_QUERY_FOR_HIGHLIGHTING = initialQuery CURRENT_QUERY_FOR_HIGHLIGHTING = initialQuery
return model{ctx: ctx, spinner: s, isLoading: true, table: nil, tableEntries: []*data.HistoryEntry{}, runQuery: &initialQuery, queryInput: queryInput, help: help.New(), shellName: shellName} return model{ctx: ctx, spinner: s, isLoading: true, table: nil, tableEntries: []*data.HistoryEntry{}, runQuery: &initialQuery, queryInput: queryInput, help: help.New(), shellName: shellName, hasFinishedFirstLoad: false}
} }
func (m model) Init() tea.Cmd { func (m model) Init() tea.Cmd {
@ -205,13 +210,14 @@ func preventTableOverscrolling(m model) {
func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.Cmd { func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.Cmd {
if (m.runQuery != nil && *m.runQuery != m.lastQuery) || forceUpdateTable || m.searchErr != nil { if (m.runQuery != nil && *m.runQuery != m.lastQuery) || forceUpdateTable || m.searchErr != nil {
// if !m.hasFinishedFirstLoad {
// return nil
// }
query := m.lastQuery query := m.lastQuery
if m.runQuery != nil { if m.runQuery != nil {
query = *m.runQuery query = *m.runQuery
} }
LAST_DISPATCHED_QUERY_ID++ queryId := allocateQueryId()
queryId := LAST_DISPATCHED_QUERY_ID
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
return func() tea.Msg { return func() tea.Msg {
conf := hctx.GetConf(m.ctx) conf := hctx.GetConf(m.ctx)
defaultFilter := conf.DefaultFilter defaultFilter := conf.DefaultFilter
@ -220,7 +226,7 @@ func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.
defaultFilter = "" defaultFilter = ""
} }
rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, m.shellName, defaultFilter, query, PADDED_NUM_ENTRIES) rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, m.shellName, defaultFilter, query, PADDED_NUM_ENTRIES)
return asyncQueryFinishedMsg{queryId, rows, entries, searchErr, forceUpdateTable, maintainCursor, nil} return asyncQueryFinishedMsg{queryId, rows, entries, searchErr, forceUpdateTable, maintainCursor, nil, false}
} }
} }
return nil return nil
@ -337,6 +343,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.queryInput.SetValue(*msg.overriddenSearchQuery) m.queryInput.SetValue(*msg.overriddenSearchQuery)
} }
} }
if msg.isFirstQuery {
m.hasFinishedFirstLoad = true
}
return m, nil return m, nil
default: default:
var cmd tea.Cmd var cmd tea.Cmd
@ -895,6 +904,12 @@ func splitQueryArray(initialQueryArray []string) []string {
return splitQueryArray return splitQueryArray
} }
func allocateQueryId() int {
LAST_DISPATCHED_QUERY_ID++
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
return LAST_DISPATCHED_QUERY_ID
}
func TuiQuery(ctx context.Context, shellName string, initialQueryArray []string) error { func TuiQuery(ctx context.Context, shellName string, initialQueryArray []string) error {
initialQueryArray = splitQueryArray(initialQueryArray) initialQueryArray = splitQueryArray(initialQueryArray)
initialQueryWithEscaping, err := buildInitialQueryWithSearchEscaping(initialQueryArray) initialQueryWithEscaping, err := buildInitialQueryWithSearchEscaping(initialQueryArray)
@ -906,18 +921,22 @@ func TuiQuery(ctx context.Context, shellName string, initialQueryArray []string)
p := tea.NewProgram(initialModel(ctx, shellName, initialQueryWithEscaping), tea.WithOutput(os.Stderr)) p := tea.NewProgram(initialModel(ctx, shellName, initialQueryWithEscaping), tea.WithOutput(os.Stderr))
// Async: Get the initial set of rows // Async: Get the initial set of rows
go func() { go func() {
LAST_DISPATCHED_QUERY_ID++ queryId := allocateQueryId()
queryId := LAST_DISPATCHED_QUERY_ID
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
conf := hctx.GetConf(ctx) conf := hctx.GetConf(ctx)
rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQueryWithEscaping, PADDED_NUM_ENTRIES) rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQueryWithEscaping, PADDED_NUM_ENTRIES)
if err == nil || initialQueryWithEscaping == "" { if err == nil || initialQueryWithEscaping == "" {
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: nil}) if err != nil {
panic(err)
}
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: nil, isFirstQuery: true})
} else { } else {
// The initial query is likely invalid in some way, let's just drop it // The initial query is likely invalid in some way, let's just drop it
emptyQuery := "" emptyQuery := ""
rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, PADDED_NUM_ENTRIES) rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, PADDED_NUM_ENTRIES)
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery}) if err != nil {
panic(err)
}
p.Send(asyncQueryFinishedMsg{queryId: allocateQueryId(), rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery, isFirstQuery: true})
} }
}() }()
// Async: Retrieve additional entries from the backend // Async: Retrieve additional entries from the backend
@ -962,4 +981,3 @@ func TuiQuery(ctx context.Context, shellName string, initialQueryArray []string)
// TODO: support custom key bindings // TODO: support custom key bindings
// TODO: make the help page wrap // TODO: make the help page wrap
// TODO: If the initial query contains dashes, maybe we should smartly escape them?