fix(input): stops accepting input after a large paste/input burst #40585

Problem:
Neovim can permanently stop accepting keyboard input after a large paste, or after
any sufficiently large input burst. The screen still redraws and honours window
resize, but typed keys have no effect and the session must be killed. A paste that
triggers the freeze is applied only partially.

Root cause:

`src/nvim/os/input.c` holds `input_buffer[INPUT_BUFFER_SIZE]` (16386 bytes),
compacted left rather than used as a ring:

- `input_get()` drains the buffer by advancing `input_read_pos`, but never rewinds
  the cursors when it empties (`input_read_pos == input_write_pos`).
- The only rewind lives in `input_enqueue_raw()`, which `input_enqueue()` reaches
  only inside `while (input_space() >= 19 && ptr < end)` (19 is the maximum
  expansion of one `<x>` key form).

So once input fills the buffer to within 19 bytes of the top and is then fully
drained, the cursors are pinned near the top with `input_space() < 19`. The gate
never reopens, `input_enqueue()` never rewinds, and all further input is silently
dropped: `input_available()` stays 0 and the editor blocks forever in
`state_enter()` → `input_get()`. Redraw and resize run on independent paths, which
is why the UI looks alive while the keyboard is ignored.

Solution:
Rewind the read/write cursors when the buffer is empty, at the start of
`input_enqueue()`. The reset moves no data in the empty case and guarantees the
space gate can reopen; the non-empty case self-heals as the editor drains.

Test case:
A reproducer (no terminal required; drives `nvim --embed` over msgpack-RPC via
`nvim_input`) floods the input buffer, lets the editor drain it, then probes with
`:qa!`. It shows a sharp threshold at `INPUT_BUFFER_SIZE − 18`:

| `--fill` | master    | with this fix |
|----------|-----------|---------------|
| ≤ 16367  | quits     | quits         |
| ≥ 16368  | **hangs** | quits         |

- On current `master`, the freeze reproduces at `fill=16368`; the patched build
  quits for every fill up to 100000.
- Interactive: a ~40 kB bracketed paste into Insert mode or a `:terminal` no longer
  freezes.
This commit is contained in:
prwang
2026-07-06 12:37:02 +00:00
committed by GitHub
parent a6827be98f
commit 732bf4db97
2 changed files with 27 additions and 0 deletions

View File

@@ -820,6 +820,23 @@ describe('API', function()
command("call nvim_input('<M-'.nr2char(0x40000000).'>')")
eq(1, eval('1'))
end)
it('keeps accepting input after the input buffer fills and drains', function()
-- Regression test: the fixed input buffer (INPUT_BUFFER_SIZE == 16386) is
-- compacted left, not a ring. Filling it to within <19 bytes of the top
-- and then draining it used to leave the read/write cursors pinned, so
-- input_enqueue() could never reclaim space and silently dropped all
-- further input, freezing the editor. One oversized burst of a Normal-mode
-- no-op (CTRL-L) saturates the buffer in a single nvim_input() call; once it
-- has drained, input must still register. poke_eventloop() waits for the
-- burst to be fully consumed before the probe: the fix only reclaims space
-- once the buffer is empty, so the probe must arrive after the drain.
api.nvim_input(('\12'):rep(20000))
n.poke_eventloop()
api.nvim_input('ix<Esc>')
n.poke_eventloop()
eq('x', api.nvim_get_current_line())
end)
end)
describe('nvim_paste', function()