feat(lua): add proper support of luv threads

This commit is contained in:
erw7 2021-09-11 11:48:58 +09:00 committed by bfredl
parent d0f8f76224
commit b87867e69e
13 changed files with 989 additions and 193 deletions

View File

@ -0,0 +1,48 @@
-- prevents luacheck from making lints for setting things on vim
local vim = assert(vim)
local pathtrails = {}
vim._so_trails = {}
for s in (package.cpath..';'):gmatch('[^;]*;') do
s = s:sub(1, -2) -- Strip trailing semicolon
-- Find out path patterns. pathtrail should contain something like
-- /?.so, \?.dll. This allows not to bother determining what correct
-- suffixes are.
local pathtrail = s:match('[/\\][^/\\]*%?.*$')
if pathtrail and not pathtrails[pathtrail] then
pathtrails[pathtrail] = true
table.insert(vim._so_trails, pathtrail)
end
end
function vim._load_package(name)
local basename = name:gsub('%.', '/')
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
if #found > 0 then
local f, err = loadfile(found[1])
return f or error(err)
end
local so_paths = {}
for _,trail in ipairs(vim._so_trails) do
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
table.insert(so_paths, path)
end
found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
if #found > 0 then
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
-- a) strip prefix up to and including the first dash, if any
-- b) replace all dots by underscores
-- c) prepend "luaopen_"
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
local dash = name:find("-", 1, true)
local modname = dash and name:sub(dash + 1) or name
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
return f or error(err)
end
return nil
end
table.insert(package.loaders, 1, vim._load_package)

View File

@ -776,7 +776,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
if (has_metatable) {
nlua_pushref(l, nlua_empty_dict_ref);
nlua_pushref(l, nlua_get_empty_dict_ref(l));
if (lua_rawequal(l, -2, -1)) {
as_empty_dict = true;
} else {
@ -822,7 +822,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
}
break;
case LUA_TUSERDATA:
nlua_pushref(l, nlua_nil_ref);
nlua_pushref(l, nlua_get_nil_ref(l));
bool is_nil = lua_rawequal(l, -2, -1);
lua_pop(l, 1);
if (is_nil) {
@ -1285,7 +1285,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
/* Handle empty objects */
if (token.type == T_OBJ_END) {
nlua_pushref(l, nlua_empty_dict_ref); \
nlua_pushref(l, nlua_get_empty_dict_ref(l)); \
lua_setmetatable(l, -2); \
json_decode_ascend(json);
return;
@ -1392,7 +1392,7 @@ static void json_process_value(lua_State *l, json_parse_t *json,
if (use_luanil) {
lua_pushnil(l);
} else {
nlua_pushref(l, nlua_nil_ref);
nlua_pushref(l, nlua_get_nil_ref(l));
}
break;;
default:
@ -1549,7 +1549,15 @@ int lua_cjson_new(lua_State *l)
};
/* Initialise number conversions */
lua_getfield(l, LUA_REGISTRYINDEX, "nvim.thread");
bool is_thread = lua_toboolean(l, -1);
lua_pop(l, 1);
// Since fpconv_init does not need to be called multiple times and is not
// thread safe, it should only be called in the main thread.
if (!is_thread) {
fpconv_init();
}
/* Test if array metatables are in registry */
lua_pushlightuserdata(l, json_lightudata_mask(&json_empty_array));
@ -1582,7 +1590,7 @@ int lua_cjson_new(lua_State *l)
compat_luaL_setfuncs(l, reg, 1);
/* Set cjson.null */
nlua_pushref(l, nlua_nil_ref);
nlua_pushref(l, nlua_get_nil_ref(l));
lua_setfield(l, -2, "null");
/* Set cjson.empty_array_mt */

View File

@ -63,6 +63,7 @@ set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua)
set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua)
set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua)
set(LUA_LOAD_PACKAGE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_load_package.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@ -336,6 +337,7 @@ add_custom_command(
${LUA_F_MODULE_SOURCE} lua_F_module
${LUA_META_MODULE_SOURCE} lua_meta_module
${LUA_FILETYPE_MODULE_SOURCE} lua_filetype_module
${LUA_LOAD_PACKAGE_MODULE_SOURCE} lua_load_package_module
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_VIM_MODULE_SOURCE}
@ -344,6 +346,7 @@ add_custom_command(
${LUA_F_MODULE_SOURCE}
${LUA_META_MODULE_SOURCE}
${LUA_FILETYPE_MODULE_SOURCE}
${LUA_LOAD_PACKAGE_MODULE_SOURCE}
VERBATIM
)

View File

@ -1955,7 +1955,7 @@ Dictionary nvim__stats(void)
Dictionary rv = ARRAY_DICT_INIT;
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
return rv;
}

View File

@ -156,7 +156,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
&& ret.string_keys_num == 0)) {
ret.type = kObjectTypeArray;
if (tsize == 0 && lua_getmetatable(lstate, -1)) {
nlua_pushref(lstate, nlua_empty_dict_ref);
nlua_pushref(lstate, nlua_get_empty_dict_ref(lstate));
if (lua_rawequal(lstate, -2, -1)) {
ret.type = kObjectTypeDictionary;
}
@ -401,7 +401,7 @@ nlua_pop_typval_table_processing_end:
}
case LUA_TUSERDATA: {
// TODO(bfredl): check mt.__call and convert to function?
nlua_pushref(lstate, nlua_nil_ref);
nlua_pushref(lstate, nlua_get_nil_ref(lstate));
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {
@ -445,7 +445,7 @@ static bool typval_conv_special = false;
if (typval_conv_special) { \
lua_pushnil(lstate); \
} else { \
nlua_pushref(lstate, nlua_nil_ref); \
nlua_pushref(lstate, nlua_get_nil_ref(lstate)); \
} \
} while (0)
@ -495,7 +495,7 @@ static bool typval_conv_special = false;
nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
} else { \
lua_createtable(lstate, 0, 0); \
nlua_pushref(lstate, nlua_empty_dict_ref); \
nlua_pushref(lstate, nlua_get_empty_dict_ref(lstate)); \
lua_setmetatable(lstate, -2); \
} \
} while (0)
@ -734,7 +734,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special
} else {
lua_createtable(lstate, 0, (int)dict.size);
if (dict.size == 0 && !special) {
nlua_pushref(lstate, nlua_empty_dict_ref);
nlua_pushref(lstate, nlua_get_empty_dict_ref(lstate));
lua_setmetatable(lstate, -2);
}
}
@ -782,7 +782,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
if (special) {
lua_pushnil(lstate);
} else {
nlua_pushref(lstate, nlua_nil_ref);
nlua_pushref(lstate, nlua_get_nil_ref(lstate));
}
break;
case kObjectTypeLuaRef: {
@ -1206,7 +1206,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
case LUA_TUSERDATA: {
nlua_pushref(lstate, nlua_nil_ref);
nlua_pushref(lstate, nlua_get_nil_ref(lstate));
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {

View File

@ -45,11 +45,22 @@ static int in_fast_callback = 0;
// Initialized in nlua_init().
static lua_State *global_lstate = NULL;
static uv_thread_t main_thread;
typedef struct {
Error err;
String lua_err_str;
} LuaError;
typedef struct {
LuaRef nil_ref;
LuaRef empty_dict_ref;
int ref_count;
#if __has_feature(address_sanitizer)
PMap(handle_T) ref_markers;
#endif
} nlua_ref_state_t;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.c.generated.h"
# include "lua/vim_module.generated.h"
@ -65,11 +76,16 @@ typedef struct {
}
#if __has_feature(address_sanitizer)
static PMap(handle_T) nlua_ref_markers = MAP_INIT;
static bool nlua_track_refs = false;
# define NLUA_TRACK_REFS
#endif
typedef enum luv_err_type {
kCallback,
kThread,
kThreadCallback,
} luv_err_t;
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@ -122,8 +138,21 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
static void nlua_luv_error_event(void **argv)
{
char *error = (char *)argv[0];
luv_err_t type = (luv_err_t)(intptr_t)argv[1];
msg_ext_set_kind("lua_error");
switch (type) {
case kCallback:
semsg_multiline("Error executing luv callback:\n%s", error);
break;
case kThread:
semsg_multiline("Error in luv thread:\n%s", error);
break;
case kThreadCallback:
semsg_multiline("Error in luv callback, thread:\n%s", error);
break;
default:
break;
}
xfree(error);
}
@ -148,7 +177,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
const char *error = lua_tostring(lstate, -1);
multiqueue_put(main_loop.events, nlua_luv_error_event,
1, xstrdup(error));
2, xstrdup(error), (intptr_t)kCallback);
lua_pop(lstate, 1); // error message
retval = -status;
} else { // LUA_OK
@ -162,6 +191,106 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
return retval;
}
static int nlua_luv_thread_cb_cfpcall(lua_State *lstate, int nargs, int nresult,
int flags)
{
return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, true);
}
static int nlua_luv_thread_cfpcall(lua_State *lstate, int nargs, int nresult,
int flags)
FUNC_ATTR_NONNULL_ALL
{
return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, false);
}
static int nlua_luv_thread_cfcpcall(lua_State *lstate, lua_CFunction func,
void *ud, int flags)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
lua_pushcfunction(lstate, func);
lua_pushlightuserdata(lstate, ud);
int retval = nlua_luv_thread_cfpcall(lstate, 1, 0, flags);
return retval;
}
static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nresult,
int flags, bool is_callback)
FUNC_ATTR_NONNULL_ALL
{
int retval;
int top = lua_gettop(lstate);
int status = lua_pcall(lstate, nargs, nresult, 0);
if (status) {
if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
// Terminate this thread, as the main thread may be able to continue
// execution.
mch_errmsg(e_outofmem);
mch_errmsg("\n");
lua_close(lstate);
#ifdef WIN32
ExitThread(0);
#else
pthread_exit(0);
#endif
}
const char *error = lua_tostring(lstate, -1);
loop_schedule_deferred(&main_loop,
event_create(nlua_luv_error_event, 2,
xstrdup(error),
is_callback
? (intptr_t)kThreadCallback
: (intptr_t)kThread));
lua_pop(lstate, 1); // error message
retval = -status;
} else { // LUA_OK
if (nresult == LUA_MULTRET) {
nresult = lua_gettop(lstate) - top + nargs + 1;
}
retval = nresult;
}
return retval;
}
static int nlua_thr_api_nvim__get_runtime(lua_State *lstate)
{
if (lua_gettop(lstate) != 3) {
return luaL_error(lstate, "Expected 3 arguments");
}
luaL_checktype(lstate, -1, LUA_TTABLE);
lua_getfield(lstate, -1, "is_lua");
if (!lua_isboolean(lstate, -1)) {
return luaL_error(lstate, "is_lua is not a boolean");
}
bool is_lua = lua_toboolean(lstate, -1);
lua_pop(lstate, 2);
luaL_checktype(lstate, -1, LUA_TBOOLEAN);
bool all = lua_toboolean(lstate, -1);
lua_pop(lstate, 1);
Error err = ERROR_INIT;
const Array pat = nlua_pop_Array(lstate, &err);
if (ERROR_SET(&err)) {
luaL_where(lstate, 1);
lua_pushstring(lstate, err.msg);
api_clear_error(&err);
lua_concat(lstate, 2);
return lua_error(lstate);
}
ArrayOf(String) ret = runtime_get_named_thread(is_lua, pat, all);
nlua_push_Array(lstate, ret, true);
api_free_array(ret);
api_free_array(pat);
return 1;
}
static void nlua_schedule_event(void **argv)
{
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
@ -302,6 +431,157 @@ static int nlua_wait(lua_State *lstate)
return 2;
}
static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread)
FUNC_ATTR_NONNULL_ALL
{
nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state));
memset(ref_state, 0, sizeof(*ref_state));
ref_state->nil_ref = LUA_NOREF;
ref_state->empty_dict_ref = LUA_NOREF;
return ref_state;
}
static nlua_ref_state_t *nlua_get_ref_state(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
lua_getfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state");
nlua_ref_state_t *ref_state = lua_touserdata(lstate, -1);
lua_pop(lstate, 1);
return ref_state;
}
LuaRef nlua_get_nil_ref(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
return ref_state->nil_ref;
}
LuaRef nlua_get_empty_dict_ref(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
return ref_state->empty_dict_ref;
}
static int nlua_get_ref_count(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
return ref_state->ref_count;
}
int nlua_get_global_ref_count(void)
{
lua_State *const lstate = global_lstate;
return nlua_get_ref_count(lstate);
}
static void nlua_common_vim_init(lua_State *lstate, bool is_thread)
FUNC_ATTR_NONNULL_ARG(1)
{
nlua_ref_state_t *ref_state = nlua_new_ref_state(lstate, is_thread);
lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state");
// vim.is_thread
lua_pushboolean(lstate, is_thread);
lua_setfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
lua_pushcfunction(lstate, &nlua_is_thread);
lua_setfield(lstate, -2, "is_thread");
// vim.NIL
lua_newuserdata(lstate, 0);
lua_createtable(lstate, 0, 0);
lua_pushcfunction(lstate, &nlua_nil_tostring);
lua_setfield(lstate, -2, "__tostring");
lua_setmetatable(lstate, -2);
ref_state->nil_ref = nlua_ref(lstate, -1);
lua_pushvalue(lstate, -1);
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL");
lua_setfield(lstate, -2, "NIL");
// vim._empty_dict_mt
lua_createtable(lstate, 0, 0);
lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
lua_setfield(lstate, -2, "__tostring");
ref_state->empty_dict_ref = nlua_ref(lstate, -1);
lua_pushvalue(lstate, -1);
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
lua_setfield(lstate, -2, "_empty_dict_mt");
// vim.loop
if (is_thread) {
luv_set_callback(lstate, nlua_luv_thread_cb_cfpcall);
luv_set_thread(lstate, nlua_luv_thread_cfpcall);
luv_set_cthread(lstate, nlua_luv_thread_cfcpcall);
} else {
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
}
luaopen_luv(lstate);
lua_pushvalue(lstate, -1);
lua_setfield(lstate, -3, "loop");
// package.loaded.luv = vim.loop
// otherwise luv will be reinitialized when require'luv'
lua_getglobal(lstate, "package");
lua_getfield(lstate, -1, "loaded");
lua_pushvalue(lstate, -3);
lua_setfield(lstate, -2, "luv");
lua_pop(lstate, 3);
}
static void nlua_common_package_init(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
{
const char *code = (char *)&shared_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua")
|| nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n"));
return;
}
}
{
const char *code = (char *)&lua_load_package_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(lua_load_package_module) - 1, "@vim/_load_package.lua")
|| lua_pcall(lstate, 0, 0, 0)) {
nlua_error(lstate, _("E5106: Error while creating _load_package module: %.*s"));
return;
}
}
{
lua_getglobal(lstate, "package"); // [package]
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
const char *code = (char *)&inspect_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
return;
}
// [package, loaded, inspect]
lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
}
{
const char *code = (char *)&lua_F_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n"));
return;
}
// [package, loaded, module]
lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
lua_pop(lstate, 2); // []
}
}
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@ -362,80 +642,22 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_wait);
lua_setfield(lstate, -2, "wait");
// vim.NIL
lua_newuserdata(lstate, 0);
lua_createtable(lstate, 0, 0);
lua_pushcfunction(lstate, &nlua_nil_tostring);
lua_setfield(lstate, -2, "__tostring");
lua_setmetatable(lstate, -2);
nlua_nil_ref = nlua_ref(lstate, -1);
lua_pushvalue(lstate, -1);
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL");
lua_setfield(lstate, -2, "NIL");
// vim._empty_dict_mt
lua_createtable(lstate, 0, 0);
lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
lua_setfield(lstate, -2, "__tostring");
nlua_empty_dict_ref = nlua_ref(lstate, -1);
lua_pushvalue(lstate, -1);
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
lua_setfield(lstate, -2, "_empty_dict_mt");
nlua_common_vim_init(lstate, false);
// internal vim._treesitter... API
nlua_add_treesitter(lstate);
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
luaopen_luv(lstate);
lua_pushvalue(lstate, -1);
lua_setfield(lstate, -3, "loop");
// package.loaded.luv = vim.loop
// otherwise luv will be reinitialized when require'luv'
lua_getglobal(lstate, "package");
lua_getfield(lstate, -1, "loaded");
lua_pushvalue(lstate, -3);
lua_setfield(lstate, -2, "luv");
lua_pop(lstate, 3);
nlua_state_add_stdlib(lstate);
nlua_state_add_stdlib(lstate, false);
lua_setglobal(lstate, "vim");
{
const char *code = (char *)&shared_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua")
|| nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n"));
return 1;
}
}
nlua_common_package_init(lstate);
{
lua_getglobal(lstate, "package"); // [package]
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
const char *code = (char *)&inspect_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
return 1;
}
// [package, loaded, inspect]
lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
code = (char *)&lua_F_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n"));
return 1;
}
// [package, loaded, module]
lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
code = (char *)&lua_filetype_module[0];
char *code = (char *)&lua_filetype_module[0];
if (luaL_loadbuffer(lstate, code, sizeof(lua_filetype_module) - 1, "@vim/filetype.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating vim.filetype module: %.*s"));
@ -495,9 +717,60 @@ void nlua_init(void)
luaL_openlibs(lstate);
nlua_state_init(lstate);
luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem);
global_lstate = lstate;
main_thread = uv_thread_self();
}
static lua_State *nlua_thread_acquire_vm(void)
{
// If it is called from the main thread, it will attempt to rebuild the cache.
const uv_thread_t self = uv_thread_self();
if (uv_thread_equal(&main_thread, &self)) {
runtime_search_path_validate();
}
lua_State *lstate = luaL_newstate();
// Add in the lua standard libraries
luaL_openlibs(lstate);
// print
lua_pushcfunction(lstate, &nlua_print);
lua_setglobal(lstate, "print");
lua_pushinteger(lstate, 0);
lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount");
// vim
lua_newtable(lstate);
nlua_common_vim_init(lstate, true);
nlua_state_add_stdlib(lstate, true);
lua_setglobal(lstate, "vim");
nlua_common_package_init(lstate);
lua_getglobal(lstate, "vim");
lua_getglobal(lstate, "package");
lua_getfield(lstate, -1, "loaded");
lua_getfield(lstate, -1, "vim.inspect");
lua_setfield(lstate, -4, "inspect");
lua_pop(lstate, 3);
lua_getglobal(lstate, "vim");
lua_createtable(lstate, 0, 0);
lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime);
lua_setfield(lstate, -2, "nvim__get_runtime");
lua_setfield(lstate, -2, "api");
lua_pop(lstate, 1);
return lstate;
}
void nlua_free_all_mem(void)
{
@ -505,26 +778,30 @@ void nlua_free_all_mem(void)
return;
}
lua_State *lstate = global_lstate;
nlua_common_free_all_mem(lstate);
}
nlua_unref(lstate, nlua_nil_ref);
nlua_unref(lstate, nlua_empty_dict_ref);
static void nlua_common_free_all_mem(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
nlua_unref(lstate, nlua_get_nil_ref(lstate));
nlua_unref(lstate, nlua_get_empty_dict_ref(lstate));
#ifdef NLUA_TRACK_REFS
if (nlua_refcount) {
fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
if (ref_state->ref_count) {
fprintf(stderr, "%d lua references were leaked!", ref_state->ref_count);
}
if (nlua_track_refs) {
// in case there are leaked luarefs, leak the associated memory
// to get LeakSanitizer stacktraces on exit
pmap_destroy(handle_T)(&nlua_ref_markers);
pmap_destroy(handle_T)(&ref_state->ref_markers);
}
#endif
nlua_refcount = 0;
lua_close(lstate);
}
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@ -602,9 +879,18 @@ static int nlua_print(lua_State *const lstate)
#undef PRINT_ERROR
ga_append(&msg_ga, NUL);
if (in_fast_callback) {
lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
bool is_thread = lua_toboolean(lstate, -1);
lua_pop(lstate, 1);
if (is_thread) {
loop_schedule_deferred(&main_loop,
event_create(nlua_print_event, 2,
msg_ga.ga_data,
(intptr_t)msg_ga.ga_len));
} else if (in_fast_callback) {
multiqueue_put(main_loop.events, nlua_print_event,
2, msg_ga.ga_data, msg_ga.ga_len);
2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len);
} else {
nlua_print_event((void *[]){ msg_ga.ga_data,
(void *)(intptr_t)msg_ga.ga_len });
@ -613,10 +899,12 @@ static int nlua_print(lua_State *const lstate)
nlua_print_error:
ga_clear(&msg_ga);
char *buff = xmalloc(IOSIZE);
const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx,
size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx,
(int)errmsg_len, errmsg);
lua_pushlstring(lstate, (char *)IObuff, len);
lua_pushlstring(lstate, buff, len);
xfree(buff);
return lua_error(lstate);
}
@ -806,16 +1094,19 @@ static int nlua_getenv(lua_State *lstate)
/// add the value to the registry
/// The current implementation does not support calls from threads.
LuaRef nlua_ref(lua_State *lstate, int index)
{
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
lua_pushvalue(lstate, index);
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
if (ref > 0) {
nlua_refcount++;
ref_state->ref_count++;
#ifdef NLUA_TRACK_REFS
if (nlua_track_refs) {
// dummy allocation to make LeakSanitizer track our luarefs
pmap_put(handle_T)(&nlua_ref_markers, ref, xmalloc(3));
pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
}
#endif
}
@ -825,12 +1116,14 @@ LuaRef nlua_ref(lua_State *lstate, int index)
/// remove the value from the registry
void nlua_unref(lua_State *lstate, LuaRef ref)
{
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
if (ref > 0) {
nlua_refcount--;
ref_state->ref_count--;
#ifdef NLUA_TRACK_REFS
// NB: don't remove entry from map to track double-unref
if (nlua_track_refs) {
xfree(pmap_get(handle_T)(&nlua_ref_markers, ref));
xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref));
}
#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
@ -1391,6 +1684,13 @@ cleanup:
return ret;
}
static int nlua_is_thread(lua_State *lstate)
{
lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
return 1;
}
// Required functions for lua c functions as VimL callbacks
int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state)

View File

@ -4,6 +4,7 @@
#include <lauxlib.h>
#include <lua.h>
#include "nvim/assert.h"
#include "nvim/api/private/defs.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
@ -14,11 +15,6 @@
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
EXTERN int nlua_refcount INIT(= 0);
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \

View File

@ -471,8 +471,9 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
}
void nlua_state_add_stdlib(lua_State *const lstate)
void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
{
if (!is_thread) {
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
@ -509,6 +510,11 @@ void nlua_state_add_stdlib(lua_State *const lstate)
lua_pushcfunction(lstate, &nlua_setvar);
lua_setfield(lstate, -2, "_setvar");
// vim.spell
luaopen_spell(lstate);
lua_setfield(lstate, -2, "spell");
}
// vim.mpack
luaopen_mpack(lstate);
lua_pushvalue(lstate, -1);
@ -526,10 +532,7 @@ void nlua_state_add_stdlib(lua_State *const lstate)
lua_pushcfunction(lstate, &nlua_xdl_diff);
lua_setfield(lstate, -2, "diff");
// vim.spell
luaopen_spell(lstate);
lua_setfield(lstate, -2, "spell");
// vim.json
lua_cjson_new(lstate);
lua_setfield(lstate, -2, "json");
}

View File

@ -43,53 +43,6 @@ assert(vim.inspect)
vim.filetype = package.loaded['vim.filetype']
assert(vim.filetype)
local pathtrails = {}
vim._so_trails = {}
for s in (package.cpath..';'):gmatch('[^;]*;') do
s = s:sub(1, -2) -- Strip trailing semicolon
-- Find out path patterns. pathtrail should contain something like
-- /?.so, \?.dll. This allows not to bother determining what correct
-- suffixes are.
local pathtrail = s:match('[/\\][^/\\]*%?.*$')
if pathtrail and not pathtrails[pathtrail] then
pathtrails[pathtrail] = true
table.insert(vim._so_trails, pathtrail)
end
end
function vim._load_package(name)
local basename = name:gsub('%.', '/')
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
if #found > 0 then
local f, err = loadfile(found[1])
return f or error(err)
end
local so_paths = {}
for _,trail in ipairs(vim._so_trails) do
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
table.insert(so_paths, path)
end
found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
if #found > 0 then
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
-- a) strip prefix up to and including the first dash, if any
-- b) replace all dots by underscores
-- c) prepend "luaopen_"
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
local dash = name:find("-", 1, true)
local modname = dash and name:sub(dash + 1) or name
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
return f or error(err)
end
return nil
end
-- Insert vim._load_package after the preloader at position 2
table.insert(package.loaders, 2, vim._load_package)
-- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c
setmetatable(vim, {

View File

@ -154,10 +154,10 @@ bool event_teardown(void)
void early_init(mparm_T *paramp)
{
env_init();
fs_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
runtime_search_path_init();
highlight_init();
#ifdef WIN32
@ -230,6 +230,10 @@ int main(int argc, char **argv)
// `argc` and `argv` are also copied, so that they can be changed.
init_params(&params, argc, argv);
// Since os_open is called during the init_startuptime, we need to call
// fs_init before it.
fs_init();
init_startuptime(&params);
// Need to find "--clean" before actually parsing arguments.

View File

@ -40,8 +40,10 @@
bool did_try_to_free = false; \
uv_call_start: {} \
uv_fs_t req; \
fs_loop_lock(); \
ret = func(&fs_loop, &req, __VA_ARGS__); \
uv_fs_req_cleanup(&req); \
fs_loop_unlock(); \
if (ret == UV_ENOMEM && !did_try_to_free) { \
try_to_free_memory(); \
did_try_to_free = true; \
@ -52,12 +54,24 @@ uv_call_start: {} \
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
static uv_loop_t fs_loop;
static uv_mutex_t fs_loop_mutex;
// Initialize the fs module
void fs_init(void)
{
uv_loop_init(&fs_loop);
uv_mutex_init_recursive(&fs_loop_mutex);
}
void fs_loop_lock(void)
{
uv_mutex_lock(&fs_loop_mutex);
}
void fs_loop_unlock(void)
{
uv_mutex_unlock(&fs_loop_mutex);
}
@ -98,9 +112,12 @@ bool os_isrealdir(const char *name)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
fs_loop_lock();
if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) {
fs_loop_unlock();
return false;
}
fs_loop_unlock();
if (S_ISLNK(request.statbuf.st_mode)) {
return false;
} else {
@ -738,7 +755,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
return UV_EINVAL;
}
uv_fs_t request;
fs_loop_lock();
int result = uv_fs_stat(&fs_loop, &request, name, NULL);
fs_loop_unlock();
if (result == kLibuvSuccess) {
*statbuf = request.statbuf;
}
@ -935,9 +954,11 @@ int os_mkdtemp(const char *template, char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
fs_loop_lock();
int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL);
fs_loop_unlock();
if (result == kLibuvSuccess) {
STRNCPY(path, request.path, TEMP_FILE_PATH_MAXLEN);
xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
}
uv_fs_req_cleanup(&request);
return result;
@ -962,7 +983,9 @@ int os_rmdir(const char *path)
bool os_scandir(Directory *dir, const char *path)
FUNC_ATTR_NONNULL_ALL
{
fs_loop_lock();
int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL);
fs_loop_unlock();
if (r < 0) {
os_closedir(dir);
}
@ -1023,7 +1046,9 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)
return false;
}
uv_fs_t request;
fs_loop_lock();
bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess;
fs_loop_unlock();
if (ok) {
file_info->stat = request.statbuf;
}
@ -1041,6 +1066,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
{
uv_fs_t request;
memset(file_info, 0, sizeof(*file_info));
fs_loop_lock();
bool ok = uv_fs_fstat(&fs_loop,
&request,
file_descriptor,
@ -1049,6 +1075,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
file_info->stat = request.statbuf;
}
uv_fs_req_cleanup(&request);
fs_loop_unlock();
return ok;
}
@ -1165,6 +1192,7 @@ char *os_realpath(const char *name, char *buf)
FUNC_ATTR_NONNULL_ARG(1)
{
uv_fs_t request;
fs_loop_lock();
int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
if (result == kLibuvSuccess) {
if (buf == NULL) {
@ -1173,6 +1201,7 @@ char *os_realpath(const char *name, char *buf)
xstrlcpy(buf, request.ptr, MAXPATHL + 1);
}
uv_fs_req_cleanup(&request);
fs_loop_unlock();
return result == kLibuvSuccess ? buf : NULL;
}

View File

@ -24,6 +24,23 @@
static bool runtime_search_path_valid = false;
static int *runtime_search_path_ref = NULL;
static RuntimeSearchPath runtime_search_path;
static RuntimeSearchPath runtime_search_path_thread;
static uv_mutex_t runtime_search_path_mutex;
void runtime_search_path_init(void)
{
uv_mutex_init(&runtime_search_path_mutex);
}
void runtime_search_path_lock(void)
{
uv_mutex_lock(&runtime_search_path_mutex);
}
void runtime_search_path_unlock(void)
{
uv_mutex_unlock(&runtime_search_path_mutex);
}
/// ":runtime [what] {name}"
void ex_runtime(exarg_T *eap)
@ -172,6 +189,17 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref)
return runtime_search_path;
}
RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
{
RuntimeSearchPath dst = KV_INITIAL_VALUE;
for (size_t j = 0; j < kv_size(src); j++) {
SearchPathItem src_item = kv_A(src, j);
kv_push(dst, ((SearchPathItem){ xstrdup(src_item.path), src_item.after, src_item.has_lua }));
}
return dst;
}
void runtime_search_path_unref(RuntimeSearchPath path, int *ref)
FUNC_ATTR_NONNULL_ALL
{
@ -302,15 +330,33 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
{
int ref;
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
ArrayOf(String) rv = ARRAY_DICT_INIT;
static char buf[MAXPATHL];
ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path);
runtime_search_path_unref(path, &ref);
return rv;
}
ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all)
{
runtime_search_path_lock();
ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread);
runtime_search_path_unlock();
return rv;
}
ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
RuntimeSearchPath path)
{
ArrayOf(String) rv = ARRAY_DICT_INIT;
size_t buf_len = MAXPATHL;
char *buf = xmalloc(MAXPATHL);
for (size_t i = 0; i < kv_size(path); i++) {
SearchPathItem *item = &kv_A(path, i);
if (lua) {
if (item->has_lua == kNone) {
size_t size = (size_t)snprintf(buf, sizeof buf, "%s/lua/", item->path);
item->has_lua = (size < sizeof buf && os_isdir((char_u *)buf)) ? kTrue : kFalse;
size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path);
item->has_lua = (size < buf_len && os_isdir((char_u *)buf));
}
if (item->has_lua == kFalse) {
continue;
@ -320,9 +366,9 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
for (size_t j = 0; j < pat.size; j++) {
Object pat_item = pat.items[j];
if (pat_item.type == kObjectTypeString) {
size_t size = (size_t)snprintf(buf, sizeof buf, "%s/%s",
size_t size = (size_t)snprintf(buf, buf_len, "%s/%s",
item->path, pat_item.data.string.data);
if (size < sizeof buf) {
if (size < buf_len) {
if (os_file_is_readable(buf)) {
ADD(rv, STRING_OBJ(cstr_to_string(buf)));
if (!all) {
@ -333,9 +379,8 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
}
}
}
done:
runtime_search_path_unref(path, &ref);
xfree(buf);
return rv;
}
@ -569,6 +614,10 @@ void runtime_search_path_validate(void)
runtime_search_path = runtime_search_path_build();
runtime_search_path_valid = true;
runtime_search_path_ref = NULL; // initially unowned
runtime_search_path_lock();
runtime_search_path_free(runtime_search_path_thread);
runtime_search_path_thread = copy_runtime_search_path(runtime_search_path);
runtime_search_path_unlock();
}
}

View File

@ -0,0 +1,403 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local assert_alive = helpers.assert_alive
local clear = helpers.clear
local feed = helpers.feed
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local next_msg = helpers.next_msg
local NIL = helpers.NIL
local pcall_err = helpers.pcall_err
describe('thread', function()
local screen
before_each(function()
clear()
screen = Screen.new(50, 10)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {bold = true, reverse = true},
[3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {bold = true},
})
end)
it('entry func is executed in protected mode', function()
local code = [[
local thread = vim.loop.new_thread(function()
error('Error in thread entry func')
end)
vim.loop.thread_join(thread)
]]
exec_lua(code)
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2: }|
{3:Error in luv thread:} |
{3:[string "<nvim>"]:2: Error in thread entry func} |
{4:Press ENTER or type command to continue}^ |
]])
feed('<cr>')
assert_alive()
end)
it('callback is executed in protected mode', function()
local code = [[
local thread = vim.loop.new_thread(function()
local timer = vim.loop.new_timer()
local function ontimeout()
timer:stop()
timer:close()
error('Error in thread callback')
end
timer:start(10, 0, ontimeout)
vim.loop.run()
end)
vim.loop.thread_join(thread)
]]
exec_lua(code)
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2: }|
{3:Error in luv callback, thread:} |
{3:[string "<nvim>"]:6: Error in thread callback} |
{4:Press ENTER or type command to continue}^ |
]])
feed('<cr>')
assert_alive()
end)
describe('print', function()
it('work', function()
local code = [[
local thread = vim.loop.new_thread(function()
print('print in thread')
end)
vim.loop.thread_join(thread)
]]
exec_lua(code)
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
print in thread |
]])
end)
end)
describe('vim.*', function()
before_each(function()
clear()
local code = [[
Thread_Test = {}
Thread_Test.entry_func = function(async, entry_str, args)
local decoded_args = vim.mpack.decode(args)
assert(loadstring(entry_str))(async, decoded_args)
end
function Thread_Test:do_test()
local async
local on_async = self.on_async
async = vim.loop.new_async(function(ret)
on_async(ret)
async:close()
end)
local thread =
vim.loop.new_thread(self.entry_func, async, self.entry_str, self.args)
vim.loop.thread_join(thread)
end
Thread_Test.new = function(entry, on_async, ...)
self = {}
setmetatable(self, {__index = Thread_Test})
self.args = vim.mpack.encode({...})
self.entry_str = string.dump(entry)
self.on_async = on_async
return self
end
]]
exec_lua(code)
end)
it('is_thread', function()
local code = [[
local entry = function(async)
async:send(vim.is_thread())
end
local on_async = function(ret)
vim.rpcnotify(1, 'result', ret)
end
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result', {true}}, next_msg())
end)
it('loop', function()
local code = [[
local entry = function(async)
async:send(vim.loop.version())
end
local on_async = function(ret)
vim.rpcnotify(1, ret)
end
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
exec_lua(code)
local msg = next_msg()
eq(msg[1], 'notification')
assert(tonumber(msg[2]) >= 72961)
end)
it('mpack', function()
local code = [[
local entry = function(async)
async:send(vim.mpack.encode({33, vim.NIL, 'text'}))
end
local on_async = function(ret)
vim.rpcnotify(1, 'result', vim.mpack.decode(ret))
end
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('json', function()
local code = [[
local entry = function(async)
async:send(vim.json.encode({33, vim.NIL, 'text'}))
end
local on_async = function(ret)
vim.rpcnotify(1, 'result', vim.json.decode(ret))
end
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('diff', function()
local code = [[
local entry = function(async)
async:send(vim.diff('Hello\n', 'Helli\n'))
end
local on_async = function(ret)
vim.rpcnotify(1, 'result', ret)
end
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result',
{table.concat({
'@@ -1 +1 @@',
'-Hello',
'+Helli',
''
}, '\n')}},
next_msg())
end)
end)
end)
describe('threadpool', function()
before_each(clear)
it('is_thread', function()
eq(false, exec_lua('return vim.is_thread()'))
local code = [[
local work_fn = function()
return vim.is_thread()
end
local after_work_fn = function(ret)
vim.rpcnotify(1, 'result', ret)
end
local work = vim.loop.new_work(work_fn, after_work_fn)
work:queue()
]]
exec_lua(code)
eq({'notification', 'result', {true}}, next_msg())
end)
it('with invalid argument', function()
local code = [[
local work = vim.loop.new_thread(function() end, function() end)
work:queue({})
]]
eq([[Error executing lua: [string "<nvim>"]:0: Error: thread arg not support type 'function' at 1]],
pcall_err(exec_lua, code))
end)
it('with invalid return value', function()
local screen = Screen.new(50, 10)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {bold = true, reverse = true},
[3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {bold = true},
})
local code = [[
local work = vim.loop.new_work(function() return {} end, function() end)
work:queue()
]]
exec_lua(code)
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2: }|
{3:Error in luv thread:} |
{3:Error: thread arg not support type 'table' at 1} |
{4:Press ENTER or type command to continue}^ |
]])
end)
describe('vim.*', function()
before_each(function()
clear()
local code = [[
Threadpool_Test = {}
Threadpool_Test.work_fn = function(work_fn_str, args)
local decoded_args = vim.mpack.decode(args)
return assert(loadstring(work_fn_str))(decoded_args)
end
function Threadpool_Test:do_test()
local work =
vim.loop.new_work(self.work_fn, self.after_work)
work:queue(self.work_fn_str, self.args)
end
Threadpool_Test.new = function(work_fn, after_work, ...)
self = {}
setmetatable(self, {__index = Threadpool_Test})
self.args = vim.mpack.encode({...})
self.work_fn_str = string.dump(work_fn)
self.after_work = after_work
return self
end
]]
exec_lua(code)
end)
it('loop', function()
local code = [[
local work_fn = function()
return vim.loop.version()
end
local after_work_fn = function(ret)
vim.rpcnotify(1, ret)
end
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
threadpool_test:do_test()
]]
exec_lua(code)
local msg = next_msg()
eq(msg[1], 'notification')
assert(tonumber(msg[2]) >= 72961)
end)
it('mpack', function()
local code = [[
local work_fn = function()
local var = vim.mpack.encode({33, vim.NIL, 'text'})
return var
end
local after_work_fn = function(ret)
vim.rpcnotify(1, 'result', vim.mpack.decode(ret))
end
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
threadpool_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('json', function()
local code = [[
local work_fn = function()
local var = vim.json.encode({33, vim.NIL, 'text'})
return var
end
local after_work_fn = function(ret)
vim.rpcnotify(1, 'result', vim.json.decode(ret))
end
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
threadpool_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('work', function()
local code = [[
local work_fn = function()
return vim.diff('Hello\n', 'Helli\n')
end
local after_work_fn = function(ret)
vim.rpcnotify(1, 'result', ret)
end
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
threadpool_test:do_test()
]]
exec_lua(code)
eq({'notification', 'result',
{table.concat({
'@@ -1 +1 @@',
'-Hello',
'+Helli',
''
}, '\n')}},
next_msg())
end)
end)
end)