You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
4.0 KiB
105 lines
4.0 KiB
-- Proof-of-concept: how Lua coroutines interact with the tokio runtime, and
|
|
-- how the async os.sleep behaves in each setting.
|
|
--
|
|
-- Run with: cargo run -- lua/coroutines-demo.lua
|
|
|
|
local function banner(s)
|
|
print("\n=== " .. s .. " ===")
|
|
end
|
|
|
|
----------------------------------------------------------------------
|
|
banner("1. Pure Lua coroutines (no async involved)")
|
|
----------------------------------------------------------------------
|
|
-- Plain cooperative coroutines work exactly as in stock Lua: this is pure VM
|
|
-- machinery and never touches tokio.
|
|
local function counter(n)
|
|
for i = 1, n do
|
|
coroutine.yield(i * i)
|
|
end
|
|
return "done"
|
|
end
|
|
|
|
local co = coroutine.wrap(counter)
|
|
print("squares:", co(3), co(3), co(3)) -- 1 4 9
|
|
|
|
local raw = coroutine.create(counter)
|
|
print("status fresh:", coroutine.status(raw)) -- suspended
|
|
coroutine.resume(raw, 1)
|
|
coroutine.resume(raw, 1) -- runs to `return`
|
|
print("status after return:", coroutine.status(raw)) -- dead
|
|
|
|
----------------------------------------------------------------------
|
|
banner("2. Async os.sleep at the top level")
|
|
----------------------------------------------------------------------
|
|
-- The main chunk is itself run by mlua's async executor (exec_async), so an
|
|
-- async call here is driven correctly and really suspends on the tokio timer.
|
|
local t0 = os.microtime()
|
|
os.sleep(0.10)
|
|
print(string.format("slept ~0.10s, measured %.3fs", os.microtime() - t0))
|
|
|
|
----------------------------------------------------------------------
|
|
banner("3. GOTCHA: async os.sleep inside a *manually resumed* coroutine")
|
|
----------------------------------------------------------------------
|
|
-- mlua implements async functions by yielding a private sentinel to whatever is
|
|
-- driving the coroutine. mlua's own executor understands it; a plain
|
|
-- coroutine.resume / coroutine.wrap does NOT. So driving an async call yourself
|
|
-- does not actually wait -- the sleep is not performed by your resume loop.
|
|
local sleeper = coroutine.wrap(function()
|
|
os.sleep(0.50)
|
|
return "awoke"
|
|
end)
|
|
|
|
local t1 = os.microtime()
|
|
local first = sleeper() -- returns immediately with the sentinel
|
|
print(
|
|
string.format("first resume returned %s after only %.3fs (did NOT wait 0.5s)", tostring(first), os.microtime() - t1)
|
|
)
|
|
print("--> lesson: don't hand-drive coroutines that call async stdlib functions.")
|
|
|
|
----------------------------------------------------------------------
|
|
banner("4. Real concurrency: task.join drives coroutines on tokio")
|
|
----------------------------------------------------------------------
|
|
-- task.join runs each function as its own coroutine and lets the tokio reactor
|
|
-- interleave them. Three tasks that each sleep concurrently finish in ~the
|
|
-- longest single sleep, not the sum -- proof the sleeps overlap on one thread.
|
|
local function worker(name, secs)
|
|
return function()
|
|
print(string.format(" [%s] start", name))
|
|
os.sleep(secs)
|
|
print(string.format(" [%s] woke after %.2fs", name, secs))
|
|
return name .. ":" .. secs
|
|
end
|
|
end
|
|
|
|
local t2 = os.microtime()
|
|
local a, b, c = task.join(worker("slow", 0.30), worker("med", 0.20), worker("fast", 0.10))
|
|
local elapsed = os.microtime() - t2
|
|
print(string.format("results: %s, %s, %s", a, b, c))
|
|
print(string.format("wall time: %.3fs", elapsed))
|
|
print(
|
|
string.format(
|
|
"sequential would have been ~0.60s; concurrent ~0.30s => %s",
|
|
elapsed < 0.45 and "CONCURRENT (overlapped on tokio)" or "serialized?!"
|
|
)
|
|
)
|
|
|
|
----------------------------------------------------------------------
|
|
banner("5. Nested + return values")
|
|
----------------------------------------------------------------------
|
|
-- task.join itself yields, so it composes: a joined task can join again.
|
|
local outer = task.join(function()
|
|
local x, y = task.join(function()
|
|
os.sleep(0.05)
|
|
return 21
|
|
end, function()
|
|
os.sleep(0.05)
|
|
return 21
|
|
end)
|
|
return x + y
|
|
end, function()
|
|
os.sleep(0.10)
|
|
return "sibling"
|
|
end)
|
|
print("nested join result:", outer) -- 42
|
|
|
|
print("\nAll demos finished.")
|
|
|