Skip to content

Commit 3774296

Browse files
committed
Run gargabe collection on main Lua instance drop
This should help preventing leaking memory when capturing Lua in async block and dropping future without finishing polling.
1 parent ece66c4 commit 3774296

File tree

4 files changed

+57
-37
lines changed

4 files changed

+57
-37
lines changed

src/state.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ use util::{callback_error_ext, StateGuard};
4343

4444
/// Top level Lua struct which represents an instance of Lua VM.
4545
#[derive(Clone)]
46-
#[repr(transparent)]
47-
pub struct Lua(XRc<ReentrantMutex<RawLua>>);
46+
pub struct Lua {
47+
pub(self) raw: XRc<ReentrantMutex<RawLua>>,
48+
// Controls whether garbage collection should be run on drop
49+
pub(self) collect_garbage: bool,
50+
}
4851

4952
#[derive(Clone)]
50-
#[repr(transparent)]
5153
pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
5254

5355
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
@@ -137,6 +139,14 @@ impl LuaOptions {
137139
}
138140
}
139141

142+
impl Drop for Lua {
143+
fn drop(&mut self) {
144+
if self.collect_garbage {
145+
let _ = self.gc_collect();
146+
}
147+
}
148+
}
149+
140150
impl fmt::Debug for Lua {
141151
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142152
write!(f, "Lua({:p})", self.lock().state())
@@ -242,7 +252,10 @@ impl Lua {
242252

243253
/// Creates a new Lua state with required `libs` and `options`
244254
unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua {
245-
let lua = Lua(RawLua::new(libs, options));
255+
let lua = Lua {
256+
raw: RawLua::new(libs, options),
257+
collect_garbage: true,
258+
};
246259

247260
#[cfg(feature = "luau")]
248261
mlua_expect!(lua.configure_luau(), "Error configuring Luau");
@@ -257,7 +270,10 @@ impl Lua {
257270
#[allow(clippy::missing_safety_doc)]
258271
#[inline]
259272
pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua {
260-
Lua(RawLua::init_from_ptr(state))
273+
Lua {
274+
raw: RawLua::init_from_ptr(state),
275+
collect_garbage: true,
276+
}
261277
}
262278

263279
/// FIXME: Deprecated load_from_std_lib
@@ -1157,6 +1173,8 @@ impl Lua {
11571173
FR: Future<Output = Result<R>> + MaybeSend + 'static,
11581174
R: IntoLuaMulti,
11591175
{
1176+
// In future we should switch to async closures when they are stable to capture `&Lua`
1177+
// See https://rust-lang.github.io/rfcs/3668-async-closures.html
11601178
(self.lock()).create_async_callback(Box::new(move |rawlua, nargs| unsafe {
11611179
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
11621180
Ok(args) => args,
@@ -1819,25 +1837,25 @@ impl Lua {
18191837

18201838
#[inline(always)]
18211839
pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> {
1822-
self.0.lock()
1840+
self.raw.lock()
18231841
}
18241842

18251843
#[inline(always)]
18261844
pub(crate) fn lock_arc(&self) -> LuaGuard {
1827-
LuaGuard(self.0.lock_arc())
1845+
LuaGuard(self.raw.lock_arc())
18281846
}
18291847

18301848
#[inline(always)]
18311849
pub(crate) fn weak(&self) -> WeakLua {
1832-
WeakLua(XRc::downgrade(&self.0))
1850+
WeakLua(XRc::downgrade(&self.raw))
18331851
}
18341852

18351853
/// Returns a handle to the unprotected Lua state without any synchronization.
18361854
///
18371855
/// This is useful where we know that the lock is already held by the caller.
18381856
#[inline(always)]
18391857
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
1840-
&*self.0.data_ptr()
1858+
&*self.raw.data_ptr()
18411859
}
18421860
}
18431861

src/state/extra.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::any::TypeId;
22
use std::cell::UnsafeCell;
3-
use std::mem::{self, MaybeUninit};
3+
use std::mem::MaybeUninit;
44
use std::os::raw::{c_int, c_void};
55
use std::ptr;
66
use std::rc::Rc;
@@ -12,7 +12,7 @@ use rustc_hash::FxHashMap;
1212
use crate::error::Result;
1313
use crate::state::RawLua;
1414
use crate::stdlib::StdLib;
15-
use crate::types::{AppData, ReentrantMutex, XRc, XWeak};
15+
use crate::types::{AppData, ReentrantMutex, XRc};
1616
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};
1717

1818
#[cfg(any(feature = "luau", doc))]
@@ -31,10 +31,8 @@ const REF_STACK_RESERVE: c_int = 1;
3131

3232
/// Data associated with the Lua state.
3333
pub(crate) struct ExtraData {
34-
// Same layout as `Lua`
35-
pub(super) lua: MaybeUninit<XRc<ReentrantMutex<RawLua>>>,
36-
// Same layout as `WeakLua`
37-
pub(super) weak: MaybeUninit<XWeak<ReentrantMutex<RawLua>>>,
34+
pub(super) lua: MaybeUninit<Lua>,
35+
pub(super) weak: MaybeUninit<WeakLua>,
3836

3937
pub(super) registered_userdata: FxHashMap<TypeId, c_int>,
4038
pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>,
@@ -185,12 +183,15 @@ impl ExtraData {
185183
extra
186184
}
187185

188-
pub(super) unsafe fn set_lua(&mut self, lua: &XRc<ReentrantMutex<RawLua>>) {
189-
self.lua.write(XRc::clone(lua));
186+
pub(super) unsafe fn set_lua(&mut self, raw: &XRc<ReentrantMutex<RawLua>>) {
187+
self.lua.write(Lua {
188+
raw: XRc::clone(raw),
189+
collect_garbage: false,
190+
});
190191
if cfg!(not(feature = "module")) {
191-
XRc::decrement_strong_count(XRc::as_ptr(lua));
192+
XRc::decrement_strong_count(XRc::as_ptr(raw));
192193
}
193-
self.weak.write(XRc::downgrade(lua));
194+
self.weak.write(WeakLua(XRc::downgrade(raw)));
194195
}
195196

196197
pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
@@ -228,16 +229,16 @@ impl ExtraData {
228229

229230
#[inline(always)]
230231
pub(super) unsafe fn lua(&self) -> &Lua {
231-
mem::transmute(self.lua.assume_init_ref())
232+
self.lua.assume_init_ref()
232233
}
233234

234235
#[inline(always)]
235236
pub(super) unsafe fn raw_lua(&self) -> &RawLua {
236-
&*self.lua.assume_init_ref().data_ptr()
237+
&*self.lua.assume_init_ref().raw.data_ptr()
237238
}
238239

239240
#[inline(always)]
240241
pub(super) unsafe fn weak(&self) -> &WeakLua {
241-
mem::transmute(self.weak.assume_init_ref())
242+
self.weak.assume_init_ref()
242243
}
243244
}

src/state/raw.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,10 @@ impl RawLua {
219219
rawlua
220220
}
221221

222-
pub(super) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option<XRc<ReentrantMutex<Self>>> {
222+
unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option<XRc<ReentrantMutex<Self>>> {
223223
match ExtraData::get(state) {
224224
extra if extra.is_null() => None,
225-
extra => Some(XRc::clone(&(*extra).lua().0)),
225+
extra => Some(XRc::clone(&(*extra).lua().raw)),
226226
}
227227
}
228228

tests/async.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -498,21 +498,22 @@ async fn test_async_thread_error() -> Result<()> {
498498

499499
#[tokio::test]
500500
async fn test_async_terminate() -> Result<()> {
501-
let lua = Lua::new();
502-
503501
let mutex = Arc::new(Mutex::new(0u32));
504-
let mutex2 = mutex.clone();
505-
let func = lua.create_async_function(move |_, ()| {
506-
let mutex = mutex2.clone();
507-
async move {
508-
let _guard = mutex.lock().await;
509-
sleep_ms(100).await;
510-
Ok(())
511-
}
512-
})?;
502+
{
503+
let lua = Lua::new();
504+
let mutex2 = mutex.clone();
505+
let func = lua.create_async_function(move |lua, ()| {
506+
let mutex = mutex2.clone();
507+
async move {
508+
let _guard = mutex.lock().await;
509+
sleep_ms(100).await;
510+
drop(lua); // Move Lua to the future to test drop
511+
Ok(())
512+
}
513+
})?;
513514

514-
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(())).await;
515-
lua.gc_collect()?;
515+
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(())).await;
516+
}
516517
assert!(mutex.try_lock().is_ok());
517518

518519
Ok(())

0 commit comments

Comments
 (0)