Various improvements for owned types, including:

- tests
- shortcuts for `OwnedFunction` and `OwnedAnyUserData`
This commit is contained in:
Alex Orlenko 2023-04-26 15:34:15 +01:00
parent bd68a155c0
commit 4daa7de997
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
10 changed files with 192 additions and 44 deletions

View File

@ -122,10 +122,9 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable"
shell: bash
env:
RUSTFLAGS: --cfg mlua_test
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
run: |
@ -157,7 +156,7 @@ jobs:
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
env:
RUSTFLAGS: --cfg mlua_test -Z sanitizer=address
RUSTFLAGS: -Z sanitizer=address
test_modules:
name: Test modules

View File

@ -25,6 +25,12 @@ use {
pub struct Function<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua function.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
///
/// [`UserData`]: crate::UserData
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
@ -415,6 +421,37 @@ impl<'lua> PartialEq for Function<'lua> {
}
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedFunction {
/// Calls the function, passing `args` as function arguments.
///
/// This is a shortcut for [`Function::call()`].
#[inline]
pub fn call<'lua, A, R>(&'lua self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.to_ref().call(args)
}
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// This is a shortcut for [`Function::call_async()`].
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[inline]
pub fn call_async<'lua, A, R>(&'lua self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
{
self.to_ref().call_async(args)
}
}
pub(crate) struct WrappedFunction<'lua>(pub(crate) Callback<'lua, 'static>);
#[cfg(feature = "async")]

View File

@ -230,30 +230,21 @@ const MULTIVALUE_POOL_SIZE: usize = 64;
#[cfg_attr(docsrs, doc(cfg(feature = "send")))]
unsafe impl Send for Lua {}
#[cfg(not(feature = "module"))]
impl Drop for Lua {
fn drop(&mut self) {
let _ = self.gc_collect();
}
}
#[cfg(not(feature = "module"))]
impl Drop for LuaInner {
fn drop(&mut self) {
unsafe {
let extra = &mut *self.extra.get();
let drain_iter = extra.wrapped_failure_pool.drain(..);
#[cfg(feature = "async")]
let drain_iter = drain_iter.chain(extra.thread_pool.drain(..));
for index in drain_iter {
ffi::lua_pushnil(extra.ref_thread);
ffi::lua_replace(extra.ref_thread, index);
extra.ref_free.push(index);
}
#[cfg(feature = "luau")]
{
(*ffi::lua_callbacks(self.state())).userdata = ptr::null_mut();
}
// This is an internal assertion used in integration tests
#[cfg(mlua_test)]
mlua_debug_assert!(
ffi::lua_gettop(extra.ref_thread) == extra.ref_stack_top
&& extra.ref_stack_top as usize == extra.ref_free.len(),
"reference leak detected"
);
ffi::lua_close(self.main_state);
}
}
@ -2477,12 +2468,12 @@ impl Lua {
#[cfg(all(feature = "unstable", not(feature = "send")))]
pub(crate) fn adopt_owned_ref(&self, loref: crate::types::LuaOwnedRef) -> LuaRef {
assert!(
Arc::ptr_eq(&loref.lua.0, &self.0),
Arc::ptr_eq(&loref.inner, &self.0),
"Lua instance passed Value created from a different main Lua state"
);
let index = loref.index;
unsafe {
ptr::read(&loref.lua);
ptr::read(&loref.inner);
mem::forget(loref);
}
LuaRef::new(self, index)
@ -3011,8 +3002,8 @@ impl Lua {
#[cfg(feature = "unstable")]
#[inline]
pub(crate) fn clone(&self) -> Self {
Lua(Arc::clone(&self.0))
pub(crate) fn clone(&self) -> Arc<LuaInner> {
Arc::clone(&self.0)
}
}

View File

@ -25,6 +25,12 @@ use {futures_core::future::LocalBoxFuture, futures_util::future};
pub struct Table<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua table.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
///
/// [`UserData`]: crate::UserData
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]

View File

@ -18,6 +18,9 @@ use crate::lua::{ExtraData, Lua};
use crate::util::{assert_stack, StackGuard};
use crate::value::MultiValue;
#[cfg(feature = "unstable")]
use {crate::lua::LuaInner, std::marker::PhantomData};
/// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers.
@ -237,9 +240,9 @@ impl<'lua> PartialEq for LuaRef<'lua> {
#[cfg(feature = "unstable")]
pub(crate) struct LuaOwnedRef {
pub(crate) lua: Lua,
pub(crate) inner: Arc<LuaInner>,
pub(crate) index: c_int,
_non_send: std::marker::PhantomData<*const ()>,
_non_send: PhantomData<*const ()>,
}
#[cfg(feature = "unstable")]
@ -259,31 +262,24 @@ impl Clone for LuaOwnedRef {
#[cfg(feature = "unstable")]
impl Drop for LuaOwnedRef {
fn drop(&mut self) {
self.lua.drop_ref_index(self.index);
let lua: &Lua = unsafe { mem::transmute(&self.inner) };
lua.drop_ref_index(self.index);
}
}
#[cfg(feature = "unstable")]
impl LuaOwnedRef {
pub(crate) const fn new(lua: Lua, index: c_int) -> Self {
#[cfg(feature = "send")]
{
let _lua = lua;
let _index = index;
panic!("mlua must be compiled without \"send\" feature to use Owned types");
}
#[cfg(not(feature = "send"))]
pub(crate) const fn new(inner: Arc<LuaInner>, index: c_int) -> Self {
LuaOwnedRef {
lua,
inner,
index,
_non_send: std::marker::PhantomData,
_non_send: PhantomData,
}
}
pub(crate) const fn to_ref(&self) -> LuaRef {
LuaRef {
lua: &self.lua,
lua: unsafe { mem::transmute(&self.inner) },
index: self.index,
drop: false,
}

View File

@ -740,6 +740,10 @@ impl Serialize for UserDataSerializeError {
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua userdata.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
@ -1124,6 +1128,32 @@ unsafe fn getuservalue_table(state: *mut ffi::lua_State, idx: c_int) -> c_int {
return ffi::lua_getuservalue(state, idx);
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedAnyUserData {
/// Borrow this userdata immutably if it is of type `T`.
///
/// This is a shortcut for [`AnyUserData::borrow()`]
#[inline]
pub fn borrow<T: 'static>(&self) -> Result<Ref<T>> {
let ud = self.to_ref();
let t = ud.borrow::<T>()?;
// Reattach lifetime to &self
Ok(unsafe { mem::transmute::<Ref<T>, Ref<T>>(t) })
}
/// Borrow this userdata mutably if it is of type `T`.
///
/// This is a shortcut for [`AnyUserData::borrow_mut()`]
#[inline]
pub fn borrow_mut<T: 'static>(&self) -> Result<RefMut<T>> {
let ud = self.to_ref();
let t = ud.borrow_mut::<T>()?;
// Reattach lifetime to &self
Ok(unsafe { mem::transmute::<RefMut<T>, RefMut<T>>(t) })
}
}
/// Handle to a `UserData` metatable.
#[derive(Clone, Debug)]
pub struct UserDataMetatable<'lua>(pub(crate) Table<'lua>);

View File

@ -271,7 +271,7 @@ async fn test_async_thread() -> Result<()> {
}
#[test]
fn test_async_thread_leak() -> Result<()> {
fn test_async_thread_capture() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(move |_lua, v: Value| async move {
@ -285,10 +285,6 @@ fn test_async_thread_leak() -> Result<()> {
thread.resume::<_, ()>("abc").unwrap();
drop(thread);
// Without running garbage collection, the captured `v` would trigger "reference leak detected" error
// with `cfg(mlua_test)`
lua.gc_collect()?;
Ok(())
}
@ -488,3 +484,21 @@ async fn test_async_thread_error() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[tokio::test]
async fn test_owned_async_call() -> Result<()> {
let lua = Lua::new();
let hello = lua
.create_async_function(|_, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(format!("hello, {}!", name))
})?
.into_owned();
drop(lua);
assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
Ok(())
}

View File

@ -199,3 +199,45 @@ fn test_function_wrap() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_function() -> Result<()> {
let lua = Lua::new();
let f = lua
.create_function(|_, ()| Ok("hello, world!"))?
.into_owned();
drop(lua);
// We still should be able to call the function despite Lua is dropped
let s = f.call::<_, String>(())?;
assert_eq!(s.to_string_lossy(), "hello, world!");
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_function_drop() -> Result<()> {
let rc = std::sync::Arc::new(());
{
let lua = Lua::new();
lua.set_app_data(rc.clone());
let f1 = lua
.create_function(|_, ()| Ok("hello, world!"))?
.into_owned();
let f2 =
lua.create_function(move |_, ()| f1.to_ref().call::<_, std::string::String>(()))?;
assert_eq!(f2.call::<_, String>(())?.to_string_lossy(), "hello, world!");
}
// Check that Lua is properly destroyed
// It works because we collect garbage when Lua goes out of scope
assert_eq!(std::sync::Arc::strong_count(&rc), 1);
Ok(())
}

View File

@ -392,3 +392,17 @@ fn test_table_call() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_table() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?.into_owned();
drop(lua);
table.to_ref().set("abc", 123)?;
assert_eq!(table.to_ref().get::<_, i64>("abc")?, 123);
Ok(())
}

View File

@ -820,3 +820,22 @@ fn test_userdata_method_errors() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_userdata() -> Result<()> {
let lua = Lua::new();
let ud = lua.create_any_userdata("abc")?.into_owned();
drop(lua);
assert_eq!(*ud.borrow::<&str>()?, "abc");
*ud.borrow_mut()? = "cba";
assert_eq!(*ud.to_ref().borrow::<&str>()?, "cba");
assert!(matches!(
ud.borrow::<i64>(),
Err(Error::UserDataTypeMismatch)
));
Ok(())
}