Skip to content

Commit bcb9a4d

Browse files
committed
Enable Thread::reset() for all Lua versions
1 parent 9caf354 commit bcb9a4d

File tree

4 files changed

+102
-73
lines changed

4 files changed

+102
-73
lines changed

src/state.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,6 @@ pub struct LuaOptions {
9898

9999
/// Max size of thread (coroutine) object pool used to execute asynchronous functions.
100100
///
101-
/// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function
102-
/// is available and allows to reuse old coroutines after resetting their state.
103-
///
104101
/// Default: **0** (disabled)
105102
///
106103
/// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread

src/state/raw.rs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,6 @@ impl RawLua {
505505
/// Wraps a Lua function into a new or recycled thread (coroutine).
506506
#[cfg(feature = "async")]
507507
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
508-
#[cfg(any(feature = "lua54", feature = "luau"))]
509508
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
510509
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
511510
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
@@ -525,27 +524,47 @@ impl RawLua {
525524

526525
/// Resets thread (coroutine) and returns it to the pool for later use.
527526
#[cfg(feature = "async")]
528-
#[cfg(any(feature = "lua54", feature = "luau"))]
529-
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool {
527+
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
528+
let thread_state = thread.1;
530529
let extra = &mut *self.extra.get();
531-
if extra.thread_pool.len() < extra.thread_pool.capacity() {
532-
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
533-
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
534-
let status = ffi::lua_resetthread(thread_state);
535-
#[cfg(all(feature = "lua54", feature = "vendored"))]
536-
let status = ffi::lua_closethread(thread_state, self.state());
530+
if extra.thread_pool.len() == extra.thread_pool.capacity() {
537531
#[cfg(feature = "lua54")]
538-
if status != ffi::LUA_OK {
539-
// Error object is on top, drop it
532+
if ffi::lua_status(thread_state) != ffi::LUA_OK {
533+
// Close all to-be-closed variables without returning thread to the pool
534+
#[cfg(not(feature = "vendored"))]
535+
ffi::lua_resetthread(thread_state);
536+
#[cfg(feature = "vendored")]
537+
ffi::lua_closethread(thread_state, self.state());
538+
}
539+
return;
540+
}
541+
542+
let mut reset_ok = false;
543+
if ffi::lua_status(thread_state) == ffi::LUA_OK {
544+
if ffi::lua_gettop(thread_state) > 0 {
540545
ffi::lua_settop(thread_state, 0);
541546
}
542-
#[cfg(feature = "luau")]
547+
reset_ok = true;
548+
}
549+
550+
#[cfg(feature = "lua54")]
551+
if !reset_ok {
552+
#[cfg(not(feature = "vendored"))]
553+
let status = ffi::lua_resetthread(thread_state);
554+
#[cfg(feature = "vendored")]
555+
let status = ffi::lua_closethread(thread_state, self.state());
556+
reset_ok = status == ffi::LUA_OK;
557+
}
558+
#[cfg(feature = "luau")]
559+
if !reset_ok {
543560
ffi::lua_resetthread(thread_state);
561+
reset_ok = true;
562+
}
563+
564+
if reset_ok {
544565
extra.thread_pool.push(thread.0.index);
545566
thread.0.drop = false; // Prevent thread from being garbage collected
546-
return true;
547567
}
548-
false
549568
}
550569

551570
/// Pushes a value that implements `IntoLua` onto the Lua stack.

src/thread.rs

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use std::fmt;
22
use std::os::raw::{c_int, c_void};
33

44
use crate::error::{Error, Result};
5-
#[allow(unused)]
6-
use crate::state::Lua;
5+
use crate::function::Function;
76
use crate::state::RawLua;
87
use crate::traits::{FromLuaMulti, IntoLuaMulti};
98
use crate::types::{LuaType, ValueRef};
@@ -42,6 +41,26 @@ pub enum ThreadStatus {
4241
Error,
4342
}
4443

44+
/// Internal representation of a Lua thread status.
45+
///
46+
/// The number in `New` and `Yielded` variants is the number of arguments pushed
47+
/// to the thread stack.
48+
#[derive(Clone, Copy)]
49+
enum ThreadStatusInner {
50+
New,
51+
Running,
52+
Yielded,
53+
Finished,
54+
Error,
55+
}
56+
57+
impl ThreadStatusInner {
58+
#[inline(always)]
59+
fn is_resumable(self) -> bool {
60+
matches!(self, ThreadStatusInner::New | ThreadStatusInner::Yielded)
61+
}
62+
}
63+
4564
/// Handle to an internal Lua thread (coroutine).
4665
#[derive(Clone)]
4766
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
@@ -122,7 +141,7 @@ impl Thread {
122141
R: FromLuaMulti,
123142
{
124143
let lua = self.0.lua.lock();
125-
if self.status_inner(&lua) != ThreadStatus::Resumable {
144+
if !self.status_inner(&lua).is_resumable() {
126145
return Err(Error::CoroutineUnresumable);
127146
}
128147

@@ -170,23 +189,27 @@ impl Thread {
170189

171190
/// Gets the status of the thread.
172191
pub fn status(&self) -> ThreadStatus {
173-
self.status_inner(&self.0.lua.lock())
192+
match self.status_inner(&self.0.lua.lock()) {
193+
ThreadStatusInner::New | ThreadStatusInner::Yielded => ThreadStatus::Resumable,
194+
ThreadStatusInner::Running => ThreadStatus::Running,
195+
ThreadStatusInner::Finished => ThreadStatus::Finished,
196+
ThreadStatusInner::Error => ThreadStatus::Error,
197+
}
174198
}
175199

176200
/// Gets the status of the thread (internal implementation).
177-
pub(crate) fn status_inner(&self, lua: &RawLua) -> ThreadStatus {
201+
fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner {
178202
let thread_state = self.state();
179203
if thread_state == lua.state() {
180204
// The thread is currently running
181-
return ThreadStatus::Running;
205+
return ThreadStatusInner::Running;
182206
}
183207
let status = unsafe { ffi::lua_status(thread_state) };
184-
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
185-
ThreadStatus::Error
186-
} else if status == ffi::LUA_YIELD || unsafe { ffi::lua_gettop(thread_state) > 0 } {
187-
ThreadStatus::Resumable
188-
} else {
189-
ThreadStatus::Finished
208+
match status {
209+
ffi::LUA_YIELD => ThreadStatusInner::Yielded,
210+
ffi::LUA_OK if unsafe { ffi::lua_gettop(thread_state) } > 0 => ThreadStatusInner::New,
211+
ffi::LUA_OK => ThreadStatusInner::Finished,
212+
_ => ThreadStatusInner::Error,
190213
}
191214
}
192215

@@ -198,7 +221,7 @@ impl Thread {
198221
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
199222
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
200223
where
201-
F: Fn(&Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
224+
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
202225
{
203226
let lua = self.0.lua.lock();
204227
unsafe {
@@ -215,32 +238,37 @@ impl Thread {
215238
/// In Luau: resets to the initial state of a newly created Lua thread.
216239
/// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
217240
///
218-
/// Sets a Lua function for the thread afterwards.
241+
/// Other Lua versions can reset only new or finished threads.
219242
///
220-
/// Requires `feature = "lua54"` OR `feature = "luau"`.
243+
/// Sets a Lua function for the thread afterwards.
221244
///
222245
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
223-
#[cfg(any(feature = "lua54", feature = "luau"))]
224-
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
225-
pub fn reset(&self, func: crate::function::Function) -> Result<()> {
246+
pub fn reset(&self, func: Function) -> Result<()> {
226247
let lua = self.0.lua.lock();
227-
if self.status_inner(&lua) == ThreadStatus::Running {
228-
return Err(Error::runtime("cannot reset a running thread"));
248+
let thread_state = self.state();
249+
match self.status_inner(&lua) {
250+
ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")),
251+
// Any Lua can reuse new or finished thread
252+
ThreadStatusInner::New => unsafe { ffi::lua_settop(thread_state, 0) },
253+
ThreadStatusInner::Finished => {}
254+
#[cfg(not(any(feature = "lua54", feature = "luau")))]
255+
_ => return Err(Error::runtime("cannot reset non-finished thread")),
256+
#[cfg(any(feature = "lua54", feature = "luau"))]
257+
_ => unsafe {
258+
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
259+
let status = ffi::lua_resetthread(thread_state);
260+
#[cfg(all(feature = "lua54", feature = "vendored"))]
261+
let status = ffi::lua_closethread(thread_state, lua.state());
262+
#[cfg(feature = "lua54")]
263+
if status != ffi::LUA_OK {
264+
return Err(pop_error(thread_state, status));
265+
}
266+
#[cfg(feature = "luau")]
267+
ffi::lua_resetthread(thread_state);
268+
},
229269
}
230270

231-
let thread_state = self.state();
232271
unsafe {
233-
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
234-
let status = ffi::lua_resetthread(thread_state);
235-
#[cfg(all(feature = "lua54", feature = "vendored"))]
236-
let status = ffi::lua_closethread(thread_state, lua.state());
237-
#[cfg(feature = "lua54")]
238-
if status != ffi::LUA_OK {
239-
return Err(pop_error(thread_state, status));
240-
}
241-
#[cfg(feature = "luau")]
242-
ffi::lua_resetthread(thread_state);
243-
244272
// Push function to the top of the thread stack
245273
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
246274

@@ -393,30 +421,19 @@ impl LuaType for Thread {
393421

394422
#[cfg(feature = "async")]
395423
impl<A, R> AsyncThread<A, R> {
396-
#[inline]
424+
#[inline(always)]
397425
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
398426
self.recycle = recyclable;
399427
}
400428
}
401429

402430
#[cfg(feature = "async")]
403-
#[cfg(any(feature = "lua54", feature = "luau"))]
404431
impl<A, R> Drop for AsyncThread<A, R> {
405432
fn drop(&mut self) {
406433
if self.recycle {
407434
if let Some(lua) = self.thread.0.lua.try_lock() {
408-
unsafe {
409-
// For Lua 5.4 this also closes all pending to-be-closed variables
410-
if !lua.recycle_thread(&mut self.thread) {
411-
#[cfg(feature = "lua54")]
412-
if self.thread.status_inner(&lua) == ThreadStatus::Error {
413-
#[cfg(not(feature = "vendored"))]
414-
ffi::lua_resetthread(self.thread.state());
415-
#[cfg(feature = "vendored")]
416-
ffi::lua_closethread(self.thread.state(), lua.state());
417-
}
418-
}
419-
}
435+
// For Lua 5.4 this also closes all pending to-be-closed variables
436+
unsafe { lua.recycle_thread(&mut self.thread) };
420437
}
421438
}
422439
}
@@ -428,7 +445,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
428445

429446
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
430447
let lua = self.thread.0.lua.lock();
431-
if self.thread.status_inner(&lua) != ThreadStatus::Resumable {
448+
if !self.thread.status_inner(&lua).is_resumable() {
432449
return Poll::Ready(None);
433450
}
434451

@@ -466,7 +483,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
466483

467484
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
468485
let lua = self.thread.0.lua.lock();
469-
if self.thread.status_inner(&lua) != ThreadStatus::Resumable {
486+
if !self.thread.status_inner(&lua).is_resumable() {
470487
return Poll::Ready(Err(Error::CoroutineUnresumable));
471488
}
472489

@@ -506,7 +523,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
506523
#[cfg(feature = "async")]
507524
#[inline(always)]
508525
unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
509-
ffi::lua_tolightuserdata(state, -1) == Lua::poll_pending().0
526+
ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0
510527
}
511528

512529
#[cfg(feature = "async")]

tests/thread.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ fn test_thread() -> Result<()> {
107107
}
108108

109109
#[test]
110-
#[cfg(any(feature = "lua54", feature = "luau"))]
111110
fn test_thread_reset() -> Result<()> {
112111
use mlua::{AnyUserData, UserData};
113112
use std::sync::Arc;
@@ -120,7 +119,8 @@ fn test_thread_reset() -> Result<()> {
120119
let arc = Arc::new(());
121120

122121
let func: Function = lua.load(r#"function(ud) coroutine.yield(ud) end"#).eval()?;
123-
let thread = lua.create_thread(func.clone())?;
122+
let thread = lua.create_thread(lua.load("return 0").into_function()?)?; // Dummy function first
123+
assert!(thread.reset(func.clone()).is_ok());
124124

125125
for _ in 0..2 {
126126
assert_eq!(thread.status(), ThreadStatus::Resumable);
@@ -145,11 +145,7 @@ fn test_thread_reset() -> Result<()> {
145145
assert!(thread.reset(func.clone()).is_err());
146146
// Reset behavior has changed in Lua v5.4.4
147147
// It's became possible to force reset thread by popping error object
148-
assert!(matches!(
149-
thread.status(),
150-
ThreadStatus::Finished | ThreadStatus::Error
151-
));
152-
// Would pass in 5.4.4
148+
assert!(matches!(thread.status(), ThreadStatus::Finished));
153149
assert!(thread.reset(func.clone()).is_ok());
154150
assert_eq!(thread.status(), ThreadStatus::Resumable);
155151
}

0 commit comments

Comments
 (0)