diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/generators/gen_api_dispatch.lua | 9 | ||||
-rw-r--r-- | src/nvim/globals.h | 3 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 54 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 11 |
4 files changed, 77 insertions, 0 deletions
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 969c19aedb..3ebcd2d1c4 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -349,6 +349,7 @@ output:write([[ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" ]]) include_headers(output, headers) output:write('\n') @@ -372,6 +373,14 @@ local function process_function(fn) binding=lua_c_function_name, api=fn.name } + + if not fn.fast then + write_shifted_output(output, string.format([[ + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "%s"); + } + ]], fn.name)) + end local cparams = '' local free_code = {} for j = #fn.parameters,1,-1 do diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0b5fafd7c0..139ffe2144 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1061,6 +1061,9 @@ EXTERN char_u e_cmdmap_key[] INIT(=N_( EXTERN char_u e_api_error[] INIT(=N_( "E5555: API call: %s")); +EXTERN char e_luv_api_disabled[] INIT(=N_( + "E5560: %s must not be called in a lua loop callback")); + EXTERN char_u e_floatonly[] INIT(=N_( "E5601: Cannot close window, only floating window would remain")); EXTERN char_u e_floatexchange[] INIT(=N_( diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index ae872c1540..0127bfae7c 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -33,6 +33,8 @@ #include "luv/luv.h" +static int in_fast_callback = 0; + typedef struct { Error err; String lua_err_str; @@ -110,6 +112,50 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } +static void nlua_luv_error_event(void **argv) +{ + char *error = (char *)argv[0]; + msg_ext_set_kind("lua_error"); + emsgf_multiline("Error executing luv callback:\n%s", error); + xfree(error); +} + +static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, + int flags) + FUNC_ATTR_NONNULL_ALL +{ + int retval; + + // luv callbacks might be executed at any os_breakcheck/line_breakcheck + // call, so using the API directly here is not safe. + in_fast_callback++; + + int top = lua_gettop(lstate); + int status = lua_pcall(lstate, nargs, nresult, 0); + if (status) { + if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { + // consider out of memory errors unrecoverable, just like xmalloc() + mch_errmsg(e_outofmem); + mch_errmsg("\n"); + preserve_exit(); + } + const char *error = lua_tostring(lstate, -1); + + multiqueue_put(main_loop.events, nlua_luv_error_event, + 1, xstrdup(error)); + lua_pop(lstate, 1); // error mesage + retval = -status; + } else { // LUA_OK + if (nresult == LUA_MULTRET) { + nresult = lua_gettop(lstate) - top + nargs + 1; + } + retval = nresult; + } + + in_fast_callback--; + return retval; +} + static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; @@ -180,6 +226,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // vim.loop luv_set_loop(lstate, &main_loop.uv); + luv_set_callback(lstate, nlua_luv_cfpcall); luaopen_luv(lstate); lua_setfield(lstate, -2, "loop"); @@ -546,6 +593,13 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, } } +/// check if the current execution context is safe for calling deferred API +/// methods. Luv callbacks are unsafe as they are called inside the uv loop. +bool nlua_is_deferred_safe(lua_State *lstate) +{ + return in_fast_callback == 0; +} + /// Run lua string /// /// Used for :lua. diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 848bccaae6..46c96b455f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -172,11 +172,22 @@ local function __index(t, key) end end +--- Defers the wrapped callback until when the nvim API is safe to call. +--- +--- See |vim-loop-callbacks| +local function schedule_wrap(cb) + return (function (...) + local args = {...} + vim.schedule(function() cb(unpack(args)) end) + end) +end + local module = { _update_package_paths = _update_package_paths, _os_proc_children = _os_proc_children, _os_proc_info = _os_proc_info, _system = _system, + schedule_wrap = schedule_wrap, } setmetatable(module, { |