mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 19:09:39 +00:00
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:
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user