Skip to main content

luaur_rt/
thread.rs

1//! The [`Thread`] handle and [`ThreadStatus`]. Mirrors `mlua::Thread` /
2//! `mlua::ThreadStatus`.
3//!
4//! A thread is a Luau coroutine. It is created from a [`Function`] via
5//! [`Lua::create_thread`] (or surfaces from `coroutine.create(...)` evaluated
6//! in Lua) and driven with [`Thread::resume`].
7//!
8//! ## Implementation
9//!
10//! The thread is a first-class Lua value, so the handle holds a registry
11//! reference (like every other handle) keeping the coroutine alive. We also
12//! cache the raw `*mut lua_State` of the coroutine for the resume/xmove dance.
13//!
14//! `resume` mirrors mlua: push the args onto the *parent* state, `lua_xmove`
15//! them to the coroutine, `lua_resume(co, parent, nargs)`, then `lua_xmove` the
16//! results back and convert them. Status is derived from `lua_status` +
17//! `lua_costatus`, matching mlua's `Resumable`/`Running`/`Normal`/`Finished`/
18//! `Error` mapping.
19
20use crate::error::{Error, Result};
21use crate::function::Function;
22use crate::multi::MultiValue;
23use crate::state::{Lua, LuaRef};
24use crate::sync::{NotSync, XRc, NOT_SYNC};
25use crate::sys::*;
26use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
27
28/// Status of a Lua thread (coroutine). Mirrors `mlua::ThreadStatus`.
29#[derive(Debug, Copy, Clone, Eq, PartialEq)]
30pub enum ThreadStatus {
31    /// The thread was just created or is suspended (yielded) and can be resumed.
32    Resumable,
33    /// The thread is currently running.
34    Running,
35    /// The thread is active but not running (it resumed another thread).
36    Normal,
37    /// The thread has finished executing.
38    Finished,
39    /// The thread raised a Lua error during execution.
40    Error,
41}
42
43/// The raw outcome of one async resume. See [`Thread::resume_for_async`].
44#[cfg(feature = "async")]
45pub(crate) enum AsyncResume {
46    /// The coroutine yielded the internal "future pending" marker.
47    Pending,
48    /// The coroutine yielded values via `coroutine.yield` (a Stream item).
49    Yielded(MultiValue),
50    /// The coroutine finished, returning these values.
51    Returned(MultiValue),
52}
53
54/// A handle to a Lua thread (coroutine). Mirrors `mlua::Thread`.
55///
56/// Under the `send` feature it is `Send` but never `Sync` — see
57/// [`crate::sync::NotSync`].
58#[derive(Clone)]
59pub struct Thread {
60    pub(crate) reference: XRc<LuaRef>,
61    /// The raw coroutine state pointer (cached from the referenced value).
62    pub(crate) thread_state: *mut lua_State,
63    pub(crate) _not_sync: NotSync,
64}
65
66// `Thread` caches a raw `*mut lua_State` (the coroutine), which is `!Send` by
67// default. Under the move-only `send` contract it is sound to move a `Thread`
68// to another thread (the cached pointer stays valid; the VM is single-threaded
69// in use). `!Sync` is preserved by the `NotSync` marker.
70#[cfg(feature = "send")]
71unsafe impl Send for Thread {}
72
73impl Thread {
74    /// Build a [`Thread`] from a registry ref to a thread value. Caches the
75    /// coroutine's raw state via `lua_tothread`.
76    pub(crate) fn from_ref(reference: LuaRef) -> Thread {
77        let state = reference.state();
78        let thread_state = unsafe {
79            reference.push();
80            let ts = lua_tothread(state, -1);
81            lua_pop(state, 1);
82            ts
83        };
84        Thread {
85            reference: XRc::new(reference),
86            thread_state,
87            _not_sync: NOT_SYNC,
88        }
89    }
90
91    pub(crate) unsafe fn push_to_stack(&self) {
92        self.reference.push();
93    }
94
95    /// The owning [`Lua`].
96    pub fn lua(&self) -> Lua {
97        self.reference.lua()
98    }
99
100    /// The raw coroutine state pointer. Mirrors `mlua::Thread::state`.
101    pub fn state(&self) -> *mut lua_State {
102        self.thread_state
103    }
104
105    /// Resume the coroutine, passing `args` and converting its yielded/returned
106    /// values to `R`. Mirrors `mlua::Thread::resume`.
107    ///
108    /// Returns [`Error::CoroutineUnresumable`] if the thread has finished,
109    /// errored, or is otherwise not resumable.
110    pub fn resume<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
111        let lua = self.lua();
112        // Convert the args first so a failing `IntoLua` (e.g. a bad argument)
113        // surfaces *before* we touch any Lua stack — matching mlua.
114        let args: MultiValue = args.into_lua_multi(&lua)?;
115
116        if !matches!(self.status(), ThreadStatus::Resumable) {
117            return Err(Error::CoroutineUnresumable);
118        }
119
120        let parent = lua.state();
121        let co = self.thread_state;
122        unsafe {
123            let nargs = args.len() as c_int;
124            if lua_checkstack(co, nargs.saturating_add(2)) == 0 {
125                return Err(Error::RuntimeError(
126                    "stack overflow: too many arguments to coroutine resume".to_string(),
127                ));
128            }
129            // Push args onto the parent, then move them to the coroutine.
130            for v in args.iter() {
131                lua.push_value(v)?;
132            }
133            if nargs > 0 {
134                lua_xmove(parent, co, nargs);
135            }
136
137            self.resume_inner::<R>(&lua, nargs)
138        }
139    }
140
141    /// Resume the coroutine, immediately raising `error` inside it.
142    /// Mirrors `mlua::Thread::resume_error` (a Luau extension).
143    pub fn resume_error<R: FromLuaMulti>(&self, error: impl IntoLua) -> Result<R> {
144        let lua = self.lua();
145        let err_value = error.into_lua(&lua)?;
146
147        if !matches!(self.status(), ThreadStatus::Resumable) {
148            return Err(Error::CoroutineUnresumable);
149        }
150
151        let parent = lua.state();
152        let co = self.thread_state;
153        unsafe {
154            if lua_checkstack(co, 2) == 0 {
155                return Err(Error::RuntimeError("stack overflow".to_string()));
156            }
157            lua.push_value(&err_value)?;
158            lua_xmove(parent, co, 1);
159            // lua_resumeerror does the resume-with-error and returns the status.
160            let status = lua_resumeerror(co, parent);
161            self.finish_resume::<R>(&lua, status)
162        }
163    }
164
165    /// Run `lua_resume` and collect/convert the results. Expects `nargs` already
166    /// moved onto the coroutine stack.
167    unsafe fn resume_inner<R: FromLuaMulti>(&self, lua: &Lua, nargs: c_int) -> Result<R> {
168        let parent = lua.state();
169        let co = self.thread_state;
170        let status = unsafe { lua_resume(co, parent, nargs) };
171        unsafe { self.finish_resume::<R>(lua, status) }
172    }
173
174    /// Common tail of `resume`/`resume_error`: inspect the status, move results
175    /// back to the parent, and convert.
176    unsafe fn finish_resume<R: FromLuaMulti>(&self, lua: &Lua, status: c_int) -> Result<R> {
177        let parent = lua.state();
178        let co = self.thread_state;
179        unsafe {
180            if status != status::OK && status != status::YIELD && status != status::BREAK {
181                // Error: the coroutine left the error object on its own stack.
182                let nres = lua_gettop(co);
183                if nres > 0 {
184                    lua_xmove(co, parent, nres);
185                }
186                let err = lua.pop_error(status);
187                // Clear any extra values the coroutine left on the parent.
188                return Err(err);
189            }
190            // `LUA_BREAK` is an interrupt-driven yield: the coroutine produced
191            // no values and its entire register window is still *live* (it must
192            // continue from the break point on the next resume). We must NOT
193            // touch its stack — moving any values off would strip live
194            // registers and corrupt the re-entry. Return an empty result.
195            if status == status::BREAK {
196                return R::from_lua_multi(MultiValue::with_capacity(0), lua);
197            }
198            // Success/yield: the produced values sit on the coroutine stack.
199            let nres = lua_gettop(co);
200            if lua_checkstack(parent, nres.saturating_add(1)) == 0 {
201                return Err(Error::RuntimeError("stack overflow".to_string()));
202            }
203            let base = lua_gettop(parent);
204            if nres > 0 {
205                lua_xmove(co, parent, nres);
206            }
207            let mut results = MultiValue::with_capacity(nres.max(0) as usize);
208            for i in 0..nres {
209                results.push_back(lua.value_from_stack(base + 1 + i)?);
210            }
211            lua_settop(parent, base);
212            R::from_lua_multi(results, lua)
213        }
214    }
215
216    /// Low-level resume used by the async driver. Pushes `args` to the
217    /// coroutine, resumes it once, and returns the raw outcome:
218    ///
219    /// * `Err(e)` — the coroutine raised an error.
220    /// * `Ok(AsyncResume::Pending)` — the coroutine yielded the internal
221    ///   "future pending" marker (a single light-userdata == `poll_pending()`);
222    ///   the stack is left cleared.
223    /// * `Ok(AsyncResume::Yielded(vals))` — the coroutine yielded `vals`
224    ///   (a `coroutine.yield`, i.e. a Stream item).
225    /// * `Ok(AsyncResume::Returned(vals))` — the coroutine finished, returning
226    ///   `vals`.
227    ///
228    /// The coroutine stack is fully consumed/cleared on every path.
229    #[cfg(feature = "async")]
230    pub(crate) fn resume_for_async(&self, args: MultiValue) -> Result<AsyncResume> {
231        let lua = self.lua();
232        let parent = lua.state();
233        let co = self.thread_state;
234        unsafe {
235            let nargs = args.len() as c_int;
236            if lua_checkstack(co, nargs.saturating_add(2)) == 0 {
237                return Err(Error::RuntimeError(
238                    "stack overflow: too many arguments to coroutine resume".to_string(),
239                ));
240            }
241            for v in args.iter() {
242                lua.push_value(v)?;
243            }
244            if nargs > 0 {
245                lua_xmove(parent, co, nargs);
246            }
247            let status = lua_resume(co, parent, nargs);
248
249            if status != status::OK && status != status::YIELD {
250                let nres = lua_gettop(co);
251                if nres > 0 {
252                    lua_xmove(co, parent, nres);
253                }
254                return Err(lua.pop_error(status));
255            }
256
257            let yielded = status == status::YIELD;
258            let nres = lua_gettop(co);
259
260            // Detect the single-light-userdata pending marker (top of the
261            // coroutine stack) on a yield.
262            if yielded
263                && nres == 1
264                && crate::sys::lua_tolightuserdata(co, -1) == crate::async_support::poll_pending()
265            {
266                lua_settop(co, 0);
267                return Ok(AsyncResume::Pending);
268            }
269
270            // Otherwise move the produced values to the parent and convert.
271            if lua_checkstack(parent, nres.saturating_add(1)) == 0 {
272                return Err(Error::RuntimeError("stack overflow".to_string()));
273            }
274            let base = lua_gettop(parent);
275            if nres > 0 {
276                lua_xmove(co, parent, nres);
277            }
278            let mut results = MultiValue::with_capacity(nres.max(0) as usize);
279            for i in 0..nres {
280                results.push_back(lua.value_from_stack(base + 1 + i)?);
281            }
282            lua_settop(parent, base);
283            lua_settop(co, 0);
284
285            if yielded {
286                Ok(AsyncResume::Yielded(results))
287            } else {
288                Ok(AsyncResume::Returned(results))
289            }
290        }
291    }
292
293    /// Resume a yielded async coroutine with the "terminate" signal so it drops
294    /// its in-flight future and parks. Best-effort; ignores errors. Used when an
295    /// [`AsyncThread`](crate::async_support::AsyncThread) is dropped mid-flight.
296    #[cfg(feature = "async")]
297    pub(crate) fn terminate_async(&self) {
298        if !self.is_resumable() {
299            return;
300        }
301        let lua = self.lua();
302        let parent = lua.state();
303        let co = self.thread_state;
304        unsafe {
305            if lua_checkstack(co, 2) == 0 {
306                return;
307            }
308            crate::sys::lua_pushlightuserdatatagged(
309                parent,
310                crate::async_support::poll_terminate(),
311                0,
312            );
313            lua_xmove(parent, co, 1);
314            let _ = lua_resume(co, parent, 1);
315            lua_settop(co, 0);
316        }
317    }
318
319    /// The thread's status. Mirrors `mlua::Thread::status`.
320    pub fn status(&self) -> ThreadStatus {
321        let lua = self.lua();
322        let parent = lua.state();
323        let co = self.thread_state;
324        // A thread whose state is the currently-running state is "Running".
325        if co == parent {
326            return ThreadStatus::Running;
327        }
328        unsafe {
329            // A coroutine yielded by an interrupt (`lua_break`) has raw status
330            // `LUA_BREAK`; `lua_costatus` reports that as "normal", but the
331            // coroutine is in fact resumable (it continues from the break point
332            // on the next resume). Detect it directly.
333            if lua_status(co) == status::BREAK {
334                return ThreadStatus::Resumable;
335            }
336            let cos = lua_costatus(parent, co);
337            match cos {
338                costatus::SUSPENDED => ThreadStatus::Resumable,
339                costatus::RUNNING => ThreadStatus::Running,
340                costatus::NORMAL => ThreadStatus::Normal,
341                costatus::FINISHED => ThreadStatus::Finished,
342                costatus::ERROR => ThreadStatus::Error,
343                _ => {
344                    // Fall back to lua_status for any unexpected code.
345                    let s = lua_status(co);
346                    if s == status::YIELD {
347                        ThreadStatus::Resumable
348                    } else if s == status::OK {
349                        // New (function on stack) vs finished (empty stack).
350                        if lua_gettop(co) > 0 {
351                            ThreadStatus::Resumable
352                        } else {
353                            ThreadStatus::Finished
354                        }
355                    } else {
356                        ThreadStatus::Error
357                    }
358                }
359            }
360        }
361    }
362
363    /// Whether the thread can be resumed. Mirrors `mlua::Thread::is_resumable`.
364    pub fn is_resumable(&self) -> bool {
365        self.status() == ThreadStatus::Resumable
366    }
367
368    /// Whether the thread is currently running. Mirrors `mlua::Thread::is_running`.
369    pub fn is_running(&self) -> bool {
370        self.status() == ThreadStatus::Running
371    }
372
373    /// Whether the thread is active but not running. Mirrors
374    /// `mlua::Thread::is_normal`.
375    pub fn is_normal(&self) -> bool {
376        self.status() == ThreadStatus::Normal
377    }
378
379    /// Whether the thread has finished executing. Mirrors
380    /// `mlua::Thread::is_finished`.
381    pub fn is_finished(&self) -> bool {
382        self.status() == ThreadStatus::Finished
383    }
384
385    /// Whether the thread raised an error. Mirrors `mlua::Thread::is_error`.
386    pub fn is_error(&self) -> bool {
387        self.status() == ThreadStatus::Error
388    }
389
390    /// Reset the thread to a fresh state and install `func` as its body.
391    /// Mirrors `mlua::Thread::reset` (Luau semantics: any non-running thread can
392    /// be reset).
393    pub fn reset(&self, func: Function) -> Result<()> {
394        let status = self.status();
395        match status {
396            ThreadStatus::Running => {
397                return Err(Error::runtime("cannot reset a running thread"));
398            }
399            ThreadStatus::Normal => {
400                return Err(Error::runtime("cannot reset a normal thread"));
401            }
402            _ => {}
403        }
404        let lua = self.lua();
405        let parent = lua.state();
406        let co = self.thread_state;
407        unsafe {
408            lua_resetthread(co);
409            // Push the new body function onto the coroutine stack.
410            func.push_to_stack();
411            lua_xmove(parent, co, 1);
412            // Re-inherit the *main* globals table into the coroutine, dropping
413            // any sandbox proxy global a prior `Thread::sandbox` had installed
414            // (matches mlua's Luau `reset`: a reset thread sees the main env).
415            lua_pushvalue(parent, LUA_GLOBALSINDEX);
416            lua_xmove(parent, co, 1);
417            lua_replace(co, LUA_GLOBALSINDEX);
418        }
419        Ok(())
420    }
421
422    /// A raw pointer identifying this thread. Mirrors `mlua::Thread::to_pointer`.
423    pub fn to_pointer(&self) -> *const c_void {
424        let state = self.reference.state();
425        unsafe {
426            self.reference.push();
427            let p = lua_topointer(state, -1);
428            lua_pop(state, 1);
429            p
430        }
431    }
432}
433
434impl std::fmt::Debug for Thread {
435    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436        write!(f, "Thread")
437    }
438}
439
440// ---------------------------------------------------------------------------
441// Async: drive a coroutine as a Rust `Future` / `Stream` (the `async` feature)
442// ---------------------------------------------------------------------------
443
444#[cfg(feature = "async")]
445impl Thread {
446    /// Convert this (resumable) thread into an
447    /// [`AsyncThread`](crate::AsyncThread) that implements
448    /// [`Future`](std::future::Future) and
449    /// [`Stream`](futures_util::stream::Stream).
450    ///
451    /// Mirrors `mlua::Thread::into_async`. `args` are passed to the coroutine on
452    /// its first resume. As a `Future` the thread is driven to completion and
453    /// resolves to its final return value(s); as a `Stream` each
454    /// `coroutine.yield` produces an item.
455    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
456    pub fn into_async<R: FromLuaMulti>(
457        self,
458        args: impl IntoLuaMulti,
459    ) -> Result<crate::async_support::AsyncThread<R>> {
460        if !self.is_resumable() {
461            return Err(Error::CoroutineUnresumable);
462        }
463        let lua = self.lua();
464        let args = args.into_lua_multi(&lua)?;
465        Ok(crate::async_support::AsyncThread::new(self, args))
466    }
467}
468
469impl PartialEq for Thread {
470    fn eq(&self, other: &Self) -> bool {
471        self.to_pointer() == other.to_pointer()
472    }
473}
474
475impl IntoLua for Thread {
476    fn into_lua(self, _lua: &Lua) -> Result<crate::value::Value> {
477        Ok(crate::value::Value::Thread(self))
478    }
479}
480
481impl IntoLua for &Thread {
482    fn into_lua(self, _lua: &Lua) -> Result<crate::value::Value> {
483        Ok(crate::value::Value::Thread(self.clone()))
484    }
485}
486
487impl FromLua for Thread {
488    fn from_lua(value: crate::value::Value, _lua: &Lua) -> Result<Self> {
489        match value {
490            crate::value::Value::Thread(t) => Ok(t),
491            other => Err(Error::FromLuaConversionError {
492                from: other.type_name(),
493                to: "Thread".to_string(),
494                message: None,
495            }),
496        }
497    }
498}
499
500use crate::traits::FromLua;
501
502impl Lua {
503    /// Create a new coroutine from a [`Function`]. Mirrors
504    /// `mlua::Lua::create_thread`.
505    pub fn create_thread(&self, func: Function) -> Result<Thread> {
506        let state = self.state();
507        unsafe {
508            // Create a new thread; it is pushed on the parent stack.
509            let co = lua_newthread(state);
510            if co.is_null() {
511                return Err(Error::runtime("luaur-rt: failed to create thread"));
512            }
513            // Take a ref to the thread value (still on the parent stack top).
514            let thread = Thread::from_ref(self.pop_ref());
515            // Move the body function onto the coroutine's stack so the first
516            // resume invokes it.
517            func.push_to_stack(); // pushes onto parent stack
518            lua_xmove(state, co, 1);
519            Ok(thread)
520        }
521    }
522
523    /// The currently-running thread. Mirrors `mlua::Lua::current_thread`.
524    ///
525    /// Inside a Rust callback this is the coroutine (or main thread) that
526    /// invoked it. Under the `async` feature, a coroutine created implicitly by
527    /// `call_async` is transparent: this returns its *owner* thread instead, so
528    /// `current_thread()` is stable across the implicit-coroutine boundary
529    /// (matching mlua).
530    pub fn current_thread(&self) -> Thread {
531        let state = self.state();
532        // If we are running on an implicit `call_async` coroutine, report the
533        // owner thread that issued the call.
534        #[cfg(feature = "async")]
535        if let Some(owner) = crate::async_support::implicit_thread_owner(state) {
536            unsafe {
537                lua_pushthread(owner);
538                // The owner-thread value is on the owner's stack; move it to this
539                // state so we can take a ref to it from here.
540                if owner != state {
541                    lua_xmove(owner, state, 1);
542                }
543                return Thread::from_ref(self.pop_ref());
544            }
545        }
546        unsafe {
547            lua_pushthread(state);
548            Thread::from_ref(self.pop_ref())
549        }
550    }
551}