Fix spurious exit while waiting for a key with --listen

- getch() charged EINTR against a 100 iteration limit meant for read
  attempts, and the count only reset once a byte was read
- So signals arriving during an idle prompt drained it, and running out
  returned getCharError, which became Event{Fatal} and exit status 2
- Only the cancellable path is affected, hence only --listen
- select() is not restarted by SA_RESTART, unlike the blocking read on
  the non-cancellable path, so it is the only one that sees EINTR
- waitReadable() already retries EINTR without a limit

Fix #4917
This commit is contained in:
Junegunn Choi
2026-09-10 17:56:15 +09:00
parent 63e82a9e3d
commit 1578b7c3c3
2 changed files with 3 additions and 3 deletions
-1
View File
@@ -27,7 +27,6 @@ const (
queryTimeout = 500 * time.Millisecond queryTimeout = 500 * time.Millisecond
maxInputBuffer = 1024 * 1024 maxInputBuffer = 1024 * 1024
escapeLookback = 256 escapeLookback = 256
maxSelectTries = 100
) )
const DefaultTtyDevice string = "/dev/tty" const DefaultTtyDevice string = "/dev/tty"
+3 -2
View File
@@ -240,7 +240,7 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
}() }()
cancelFd := int(rpipe.Fd()) cancelFd := int(rpipe.Fd())
for range maxSelectTries { for {
var rfds unix.FdSet var rfds unix.FdSet
limit := len(rfds.Bits) * unix.NFDBITS limit := len(rfds.Bits) * unix.NFDBITS
if fd >= limit || cancelFd >= limit { if fd >= limit || cancelFd >= limit {
@@ -251,6 +251,8 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
rfds.Set(cancelFd) rfds.Set(cancelFd)
_, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil) _, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil)
if err != nil { if err != nil {
// An interrupted wait is not a failed read, so it must not count
// against anything. Retry until the fd is ready or the wait fails.
if err == syscall.EINTR { if err == syscall.EINTR {
continue continue
} }
@@ -265,7 +267,6 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
return getter() return getter()
} }
} }
return 0, getCharError
} }
func (r *LightRenderer) Size() TermSize { func (r *LightRenderer) Size() TermSize {