Merge pull request #643 from peco/fix-input-alt-key-race

Handle esc in the input loop as well
This commit is contained in:
lestrrat 2026-02-17 11:10:29 +09:00 committed by GitHub
commit 3ba1cbdb01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 11 deletions

View file

@ -11,9 +11,10 @@ import (
func NewInput(state *Peco, am ActionMap, src chan Event) *Input {
return &Input{
actions: am,
evsrc: src,
state: state,
actions: am,
evsrc: src,
pendingEsc: make(chan Event, 1),
state: state,
}
}
@ -24,6 +25,11 @@ func (i *Input) Loop(ctx context.Context, cancel func()) error {
select {
case <-ctx.Done():
return nil
case ev := <-i.pendingEsc:
// Timer fired and determined this was a standalone Esc press.
// Execute the action here on the input loop goroutine, not
// on the timer goroutine, to avoid concurrent ExecuteAction calls.
i.state.Keymap().ExecuteAction(ctx, i.state, ev)
case ev, ok := <-i.evsrc:
if !ok {
return nil
@ -73,7 +79,13 @@ func (i *Input) handleInputEvent(ctx context.Context, ev Event) error {
}
i.mod = nil
m.Unlock()
i.state.Keymap().ExecuteAction(ctx, i.state, tmp)
// Send to the input loop instead of calling ExecuteAction
// directly, so all action execution is serialized on the
// input loop goroutine.
select {
case i.pendingEsc <- tmp:
case <-ctx.Done():
}
})
m.Unlock()
return nil

View file

@ -85,9 +85,23 @@ func TestInputModifierKeyRace(t *testing.T) {
state.config.Action = map[string][]string{}
require.NoError(t, state.populateKeymap())
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
input := NewInput(state, state.Keymap(), make(chan Event))
// Start a goroutine that drains pendingEsc and executes actions,
// simulating what Loop does.
go func() {
for {
select {
case <-ctx.Done():
return
case ev := <-input.pendingEsc:
input.state.Keymap().ExecuteAction(ctx, input.state, ev)
}
}
}()
// Send Esc event — starts the 50ms timer
escEv := Event{Type: EventKey, Key: keyseq.KeyEsc, Ch: 0}
input.handleInputEvent(ctx, escEv)

View file

@ -504,14 +504,15 @@ type ActionMap interface {
}
type Input struct {
actions ActionMap
evsrc chan Event
mod *time.Timer
modGen uint64 // generation counter to invalidate stale timer callbacks.
actions ActionMap
evsrc chan Event
pendingEsc chan Event // receives Esc events from the timer callback
mod *time.Timer
modGen uint64 // generation counter to invalidate stale timer callbacks.
// uint64 holds up to ~1.8×10¹⁹. At most 2 increments per Esc key event
// and a generous 100 keystrokes/second, overflow would take ~2.9 trillion years.
mutex sync.Mutex
state *Peco
mutex sync.Mutex
state *Peco
}
// HubSender provides methods for sending messages to the hub.