libluv: use luv_set_callback to control callback execution

Disable the use of deferred API functions in a fast lua callback
Correctly display error messages from a fast lua callback
This commit is contained in:
Björn Linse
2019-06-23 20:10:28 +02:00
parent 7030d7daf1
commit d33aaa0f5f
8 changed files with 192 additions and 14 deletions

View File

@@ -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

View File

@@ -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_(

View File

@@ -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.

View File

@@ -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, {