Skip to content

Commit 728e8ea

Browse files
authored
Merge pull request #68 from chucklefish/scope
Lots of changes, not sure if actually safe yet.
2 parents 7780a91 + 164250b commit 728e8ea

File tree

9 files changed

+343
-111
lines changed

9 files changed

+343
-111
lines changed

src/conversion.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
112112
}
113113
}
114114

115-
impl<'lua, T: UserData> ToLua<'lua> for T {
115+
impl<'lua, T: Send + UserData> ToLua<'lua> for T {
116116
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
117117
Ok(Value::UserData(lua.create_userdata(self)?))
118118
}

src/ffi.rs

+3
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ extern "C" {
124124
pub fn lua_setuservalue(state: *mut lua_State, index: c_int);
125125
pub fn lua_getuservalue(state: *mut lua_State, index: c_int) -> c_int;
126126

127+
pub fn lua_getupvalue(state: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
128+
pub fn lua_setupvalue(state: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
129+
127130
pub fn lua_settable(state: *mut lua_State, index: c_int);
128131
pub fn lua_rawset(state: *mut lua_State, index: c_int);
129132
pub fn lua_setmetatable(state: *mut lua_State, index: c_int);

src/lua.rs

+153-36
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
use std::{ptr, str};
1+
use std::{mem, process, ptr, str};
22
use std::sync::{Arc, Mutex};
33
use std::ops::DerefMut;
44
use std::cell::RefCell;
55
use std::ffi::CString;
6-
use std::any::TypeId;
6+
use std::any::{Any, TypeId};
77
use std::marker::PhantomData;
88
use std::collections::HashMap;
99
use std::os::raw::{c_char, c_int, c_void};
10-
use std::mem;
11-
use std::process;
1210

1311
use libc;
1412

@@ -30,12 +28,21 @@ pub struct Lua {
3028
ephemeral: bool,
3129
}
3230

31+
/// Constructed by the `Lua::scope` method, allows temporarily passing to Lua userdata that is
32+
/// !Send, and callbacks that are !Send and not 'static.
33+
pub struct Scope<'lua> {
34+
lua: &'lua Lua,
35+
destructors: RefCell<Vec<Box<FnMut(*mut ffi::lua_State) -> Box<Any>>>>,
36+
}
37+
3338
// Data associated with the main lua_State via lua_getextraspace.
3439
struct ExtraData {
3540
registered_userdata: HashMap<TypeId, c_int>,
36-
registry_drop_list: Arc<Mutex<Vec<c_int>>>,
41+
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
3742
}
3843

44+
unsafe impl Send for Lua {}
45+
3946
impl Drop for Lua {
4047
fn drop(&mut self) {
4148
unsafe {
@@ -49,6 +56,7 @@ impl Drop for Lua {
4956
}
5057

5158
let extra_data = *(ffi::lua_getextraspace(self.state) as *mut *mut ExtraData);
59+
*(*extra_data).registry_unref_list.lock().unwrap() = None;
5260
Box::from_raw(extra_data);
5361

5462
ffi::lua_close(self.state);
@@ -259,7 +267,7 @@ impl Lua {
259267
where
260268
A: FromLuaMulti<'lua>,
261269
R: ToLuaMulti<'lua>,
262-
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
270+
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
263271
{
264272
self.create_callback_function(Box::new(move |lua, args| {
265273
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
@@ -286,25 +294,9 @@ impl Lua {
286294
/// Create a Lua userdata object from a custom userdata type.
287295
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
288296
where
289-
T: UserData,
297+
T: Send + UserData,
290298
{
291-
unsafe {
292-
stack_err_guard(self.state, 0, move || {
293-
check_stack(self.state, 3);
294-
295-
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
296-
297-
ffi::lua_rawgeti(
298-
self.state,
299-
ffi::LUA_REGISTRYINDEX,
300-
self.userdata_metatable::<T>()? as ffi::lua_Integer,
301-
);
302-
303-
ffi::lua_setmetatable(self.state, -2);
304-
305-
Ok(AnyUserData(self.pop_ref(self.state)))
306-
})
307-
}
299+
self.do_create_userdata(data)
308300
}
309301

310302
/// Returns a handle to the global environment.
@@ -318,6 +310,28 @@ impl Lua {
318310
}
319311
}
320312

313+
/// Calls the given function with a `Scope` parameter, giving the function the ability to create
314+
/// userdata from rust types that are !Send, and rust callbacks that are !Send and not 'static.
315+
/// The lifetime of any function or userdata created through `Scope` lasts only until the
316+
/// completion of this method call, on completion all such created values are automatically
317+
/// dropped and Lua references to them are invalidated. If a script accesses a value created
318+
/// through `Scope` outside of this method, a Lua error will result. Since we can ensure the
319+
/// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another
320+
/// thread while `Scope` is live, it is safe to allow !Send datatypes and functions whose
321+
/// lifetimes only outlive the scope lifetime.
322+
pub fn scope<'lua, F, R>(&'lua self, f: F) -> R
323+
where
324+
F: FnOnce(&mut Scope<'lua>) -> R,
325+
{
326+
let mut scope = Scope {
327+
lua: self,
328+
destructors: RefCell::new(Vec::new()),
329+
};
330+
let r = f(&mut scope);
331+
drop(scope);
332+
r
333+
}
334+
321335
/// Coerces a Lua value to a string.
322336
///
323337
/// The value must be a string (in which case this is a no-op) or a number.
@@ -492,7 +506,8 @@ impl Lua {
492506

493507
Ok(RegistryKey {
494508
registry_id,
495-
drop_list: (*self.extra()).registry_drop_list.clone(),
509+
unref_list: (*self.extra()).registry_unref_list.clone(),
510+
drop_unref: true,
496511
})
497512
})
498513
}
@@ -504,7 +519,7 @@ impl Lua {
504519
/// value previously placed by `create_registry_value`.
505520
pub fn registry_value<'lua, T: FromLua<'lua>>(&'lua self, key: &RegistryKey) -> Result<T> {
506521
unsafe {
507-
if !Arc::ptr_eq(&key.drop_list, &(*self.extra()).registry_drop_list) {
522+
if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) {
508523
return Err(Error::MismatchedRegistryKey);
509524
}
510525

@@ -528,13 +543,12 @@ impl Lua {
528543
/// `RegistryKey`s have been dropped.
529544
pub fn remove_registry_value(&self, mut key: RegistryKey) -> Result<()> {
530545
unsafe {
531-
if !Arc::ptr_eq(&key.drop_list, &(*self.extra()).registry_drop_list) {
546+
if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) {
532547
return Err(Error::MismatchedRegistryKey);
533548
}
534549

535550
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.registry_id);
536-
// Don't adding to the registry drop list when dropping the key
537-
key.registry_id = ffi::LUA_REFNIL;
551+
key.drop_unref = false;
538552
Ok(())
539553
}
540554
}
@@ -546,19 +560,19 @@ impl Lua {
546560
/// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a
547561
/// matching `Lua` state.
548562
pub fn owns_registry_value(&self, key: &RegistryKey) -> bool {
549-
unsafe { Arc::ptr_eq(&key.drop_list, &(*self.extra()).registry_drop_list) }
563+
unsafe { Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) }
550564
}
551565

552566
/// Remove any registry values whose `RegistryKey`s have all been dropped. Unlike normal handle
553567
/// values, `RegistryKey`s cannot automatically clean up their registry entries on Drop, but you
554568
/// can call this method to remove any unreachable registry values.
555569
pub fn expire_registry_values(&self) {
556570
unsafe {
557-
let drop_list = mem::replace(
558-
(*self.extra()).registry_drop_list.lock().unwrap().as_mut(),
559-
Vec::new(),
571+
let unref_list = mem::replace(
572+
&mut *(*self.extra()).registry_unref_list.lock().unwrap(),
573+
Some(Vec::new()),
560574
);
561-
for id in drop_list {
575+
for id in unref_list.unwrap() {
562576
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, id);
563577
}
564578
}
@@ -694,6 +708,7 @@ impl Lua {
694708
LuaRef {
695709
lua: self,
696710
registry_id: registry_id,
711+
drop_unref: true,
697712
}
698713
}
699714

@@ -920,7 +935,7 @@ impl Lua {
920935

921936
let extra_data = Box::into_raw(Box::new(ExtraData {
922937
registered_userdata: HashMap::new(),
923-
registry_drop_list: Arc::new(Mutex::new(Vec::new())),
938+
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
924939
}));
925940
*(ffi::lua_getextraspace(state) as *mut *mut ExtraData) = extra_data;
926941
});
@@ -934,6 +949,11 @@ impl Lua {
934949

935950
fn create_callback_function<'lua>(&'lua self, func: Callback<'lua>) -> Result<Function<'lua>> {
936951
unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int {
952+
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL {
953+
ffi::lua_pushstring(state, cstr!("rust callback has been destructed"));
954+
ffi::lua_error(state)
955+
}
956+
937957
callback_error(state, || {
938958
let lua = Lua {
939959
state: state,
@@ -976,7 +996,7 @@ impl Lua {
976996
self.state,
977997
&FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
978998
);
979-
ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX);
999+
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
9801000
ffi::lua_setmetatable(self.state, -2);
9811001

9821002
protect_lua_call(self.state, 1, 1, |state| {
@@ -988,9 +1008,106 @@ impl Lua {
9881008
}
9891009
}
9901010

1011+
fn do_create_userdata<T>(&self, data: T) -> Result<AnyUserData>
1012+
where
1013+
T: UserData,
1014+
{
1015+
unsafe {
1016+
stack_err_guard(self.state, 0, move || {
1017+
check_stack(self.state, 3);
1018+
1019+
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
1020+
1021+
ffi::lua_rawgeti(
1022+
self.state,
1023+
ffi::LUA_REGISTRYINDEX,
1024+
self.userdata_metatable::<T>()? as ffi::lua_Integer,
1025+
);
1026+
1027+
ffi::lua_setmetatable(self.state, -2);
1028+
1029+
Ok(AnyUserData(self.pop_ref(self.state)))
1030+
})
1031+
}
1032+
}
1033+
9911034
unsafe fn extra(&self) -> *mut ExtraData {
9921035
*(ffi::lua_getextraspace(self.main_state) as *mut *mut ExtraData)
9931036
}
9941037
}
9951038

1039+
impl<'lua> Scope<'lua> {
1040+
pub fn create_function<'scope, A, R, F>(&'scope self, mut func: F) -> Result<Function<'scope>>
1041+
where
1042+
A: FromLuaMulti<'scope>,
1043+
R: ToLuaMulti<'scope>,
1044+
F: 'scope + FnMut(&'scope Lua, A) -> Result<R>,
1045+
{
1046+
unsafe {
1047+
let mut f = self.lua
1048+
.create_callback_function(Box::new(move |lua, args| {
1049+
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
1050+
}))?;
1051+
f.0.drop_unref = false;
1052+
let mut destructors = self.destructors.borrow_mut();
1053+
let registry_id = f.0.registry_id;
1054+
destructors.push(Box::new(move |state| {
1055+
check_stack(state, 2);
1056+
ffi::lua_rawgeti(
1057+
state,
1058+
ffi::LUA_REGISTRYINDEX,
1059+
registry_id as ffi::lua_Integer,
1060+
);
1061+
ffi::lua_getupvalue(state, -1, 1);
1062+
let ud = take_userdata::<RefCell<Callback>>(state);
1063+
1064+
ffi::lua_pushnil(state);
1065+
ffi::lua_setupvalue(state, -2, 1);
1066+
1067+
ffi::lua_pop(state, 1);
1068+
Box::new(ud)
1069+
}));
1070+
Ok(f)
1071+
}
1072+
}
1073+
1074+
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
1075+
where
1076+
T: UserData,
1077+
{
1078+
unsafe {
1079+
let mut u = self.lua.do_create_userdata(data)?;
1080+
u.0.drop_unref = false;
1081+
let mut destructors = self.destructors.borrow_mut();
1082+
let registry_id = u.0.registry_id;
1083+
destructors.push(Box::new(move |state| {
1084+
check_stack(state, 1);
1085+
ffi::lua_rawgeti(
1086+
state,
1087+
ffi::LUA_REGISTRYINDEX,
1088+
registry_id as ffi::lua_Integer,
1089+
);
1090+
Box::new(take_userdata::<RefCell<T>>(state))
1091+
}));
1092+
Ok(u)
1093+
}
1094+
}
1095+
}
1096+
1097+
impl<'lua> Drop for Scope<'lua> {
1098+
fn drop(&mut self) {
1099+
// We separate the action of invalidating the userdata in Lua and actually dropping the
1100+
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
1101+
// can be sure that all of the userdata in Lua is actually invalidated.
1102+
1103+
let state = self.lua.state;
1104+
let to_drop = self.destructors
1105+
.get_mut()
1106+
.drain(..)
1107+
.map(|mut destructor| destructor(state))
1108+
.collect::<Vec<_>>();
1109+
drop(to_drop);
1110+
}
1111+
}
1112+
9961113
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;

0 commit comments

Comments
 (0)