Last active
April 1, 2025 16:29
-
-
Save duboisf/8ec9b8666cfe5da8e1dd1ffb65331e8c to your computer and use it in GitHub Desktop.
Example of running nvim coroutines sequentially and in parallel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Example of using coroutines with slow IO operations in Neovim. | |
-- It shows how to run coroutines sequentially and in "parallel". | |
-- The word "parallel" is in quotes because Lua coroutines are not actually | |
-- running in parallel, but the IO operations that they start are. | |
-- The coroutines are paused and resumed as the IO operations complete. | |
-- To run this example, put this file somewhere and do `:luafile path/to/coroutines.lua` in nvim. | |
local co = coroutine | |
vim.cmd "new" | |
Buf = vim.api.nvim_get_current_buf() | |
vim.bo.buftype = "nofile" | |
local function clear_buffer() | |
vim.api.nvim_buf_set_lines(Buf, 0, -1, false, {}) | |
end | |
local function append_lines_to_buffer(buf, lines) | |
vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines) | |
end | |
local function get_time() | |
-- Get time formatted as string | |
return os.date("%H:%M:%S") | |
end | |
---Log a message to the buffer. | |
---@param ... any | |
local function log(...) | |
local strings = vim.tbl_map(tostring, { ... }) | |
local line = get_time() .. " " .. table.concat(strings, " ") | |
append_lines_to_buffer(Buf, { line }) | |
end | |
clear_buffer() | |
vim.api.nvim_buf_set_lines(Buf, 0, -1, false, { get_time() .. " test.lua start" }) | |
---@async | |
---Simulates slow IO by sleeping for a given number of milliseconds. | |
---@param delay number: The number of milliseconds to sleep for. | |
---@param result string: The result of the slow IO operation. | |
---@return string result The result of the slow IO operation. | |
local function simulate_slow_io(delay, result) | |
local thread = co.running() | |
assert(thread, "This function must be called inside a coroutine.") | |
vim.defer_fn(function() | |
log("resuming", thread) | |
co.resume(thread) | |
end, delay) | |
log("pausing", thread, "for", delay, "ms") | |
co.yield() | |
return result | |
end | |
local function parallel() | |
local thread = co.running() | |
assert(thread, "parallel() must be called inside a coroutine.") | |
local result1, result2, result3 | |
co.wrap(function() | |
result1 = simulate_slow_io(3000, "parallel: slow io result1") | |
co.resume(thread) | |
end)() | |
co.wrap(function() | |
result2 = simulate_slow_io(1000, "parallel: slow io result2") | |
co.resume(thread) | |
end)() | |
co.wrap(function() | |
result3 = simulate_slow_io(5000, "parallel: slow io result3") | |
co.resume(thread) | |
end)() | |
-- yield 3 times as we started 3 coroutines that each yield once | |
-- when the slow IO operation is started and then resume the main coroutine | |
-- when the slow IO operation is done. | |
co.yield() | |
co.yield() | |
co.yield() | |
return result1, result2, result3 | |
end | |
local function sequential() | |
local thread = co.running() | |
assert(thread, "sequential() must be called inside a coroutine.") | |
local result1, result2, result3 | |
co.wrap(function() | |
result1 = simulate_slow_io(3000, "sequential: slow io result1") | |
result2 = simulate_slow_io(1000, "sequential: slow io result2") | |
result3 = simulate_slow_io(5000, "sequential: slow io result3") | |
co.resume(thread) | |
end)() | |
co.yield() | |
return result1, result2, result3 | |
end | |
co.wrap(function() | |
local start_time = os.time() | |
log("Runnning sequentially") | |
local r1, r2, r3 = sequential() | |
local total_time = os.time() - start_time | |
log("sequential: total time: ", total_time, "seconds, results:", r1, r2, r3) | |
start_time = os.time() | |
log("Runnning in parallel") | |
r1, r2, r3 = parallel() | |
total_time = os.time() - start_time | |
log("parallel: total time", total_time, "seconds, results", r1, r2, r3) | |
log("test.lua end") | |
end)() | |
-- output should look like this: | |
-- 12:21:35 test.lua start | |
-- 12:21:35 Runnning sequentially | |
-- 12:21:35 pausing thread: 0x75db2de6ad78 for 3000 ms | |
-- 12:21:38 resuming thread: 0x75db2de6ad78 | |
-- 12:21:38 pausing thread: 0x75db2de6ad78 for 1000 ms | |
-- 12:21:39 resuming thread: 0x75db2de6ad78 | |
-- 12:21:39 pausing thread: 0x75db2de6ad78 for 5000 ms | |
-- 12:21:44 resuming thread: 0x75db2de6ad78 | |
-- 12:21:44 sequential: total time: 9 seconds, results: sequential: slow io result1 sequential: slow io result2 sequential: slow io result3 | |
-- 12:21:44 Runnning in parallel | |
-- 12:21:44 pausing thread: 0x75db2ce46520 for 3000 ms | |
-- 12:21:44 pausing thread: 0x75db2e312840 for 1000 ms | |
-- 12:21:44 pausing thread: 0x75db2d26e5e0 for 5000 ms | |
-- 12:21:45 resuming thread: 0x75db2e312840 | |
-- 12:21:47 resuming thread: 0x75db2ce46520 | |
-- 12:21:49 resuming thread: 0x75db2d26e5e0 | |
-- 12:21:49 parallel: total time 5 seconds, results parallel: slow io result1 parallel: slow io result2 parallel: slow io result3 | |
-- 12:21:49 test.lua end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment