# mlua-isle
Thread-isolated Lua VM with cancellation and async bridge for [mlua](https://crates.io/crates/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`
- **Zero unsafe in user code** — both `Isle` and `AsyncIsle` are safe to
share across threads
## Architecture
### Sync (`Isle`)
```text
┌─────────────────┐ std mpsc ┌──────────────────┐
│ caller thread │──────────►│ Lua thread │
│ │ │ (mlua confined) │
│ Isle handle │◄──────────│ │
│ │ oneshot │ Lua VM + hook │
└─────────────────┘ └──────────────────┘
```
### Async (`AsyncIsle`, requires `tokio` feature)
```text
┌──────────────────┐ ┌──────────────────┐
│ 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`:
```toml
[dependencies]
mlua-isle = "0.3"
# For async support:
# mlua-isle = { version = "0.3", features = ["tokio"] }
```
### Sync API
```rust
use mlua_isle::Isle;
Ok(())
}).unwrap();
let result: String = isle.eval("return greeting").unwrap();
assert_eq!(result, "hello");
isle.shutdown().unwrap();
```
### Async API
```rust
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;
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(())
# }
```
### Cancellation (sync)
```rust
use mlua_isle::Isle;
use std::time::Duration;
use std::thread;
thread::sleep(Duration::from_millis(50));
task.cancel();
let result = task.wait();
assert!(result.is_err()); // IsleError::Cancelled
```
### Cancellation (async)
```rust
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;
use std::time::Duration;
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`)
| `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)
| `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
- [MIT license](LICENSE-MIT)
- [Apache License, Version 2.0](LICENSE-APACHE)
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.