fix(lua): disallow vim.wait() in fast contexts

`vim.wait()` cannot be called in a fast callback since the main loop
cannot be run in that context as it is not reentrant

Fixes #26122
This commit is contained in:
Lewis Russell 2023-11-21 11:24:30 +00:00 committed by Lewis Russell
parent 6343d41436
commit 84bbe4b0ca
6 changed files with 29 additions and 8 deletions

View File

@ -1143,6 +1143,8 @@ vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
milliseconds (default 200). Nvim still processes other events during this milliseconds (default 200). Nvim still processes other events during this
time. time.
Cannot be called while in an |api-fast| event.
Examples: >lua Examples: >lua
--- ---
-- Wait for 100 ms, allowing other events to process -- Wait for 100 ms, allowing other events to process
@ -1173,8 +1175,7 @@ vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
• {interval} (integer|nil) (Approximate) number of milliseconds to • {interval} (integer|nil) (Approximate) number of milliseconds to
wait between polls wait between polls
• {fast_only} (boolean|nil) If true, only |api-fast| events will be • {fast_only} (boolean|nil) If true, only |api-fast| events will be
processed. If called from while in an |api-fast| event, processed.
will automatically be set to `true`.
Return: ~ Return: ~
boolean, nil|-1|-2 boolean, nil|-1|-2
@ -1828,7 +1829,8 @@ vim.system({cmd}, {opts}, {on_exit}) *vim.system()*
• pid (integer) Process ID • pid (integer) Process ID
• wait (fun(timeout: integer|nil): SystemCompleted) Wait for the • wait (fun(timeout: integer|nil): SystemCompleted) Wait for the
process to complete. Upon timeout the process is sent the KILL process to complete. Upon timeout the process is sent the KILL
signal (9) and the exit code is set to 124. signal (9) and the exit code is set to 124. Cannot be called in
|api-fast|.
• SystemCompleted is an object with the fields: • SystemCompleted is an object with the fields:
• code: (integer) • code: (integer)
• signal: (integer) • signal: (integer)

View File

@ -325,6 +325,8 @@ The following changes to existing APIs or features add new behavior.
NOTE: the regexp engine still has a hard-coded limit of considering NOTE: the regexp engine still has a hard-coded limit of considering
6 composing chars only. 6 composing chars only.
• |vim.wait()| is no longer allowed to be called in |api-fast|.
============================================================================== ==============================================================================
REMOVED FEATURES *news-removed* REMOVED FEATURES *news-removed*

View File

@ -124,7 +124,8 @@ vim.log = {
--- @return vim.SystemObj Object with the fields: --- @return vim.SystemObj Object with the fields:
--- - pid (integer) Process ID --- - pid (integer) Process ID
--- - wait (fun(timeout: integer|nil): SystemCompleted) Wait for the process to complete. Upon --- - wait (fun(timeout: integer|nil): SystemCompleted) Wait for the process to complete. Upon
--- timeout the process is sent the KILL signal (9) and the exit code is set to 124. --- timeout the process is sent the KILL signal (9) and the exit code is set to 124. Cannot
--- be called in |api-fast|.
--- - SystemCompleted is an object with the fields: --- - SystemCompleted is an object with the fields:
--- - code: (integer) --- - code: (integer)
--- - signal: (integer) --- - signal: (integer)

View File

@ -205,6 +205,8 @@ function vim.schedule(fn) end
--- milliseconds (default 200). Nvim still processes other events during --- milliseconds (default 200). Nvim still processes other events during
--- this time. --- this time.
--- ---
--- Cannot be called while in an |api-fast| event.
---
--- Examples: --- Examples:
--- ---
--- ```lua --- ```lua
@ -235,8 +237,6 @@ function vim.schedule(fn) end
--- @param callback? fun(): boolean Optional callback. Waits until {callback} returns true --- @param callback? fun(): boolean Optional callback. Waits until {callback} returns true
--- @param interval? integer (Approximate) number of milliseconds to wait between polls --- @param interval? integer (Approximate) number of milliseconds to wait between polls
--- @param fast_only? boolean If true, only |api-fast| events will be processed. --- @param fast_only? boolean If true, only |api-fast| events will be processed.
--- If called from while in an |api-fast| event, will
--- automatically be set to `true`.
--- @return boolean, nil|-1|-2 --- @return boolean, nil|-1|-2
--- - If {callback} returns `true` during the {time}: `true, nil` --- - If {callback} returns `true` during the {time}: `true, nil`
--- - If {callback} never returns `true` during the {time}: `false, -1` --- - If {callback} never returns `true` during the {time}: `false, -1`

View File

@ -411,6 +411,10 @@ static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_r
static int nlua_wait(lua_State *lstate) static int nlua_wait(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
if (in_fast_callback) {
return luaL_error(lstate, e_luv_api_disabled, "vim.wait");
}
intptr_t timeout = luaL_checkinteger(lstate, 1); intptr_t timeout = luaL_checkinteger(lstate, 1);
if (timeout < 0) { if (timeout < 0) {
return luaL_error(lstate, "timeout must be >= 0"); return luaL_error(lstate, "timeout must be >= 0");
@ -449,8 +453,7 @@ static int nlua_wait(lua_State *lstate)
fast_only = lua_toboolean(lstate, 4); fast_only = lua_toboolean(lstate, 4);
} }
MultiQueue *loop_events = fast_only || in_fast_callback > 0 MultiQueue *loop_events = fast_only ? main_loop.fast_events : main_loop.events;
? main_loop.fast_events : main_loop.events;
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));

View File

@ -2769,6 +2769,19 @@ describe('lua stdlib', function()
eq({'notification', 'wait', {-2}}, next_msg(500)) eq({'notification', 'wait', {-2}}, next_msg(500))
end) end)
end) end)
it('should not run in fast callbacks #26122', function()
exec_lua([[
vim.uv.new_timer():start(0, 100, function()
local count = 0
vim.wait(100, function()
count = count + 1
return count == 10
end, 100)
end)
]])
assert_alive()
end)
end) end)
it('vim.notify_once', function() it('vim.notify_once', function()