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 + Synchandle - Cancellation — long-running Lua code can be interrupted via
CancelTokenusing a Lua debug hook - Sync API — blocking
Islehandle withTaskfor non-blocking usage - Async API (optional,
tokiofeature) —AsyncIslehandle with Handle/Driver separation, bounded channel backpressure, andAsyncTask<T>which implementsFuture - Zero unsafe in user code — both
IsleandAsyncIsleare 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 withoutArc. - Driver (
AsyncIsleDriver) — sole lifecycle owner. Callshutdown().awaitfor clean thread join, or drop to let the channel-close mechanism terminate the thread naturally.
Usage
Add to your Cargo.toml:
[]
= "0.3"
# For async support:
# mlua-isle = { version = "0.3", features = ["tokio"] }
Sync API
use Isle;
let isle = spawn.unwrap;
let result: String = isle.eval.unwrap;
assert_eq!;
isle.shutdown.unwrap;
Async API
#
# async
Cancellation (sync)
use Isle;
use Duration;
use thread;
let isle = spawn.unwrap;
let task = isle.spawn_eval;
sleep;
task.cancel;
let result = task.wait;
assert!; // IsleError::Cancelled
Cancellation (async)
#
# async
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) |
isle.call(func, args) |
Call a global Lua function (async) |
isle.exec(closure) |
Run a closure on the Lua thread (async) |
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 |
driver.shutdown().await |
Graceful shutdown and thread join |
task.cancel() |
Cancel the running task |
task.cancel_token() |
Access the CancelToken for sharing |
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.