aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbfredl <bjorn.linse@gmail.com>2022-02-26 11:03:39 +0100
committerbfredl <bjorn.linse@gmail.com>2022-02-26 15:00:13 +0100
commit850b3e19c9fc8d84d960e6932a9ad4f0bcad2a8e (patch)
treee39bc72d137cd8a0863881b675fe83df6969b047
parentacf38245d8961125f02d4c4168053e0d83dbc6df (diff)
downloadrneovim-850b3e19c9fc8d84d960e6932a9ad4f0bcad2a8e.tar.gz
rneovim-850b3e19c9fc8d84d960e6932a9ad4f0bcad2a8e.tar.bz2
rneovim-850b3e19c9fc8d84d960e6932a9ad4f0bcad2a8e.zip
refactor(lua): cleanup and docs for threads
-rw-r--r--runtime/doc/lua.txt20
-rw-r--r--runtime/lua/vim/_load_package.lua3
-rw-r--r--src/nvim/lua/stdlib.c3
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/os/fs.c3
-rw-r--r--src/nvim/runtime.c33
-rw-r--r--test/functional/lua/thread_spec.lua93
7 files changed, 79 insertions, 78 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 355c31090e..4ea78c2426 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -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*
diff --git a/runtime/lua/vim/_load_package.lua b/runtime/lua/vim/_load_package.lua
index 3e346fb3f6..525f603438 100644
--- a/runtime/lua/vim/_load_package.lua
+++ b/runtime/lua/vim/_load_package.lua
@@ -45,4 +45,5 @@ function vim._load_package(name)
return nil
end
-table.insert(package.loaders, 1, vim._load_package)
+-- Insert vim._load_package after the preloader at position 2
+table.insert(package.loaders, 2, vim._load_package)
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 6a2aef6683..c2ce899a74 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -474,6 +474,9 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
{
if (!is_thread) {
+ // TODO(bfredl): some of basic string functions should already be
+ // (or be easy to make) threadsafe
+
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
diff --git a/src/nvim/main.c b/src/nvim/main.c
index dfd5e5dbe0..ec64e407b2 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -157,7 +157,7 @@ void early_init(mparm_T *paramp)
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
- runtime_search_path_init();
+ runtime_init();
highlight_init();
#ifdef WIN32
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index cca76e175d..daf974ee74 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -64,6 +64,9 @@ void fs_init(void)
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);
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 4497e2b055..1ec3e40abe 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -27,21 +27,11 @@ 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)
+void runtime_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)
{
@@ -330,8 +320,9 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
{
int ref;
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
+ static char buf[MAXPATHL];
- ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path);
+ ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path, buf, sizeof buf);
runtime_search_path_unref(path, &ref);
return rv;
@@ -339,18 +330,19 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
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();
+ // 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)
+ RuntimeSearchPath path, char *buf, size_t buf_len)
{
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) {
@@ -380,7 +372,6 @@ ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
}
}
done:
- xfree(buf);
return rv;
}
@@ -614,10 +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
- runtime_search_path_lock();
+ 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);
- runtime_search_path_unlock();
+ uv_mutex_unlock(&runtime_search_path_mutex);
}
}
diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua
index a60f57a860..2e0ab7bdff 100644
--- a/test/functional/lua/thread_spec.lua
+++ b/test/functional/lua/thread_spec.lua
@@ -26,13 +26,12 @@ describe('thread', function()
end)
it('entry func is executed in protected mode', function()
- local code = [[
+ exec_lua [[
local thread = vim.loop.new_thread(function()
error('Error in thread entry func')
end)
vim.loop.thread_join(thread)
]]
- exec_lua(code)
screen:expect([[
|
@@ -51,7 +50,7 @@ describe('thread', function()
end)
it('callback is executed in protected mode', function()
- local code = [[
+ exec_lua [[
local thread = vim.loop.new_thread(function()
local timer = vim.loop.new_timer()
local function ontimeout()
@@ -64,7 +63,6 @@ describe('thread', function()
end)
vim.loop.thread_join(thread)
]]
- exec_lua(code)
screen:expect([[
|
@@ -83,35 +81,33 @@ describe('thread', function()
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 |
- ]])
+ 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()
- local code = [[
+ exec_lua [[
Thread_Test = {}
Thread_Test.entry_func = function(async, entry_str, args)
@@ -140,11 +136,10 @@ describe('thread', function()
return self
end
]]
- exec_lua(code)
end)
it('is_thread', function()
- local code = [[
+ exec_lua [[
local entry = function(async)
async:send(vim.is_thread())
end
@@ -154,13 +149,12 @@ describe('thread', function()
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 = [[
+ exec_lua [[
local entry = function(async)
async:send(vim.loop.version())
end
@@ -170,7 +164,6 @@ describe('thread', function()
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
- exec_lua(code)
local msg = next_msg()
eq(msg[1], 'notification')
@@ -178,7 +171,7 @@ describe('thread', function()
end)
it('mpack', function()
- local code = [[
+ exec_lua [[
local entry = function(async)
async:send(vim.mpack.encode({33, vim.NIL, 'text'}))
end
@@ -188,13 +181,12 @@ describe('thread', function()
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 = [[
+ exec_lua [[
local entry = function(async)
async:send(vim.json.encode({33, vim.NIL, 'text'}))
end
@@ -204,13 +196,12 @@ describe('thread', function()
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 = [[
+ exec_lua [[
local entry = function(async)
async:send(vim.diff('Hello\n', 'Helli\n'))
end
@@ -220,7 +211,6 @@ describe('thread', function()
local thread_test = Thread_Test.new(entry, on_async)
thread_test:do_test()
]]
- exec_lua(code)
eq({'notification', 'result',
{table.concat({
@@ -238,9 +228,9 @@ describe('threadpool', function()
before_each(clear)
it('is_thread', function()
- eq(false, exec_lua('return vim.is_thread()'))
+ eq(false, exec_lua [[return vim.is_thread()]])
- local code = [[
+ exec_lua [[
local work_fn = function()
return vim.is_thread()
end
@@ -250,19 +240,18 @@ describe('threadpool', function()
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 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]],
- pcall_err(exec_lua, code))
+ status)
end)
it('with invalid return value', function()
@@ -276,11 +265,10 @@ describe('threadpool', function()
[5] = {bold = true},
})
- local code = [[
+ exec_lua [[
local work = vim.loop.new_work(function() return {} end, function() end)
work:queue()
]]
- exec_lua(code)
screen:expect([[
|
@@ -299,7 +287,7 @@ describe('threadpool', function()
describe('vim.*', function()
before_each(function()
clear()
- local code = [[
+ exec_lua [[
Threadpool_Test = {}
Threadpool_Test.work_fn = function(work_fn_str, args)
@@ -322,11 +310,10 @@ describe('threadpool', function()
return self
end
]]
- exec_lua(code)
end)
it('loop', function()
- local code = [[
+ exec_lua [[
local work_fn = function()
return vim.loop.version()
end
@@ -336,7 +323,6 @@ describe('threadpool', function()
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')
@@ -344,7 +330,7 @@ describe('threadpool', function()
end)
it('mpack', function()
- local code = [[
+ exec_lua [[
local work_fn = function()
local var = vim.mpack.encode({33, vim.NIL, 'text'})
return var
@@ -355,13 +341,12 @@ describe('threadpool', function()
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 = [[
+ exec_lua [[
local work_fn = function()
local var = vim.json.encode({33, vim.NIL, 'text'})
return var
@@ -372,13 +357,12 @@ describe('threadpool', function()
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 = [[
+ exec_lua [[
local work_fn = function()
return vim.diff('Hello\n', 'Helli\n')
end
@@ -388,7 +372,6 @@ describe('threadpool', function()
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
threadpool_test:do_test()
]]
- exec_lua(code)
eq({'notification', 'result',
{table.concat({