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

@@ -315,6 +315,16 @@ size_t input_enqueue(uint64_t chan_id, String keys)
{
current_ui = chan_id;
// If the buffer has been fully drained, rewind the read/write cursors to the
// start to reclaim tail space. Otherwise a drained-but-not-rewound buffer left
// pinned within <19 bytes of the top (input_get() advances input_read_pos but
// never rewinds it) would fail the `input_space() >= 19` gate below forever,
// silently dropping all further input and freezing the editor.
if (input_read_pos == input_write_pos) {
input_read_pos = input_buffer;
input_write_pos = input_buffer;
}
if (keys.size > 0) {
set_vim_var_nr(VV_USERACTIVE, (varnumber_T)os_realtime());
}

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()