Guarantee that wait/sleep functions always yield in both task library and scheduler examples

This commit is contained in:
Filip Tibell 2025-04-23 19:18:08 +02:00
parent 8ffbb328f3
commit c091b05f6c
No known key found for this signature in database
4 changed files with 56 additions and 2 deletions

View file

@ -5,7 +5,10 @@ use std::time::Duration;
use mlua::prelude::*;
use mlua_luau_scheduler::Functions;
use tokio::time::{sleep, Instant};
use tokio::{
task::yield_now,
time::{sleep, Instant},
};
use lune_utils::TableBuilder;
@ -49,7 +52,14 @@ return defer(function(...)
end, ...)
";
async fn wait(_: Lua, secs: Option<f64>) -> LuaResult<f64> {
async fn wait(lua: Lua, secs: Option<f64>) -> LuaResult<f64> {
// NOTE: We must guarantee that the task.wait API always yields
// from a lua perspective, even if sleep/timer completes instantly
yield_now().await;
wait_inner(lua, secs).await
}
async fn wait_inner(_: Lua, secs: Option<f64>) -> LuaResult<f64> {
let duration = Duration::from_secs_f64(secs.unwrap_or_default());
let before = Instant::now();

View file

@ -4,6 +4,7 @@
use std::time::{Duration, Instant};
use async_io::{block_on, Timer};
use futures_lite::future::yield_now;
use mlua::prelude::*;
use mlua_luau_scheduler::Scheduler;
@ -22,6 +23,10 @@ pub fn main() -> LuaResult<()> {
lua.globals().set(
"sleep",
lua.create_async_function(|_, duration: f64| async move {
// Guarantee that the coroutine that calls this sleep function
// always yields, even if the timer completes without doing so
yield_now().await;
// We may then sleep as normal
let before = Instant::now();
let after = Timer::after(Duration::from_secs_f64(duration)).await;
Ok((after - before).as_secs_f64())

View file

@ -4,6 +4,7 @@
use std::time::Duration;
use async_io::{block_on, Timer};
use futures_lite::future::yield_now;
use mlua::prelude::*;
use mlua_luau_scheduler::{Functions, Scheduler};
@ -28,6 +29,9 @@ pub fn main() -> LuaResult<()> {
lua.globals().set(
"sleep",
lua.create_async_function(|_, ()| async move {
// Guarantee that the coroutine that calls this sleep function
// always yields, even if the timer completes without doing so
yield_now().await;
// Obviously we can't sleep for a single nanosecond since
// this uses OS scheduling under the hood, but we can try
Timer::after(ONE_NANOSECOND).await;

35
test.luau Normal file
View file

@ -0,0 +1,35 @@
local task = require("@lune/task")
local started = os.clock()
local amount = 400000
local batches = 5
local per_batch = amount / batches
for current = 1, batches do
local thread = coroutine.running()
print(`Batch {current} / {batches}`)
for i = 1, per_batch do
--print("Spawning thread #" .. i)
task.spawn(function()
print("[BEFORE] Thread number", i)
task.wait(0.1)
--_TEST_ASYNC_WORK(0.1)
print("[AFTER] Thread number", i)
if i == per_batch then
print("Last thread in batch #" .. current)
assert(
coroutine.status(thread) == "suspended",
`Thread {i} has status {coroutine.status(thread)}`
)
task.spawn(thread)
end
end)
end
coroutine.yield()
end
local took = os.clock() - started
print(`Spawned {amount} sleeping threads in {took}s`)