Skip to content

ynishi/mlua-isle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mlua-isle

Thread-isolated Lua VM with cancellation and async bridge for mlua.

Problem

mlua::Lua is !Send — it cannot cross thread boundaries. This makes it difficult to use from async runtimes, UI threads, or any multi-threaded context.

mlua-isle solves this by confining the Lua VM to a dedicated thread and communicating via channels.

Features

  • Thread isolation — Lua VM runs on a dedicated thread; callers interact via a Send + Sync handle
  • Cancellation — long-running Lua code can be interrupted via CancelToken using a Lua debug hook
  • Sync API — blocking Isle handle with Task for non-blocking usage
  • Async API (optional, tokio feature) — AsyncIsle handle with Handle/Driver separation, bounded channel backpressure, and AsyncTask<T> which implements Future
  • Coroutine execution (optional, tokio feature) — cooperative multitasking via coroutine_eval / coroutine_call. Multiple Lua coroutines share the same VM and yield when awaiting async Rust functions
  • Connection pool (optional, pool feature) — IslePool manages multiple Isle instances with checkout/return semantics. Supports Cold (fresh VM) and Warm (reuse) strategies
  • Async connection pool (optional, pool + tokio features) — AsyncIslePool is the async counterpart of IslePool: checkout / try_checkout / checkout_timeout are async, idle wait uses tokio::sync::Notify, and each slot owns both an AsyncIsle handle and its AsyncIsleDriver so VMs can be joined on shutdown
  • Zero unsafe in user code — both Isle and AsyncIsle are safe to share across threads

Architecture

Sync (Isle)

┌─────────────────┐  std mpsc  ┌──────────────────┐
│  caller thread   │──────────►│  Lua thread       │
│                  │           │  (mlua confined)   │
│  Isle handle     │◄──────────│                    │
│                  │  oneshot   │  Lua VM + hook    │
└─────────────────┘            └──────────────────┘

Async (AsyncIsle, requires tokio feature)

┌──────────────────┐                ┌──────────────────┐
│  tokio tasks      │  tokio mpsc   │  Lua thread       │
│                   │──────────────►│  (mlua confined)   │
│  AsyncIsle handle │  (bounded,    │                    │
│  (Clone, no Arc)  │  backpressure)│  Lua VM + hook    │
│                   │◄──────────────│                    │
│                   │   oneshot     │                    │
├──────────────────┤                │                    │
│  AsyncIsleDriver  │───done_tx────►│                    │
│  (lifecycle owner)│               └──────────────────┘
└──────────────────┘
  • Handle (AsyncIsle) — lightweight, cloneable. Share across tasks without Arc.
  • Driver (AsyncIsleDriver) — sole lifecycle owner. Call shutdown().await for clean thread join, or drop to let the channel-close mechanism terminate the thread naturally.

Usage

Add to your Cargo.toml:

[dependencies]
mlua-isle = "0.4"

# For async support (includes coroutine execution):
# mlua-isle = { version = "0.4", features = ["tokio"] }

# For connection pool:
# mlua-isle = { version = "0.4", features = ["pool"] }

# Both:
# mlua-isle = { version = "0.4", features = ["tokio", "pool"] }

Sync API

use mlua_isle::Isle;

let isle = Isle::spawn(|lua| {
    lua.globals().set("greeting", "hello")?;
    Ok(())
}).unwrap();

let result: String = isle.eval("return greeting").unwrap();
assert_eq!(result, "hello");

isle.shutdown().unwrap();

Async API

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;

let (isle, driver) = AsyncIsle::spawn(|lua| {
    lua.globals().set("greeting", "hello")?;
    Ok(())
}).await?;

// Clone freely — no Arc needed.
let isle2 = isle.clone();

let result: String = isle.eval("return greeting").await?;
assert_eq!(result, "hello");

driver.shutdown().await?;
# Ok(())
# }

Coroutine execution (async)

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;

let (isle, driver) = AsyncIsle::spawn(|lua| {
    // Register an async Rust function — coroutines yield here
    lua.globals().set("sleep_ms", lua.create_async_function(|_, ms: u64| async move {
        tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
        Ok(())
    })?)?;
    Ok(())
}).await?;

// Multiple coroutines share the same VM cooperatively
let t1 = isle.spawn_coroutine_eval("sleep_ms(10) return 'a'");
let t2 = isle.spawn_coroutine_eval("sleep_ms(10) return 'b'");

let (r1, r2) = tokio::join!(t1, t2);
assert!(r1.is_ok());
assert!(r2.is_ok());

driver.shutdown().await?;
# Ok(())
# }

Connection pool

use mlua_isle::{IslePool, PoolConfig, PoolStrategy};

let pool = IslePool::new(
    |lua| {
        lua.globals().set("greeting", "hello")?;
        Ok(())
    },
    PoolConfig {
        max_size: 4,
        strategy: PoolStrategy::Warm,
    },
).unwrap();

{
    let isle = pool.checkout().unwrap();
    let result: String = isle.eval("return greeting").unwrap();
    assert_eq!(result, "hello");
} // isle returned to pool automatically

pool.shutdown();

Cancellation (sync)

use mlua_isle::Isle;
use std::time::Duration;
use std::thread;

let isle = Isle::spawn(|_| Ok(())).unwrap();

let task = isle.spawn_eval("while true do end");

thread::sleep(Duration::from_millis(50));
task.cancel();

let result = task.wait();
assert!(result.is_err()); // IsleError::Cancelled

Cancellation (async)

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;
use std::time::Duration;

let (isle, driver) = AsyncIsle::spawn(|_lua| Ok(())).await?;
let task = isle.spawn_eval("while true do end");

let token = task.cancel_token().clone();
tokio::spawn(async move {
    tokio::time::sleep(Duration::from_millis(100)).await;
    token.cancel();
});

let result = task.await; // Err(Cancelled)
assert!(result.is_err());
driver.shutdown().await?;
# Ok(())
# }

API

Sync (Isle)

Method Description
Isle::spawn(init) Create a Lua VM on a dedicated thread
isle.eval(code) Evaluate a Lua chunk (blocking)
isle.call(func, args) Call a global Lua function (blocking)
isle.exec(closure) Run an arbitrary closure on the Lua thread
isle.spawn_eval(code) Non-blocking eval, returns a Task
isle.spawn_call(func, args) Non-blocking call, returns a Task
isle.spawn_exec(closure) Non-blocking exec, returns a Task
isle.shutdown() Graceful shutdown and thread join
task.wait() Block until the task completes
task.cancel() Cancel the running task

Async (AsyncIsle, tokio feature)

Method Description
AsyncIsle::spawn(init) Create a Lua VM, returns (AsyncIsle, AsyncIsleDriver)
AsyncIsle::builder() Configure channel capacity / thread name
isle.eval(code) Evaluate a Lua chunk (async, exclusive)
isle.call(func, args) Call a global Lua function (async, exclusive)
isle.exec(closure) Run a closure on the Lua thread (async, exclusive)
isle.coroutine_eval(code) Evaluate as a cooperative coroutine
isle.coroutine_call(func, args) Call a function as a cooperative coroutine
isle.spawn_eval(code) Returns a cancellable AsyncTask
isle.spawn_call(func, args) Returns a cancellable AsyncTask
isle.spawn_exec(closure) Returns a cancellable AsyncTask
isle.spawn_coroutine_eval(code) Coroutine eval, returns AsyncTask
isle.spawn_coroutine_call(func, args) Coroutine call, returns AsyncTask
driver.shutdown().await Graceful shutdown (drains pending coroutines)
task.cancel() Cancel the running task
task.cancel_token() Access the CancelToken for sharing

Pool (IslePool, pool feature)

Method Description
IslePool::new(factory, config) Create a pool with factory closure
pool.checkout() Checkout an Isle (blocks until available)
pool.try_checkout() Non-blocking checkout, returns None at capacity
pool.checkout_timeout(dur) Checkout with timeout
pool.active() Number of currently checked-out Isles
pool.idle() Number of idle Isles
pool.shutdown() Shut down all idle Isles
pooled.kill() Mark Isle for disposal on drop

Minimum Supported Rust Version

Rust 1.77 or later.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

No description, website, or topics provided.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages