Merge pull request #17386 from bfredl/neothread

support threads in lua
This commit is contained in:
bfredl 2022-02-27 09:41:02 +01:00 committed by GitHub
commit 7dd2b0b79a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1006 additions and 203 deletions

View File

@ -568,6 +568,26 @@ Example: TCP echo-server *tcp-server*
end)
print('TCP echo-server listening on port: '..server:getsockname().port)
Multithreading *lua-loop-threading*
Plugins can perform work in separate (os-level) threads using the threading
APIs in luv, for instance `vim.loop.new_thread`. Note that every thread
gets its own separate lua interpreter state, with no access to lua globals
in the main thread. Neither can the state of the editor (buffers, windows,
etc) be directly accessed from threads.
A subset of the `vim.*` API is available in threads. This includes:
- `vim.loop` with a separate event loop per thread.
- `vim.mpack` and `vim.json` (useful for serializing messages between threads)
- `require` in threads can use lua packages from the global |lua-package-path|
- `print()` and `vim.inspect`
- `vim.diff`
- most utility functions in `vim.*` for working with pure lua values
like `vim.split`, `vim.tbl_*`, `vim.list_*`, and so on.
- `vim.is_thread()` returns true from a non-main thread.
------------------------------------------------------------------------------
VIM.HIGHLIGHT *lua-highlight*

View File

@ -0,0 +1,49 @@
-- 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
-- Insert vim._load_package after the preloader at position 2
table.insert(package.loaders, 2, 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 */
fpconv_init();
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_global_refs->empty_dict_ref);
if (lua_rawequal(lstate, -2, -1)) {
ret.type = kObjectTypeDictionary;
}
@ -316,7 +316,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
LuaRef table_ref = LUA_NOREF;
if (lua_getmetatable(lstate, -1)) {
lua_pop(lstate, 1);
table_ref = nlua_ref(lstate, -1);
table_ref = nlua_ref_global(lstate, -1);
}
const LuaTableProps table_props = nlua_traverse_table(lstate);
@ -389,7 +389,7 @@ nlua_pop_typval_table_processing_end:
}
case LUA_TFUNCTION: {
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
state->lua_callable.func_ref = nlua_ref(lstate, -1);
state->lua_callable.func_ref = nlua_ref_global(lstate, -1);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free,
@ -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_global_refs->nil_ref);
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_global_refs->nil_ref); \
} \
} 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_global_refs->empty_dict_ref); \
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_global_refs->empty_dict_ref);
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_global_refs->nil_ref);
}
break;
case kObjectTypeLuaRef: {
@ -1199,14 +1199,14 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
case LUA_TFUNCTION:
if (ref) {
*cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1));
*cur.obj = LUAREF_OBJ(nlua_ref_global(lstate, -1));
} else {
goto type_error;
}
break;
case LUA_TUSERDATA: {
nlua_pushref(lstate, nlua_nil_ref);
nlua_pushref(lstate, nlua_global_refs->nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {
@ -1240,7 +1240,7 @@ type_error:
LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
{
LuaRef rv = nlua_ref(lstate, -1);
LuaRef rv = nlua_ref_global(lstate, -1);
lua_pop(lstate, 1);
return rv;
}

View File

@ -45,6 +45,8 @@ 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;
@ -65,11 +67,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 +129,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");
semsg_multiline("Error executing luv callback:\n%s", 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 +168,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,12 +182,112 @@ 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];
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, cb);
nlua_unref(lstate, cb);
nlua_unref_global(lstate, cb);
if (nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
}
@ -184,7 +304,7 @@ static int nlua_schedule(lua_State *const lstate)
return lua_error(lstate);
}
LuaRef cb = nlua_ref(lstate, 1);
LuaRef cb = nlua_ref_global(lstate, 1);
multiqueue_put(main_loop.events, nlua_schedule_event,
1, (void *)(ptrdiff_t)cb);
@ -302,6 +422,152 @@ 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;
if (!is_thread) {
nlua_global_refs = ref_state;
}
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;
}
int nlua_get_global_ref_count(void)
{
return nlua_global_refs->ref_count;
}
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, ref_state, -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, ref_state, -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 +628,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 +703,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 +764,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_ref_state_t *ref_state = nlua_get_ref_state(lstate);
nlua_unref(lstate, ref_state, ref_state->nil_ref);
nlua_unref(lstate, ref_state, ref_state->empty_dict_ref);
#ifdef NLUA_TRACK_REFS
if (nlua_refcount) {
fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
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 +865,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 +885,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,40 +1080,53 @@ static int nlua_getenv(lua_State *lstate)
/// add the value to the registry
LuaRef nlua_ref(lua_State *lstate, int index)
/// The current implementation does not support calls from threads.
LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index)
{
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
}
return ref;
}
LuaRef nlua_ref_global(lua_State *lstate, int index)
{
return nlua_ref(lstate, nlua_global_refs, index);
}
/// remove the value from the registry
void nlua_unref(lua_State *lstate, LuaRef ref)
void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
{
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);
}
}
void nlua_unref_global(lua_State *lstate, LuaRef ref)
{
nlua_unref(lstate, nlua_global_refs, ref);
}
void api_free_luaref(LuaRef ref)
{
nlua_unref(global_lstate, ref);
nlua_unref_global(global_lstate, ref);
}
/// push a value referenced in the registry
@ -861,7 +1148,7 @@ LuaRef api_new_luaref(LuaRef original_ref)
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, original_ref);
LuaRef new_ref = nlua_ref(lstate, -1);
LuaRef new_ref = nlua_ref_global(lstate, -1);
lua_pop(lstate, 1);
return new_ref;
}
@ -1391,6 +1678,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)
@ -1407,7 +1701,7 @@ void nlua_CFunction_func_free(void *state)
lua_State *const lstate = global_lstate;
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
nlua_unref(lstate, funcstate->lua_callable.func_ref);
nlua_unref_global(lstate, funcstate->lua_callable.func_ref);
xfree(funcstate);
}
@ -1457,7 +1751,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
lua_pop(lstate, 2); // [table]
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
state->lua_callable.func_ref = nlua_ref(lstate, -1);
state->lua_callable.func_ref = nlua_ref_global(lstate, -1);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free, 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,10 +15,14 @@
// 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);
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;
#define set_api_error(s, err) \
do { \
@ -39,4 +44,7 @@ EXTERN int nlua_refcount INIT(= 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.h.generated.h"
#endif
EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL);
#endif // NVIM_LUA_EXECUTOR_H

View File

@ -471,43 +471,52 @@ 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)
{
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
// str_utfindex
lua_pushcfunction(lstate, &nlua_str_utfindex);
lua_setfield(lstate, -2, "str_utfindex");
// str_byteindex
lua_pushcfunction(lstate, &nlua_str_byteindex);
lua_setfield(lstate, -2, "str_byteindex");
// str_utf_pos
lua_pushcfunction(lstate, &nlua_str_utf_pos);
lua_setfield(lstate, -2, "str_utf_pos");
// str_utf_start
lua_pushcfunction(lstate, &nlua_str_utf_start);
lua_setfield(lstate, -2, "str_utf_start");
// str_utf_end
lua_pushcfunction(lstate, &nlua_str_utf_end);
lua_setfield(lstate, -2, "str_utf_end");
// regex
lua_pushcfunction(lstate, &nlua_regex);
lua_setfield(lstate, -2, "regex");
luaL_newmetatable(lstate, "nvim_regex");
luaL_register(lstate, NULL, regex_meta);
if (!is_thread) {
// TODO(bfredl): some of basic string functions should already be
// (or be easy to make) threadsafe
lua_pushvalue(lstate, -1); // [meta, meta]
lua_setfield(lstate, -2, "__index"); // [meta]
lua_pop(lstate, 1); // don't use metatable now
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
// str_utfindex
lua_pushcfunction(lstate, &nlua_str_utfindex);
lua_setfield(lstate, -2, "str_utfindex");
// str_byteindex
lua_pushcfunction(lstate, &nlua_str_byteindex);
lua_setfield(lstate, -2, "str_byteindex");
// str_utf_pos
lua_pushcfunction(lstate, &nlua_str_utf_pos);
lua_setfield(lstate, -2, "str_utf_pos");
// str_utf_start
lua_pushcfunction(lstate, &nlua_str_utf_start);
lua_setfield(lstate, -2, "str_utf_start");
// str_utf_end
lua_pushcfunction(lstate, &nlua_str_utf_end);
lua_setfield(lstate, -2, "str_utf_end");
// regex
lua_pushcfunction(lstate, &nlua_regex);
lua_setfield(lstate, -2, "regex");
luaL_newmetatable(lstate, "nvim_regex");
luaL_register(lstate, NULL, regex_meta);
// _getvar
lua_pushcfunction(lstate, &nlua_getvar);
lua_setfield(lstate, -2, "_getvar");
lua_pushvalue(lstate, -1); // [meta, meta]
lua_setfield(lstate, -2, "__index"); // [meta]
lua_pop(lstate, 1); // don't use metatable now
// _setvar
lua_pushcfunction(lstate, &nlua_setvar);
lua_setfield(lstate, -2, "_setvar");
// _getvar
lua_pushcfunction(lstate, &nlua_getvar);
lua_setfield(lstate, -2, "_getvar");
// _setvar
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);
@ -526,10 +535,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_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,27 @@ 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);
}
/// TODO(bfredl): some of these operations should
/// be possible to do the private libuv loop of the
/// thread, instead of contending the global fs loop
void fs_loop_lock(void)
{
uv_mutex_lock(&fs_loop_mutex);
}
void fs_loop_unlock(void)
{
uv_mutex_unlock(&fs_loop_mutex);
}
@ -98,9 +115,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 +758,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 +957,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 +986,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 +1049,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 +1069,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 +1078,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 +1195,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 +1204,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,13 @@
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_init(void)
{
uv_mutex_init(&runtime_search_path_mutex);
}
/// ":runtime [what] {name}"
void ex_runtime(exarg_T *eap)
@ -172,6 +179,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 +320,35 @@ 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, buf, sizeof buf);
runtime_search_path_unref(path, &ref);
return rv;
}
ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all)
{
// TODO(bfredl): avoid contention between multiple worker threads?
uv_mutex_lock(&runtime_search_path_mutex);
static char buf[MAXPATHL];
ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread,
buf, sizeof buf);
uv_mutex_unlock(&runtime_search_path_mutex);
return rv;
}
ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
RuntimeSearchPath path, char *buf, size_t buf_len)
{
ArrayOf(String) rv = ARRAY_DICT_INIT;
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 +358,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 +371,7 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
}
}
}
done:
runtime_search_path_unref(path, &ref);
return rv;
}
@ -569,6 +605,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
uv_mutex_lock(&runtime_search_path_mutex);
runtime_search_path_free(runtime_search_path_thread);
runtime_search_path_thread = copy_runtime_search_path(runtime_search_path);
uv_mutex_unlock(&runtime_search_path_mutex);
}
}

View File

@ -0,0 +1,386 @@
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()
exec_lua [[
local thread = vim.loop.new_thread(function()
error('Error in thread entry func')
end)
vim.loop.thread_join(thread)
]]
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()
exec_lua [[
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)
]]
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('works', function()
exec_lua [[
local thread = vim.loop.new_thread(function()
print('print in thread')
end)
vim.loop.thread_join(thread)
]]
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
print in thread |
]])
end)
end)
describe('vim.*', function()
before_each(function()
clear()
exec_lua [[
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
]]
end)
it('is_thread', function()
exec_lua [[
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()
]]
eq({'notification', 'result', {true}}, next_msg())
end)
it('loop', function()
exec_lua [[
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()
]]
local msg = next_msg()
eq(msg[1], 'notification')
assert(tonumber(msg[2]) >= 72961)
end)
it('mpack', function()
exec_lua [[
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()
]]
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('json', function()
exec_lua [[
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()
]]
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('diff', function()
exec_lua [[
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()
]]
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()]])
exec_lua [[
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()
]]
eq({'notification', 'result', {true}}, next_msg())
end)
it('with invalid argument', function()
local status = pcall_err(exec_lua, [[
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]],
status)
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},
})
exec_lua [[
local work = vim.loop.new_work(function() return {} end, function() end)
work:queue()
]]
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()
exec_lua [[
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
]]
end)
it('loop', function()
exec_lua [[
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()
]]
local msg = next_msg()
eq(msg[1], 'notification')
assert(tonumber(msg[2]) >= 72961)
end)
it('mpack', function()
exec_lua [[
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()
]]
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('json', function()
exec_lua [[
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()
]]
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
end)
it('work', function()
exec_lua [[
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()
]]
eq({'notification', 'result',
{table.concat({
'@@ -1 +1 @@',
'-Hello',
'+Helli',
''
}, '\n')}},
next_msg())
end)
end)
end)