From e61b4719f12a2ca8f348b546e224ceabbeb6a474 Mon Sep 17 00:00:00 2001 From: Georgy Moshkin Date: Thu, 21 Oct 2021 15:58:15 +0300 Subject: [PATCH 1/9] chore(lua_ffi): add some doc comments --- hlua/ffi/src/lib.rs | 275 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 236 insertions(+), 39 deletions(-) diff --git a/hlua/ffi/src/lib.rs b/hlua/ffi/src/lib.rs index ef949a3f..662012df 100644 --- a/hlua/ffi/src/lib.rs +++ b/hlua/ffi/src/lib.rs @@ -48,6 +48,40 @@ pub struct luaL_Reg { pub type lua_Number = libc::c_double; pub type lua_Integer = libc::ptrdiff_t; +/// Type for C functions. +/// +/// In order to communicate properly with Lua, a C function must use the +/// following protocol, which defines the way parameters and results are passed: +/// a C function receives its arguments from Lua in its stack in direct order +/// (the first argument is pushed first). So, when the function starts, +/// [`lua_gettop`]`(L)` returns the number of arguments received by the function. +/// The first argument (if any) is at index 1 and its last argument is at index +/// [`lua_gettop`](L). To return values to Lua, a C function just pushes them +/// onto the stack, in direct order (the first result is pushed first), and +/// returns the number of results. Any other value in the stack below the +/// results will be properly discarded by Lua. Like a Lua function, a C function +/// called by Lua can also return many results. +/// +/// As an example, the following function receives a variable number of +/// numerical arguments and returns their average and sum: +/// +/// ``` +/// unsafe extern "C" fn foo(l: *mut lua_State) { +/// let n = lua_gettop(l); /* number of arguments */ +/// let mut sum: lua_Number = 0; +/// let i: i32; +/// for i in 1..=n { +/// if !lua_isnumber(l, i) { +/// lua_pushstring(l, CString::new("incorrect argument").into_raw()); +/// lua_error(l); +/// } +/// sum += lua_tonumber(l, i); +/// } +/// lua_pushnumber(l, sum / n); /* first result */ +/// lua_pushnumber(l, sum); /* second result */ +/// return 2; /* number of results */ +/// } +/// ``` pub type lua_CFunction = unsafe extern "C" fn(l: *mut lua_State) -> c_int; pub type lua_Alloc = extern "C" fn( @@ -80,56 +114,188 @@ extern "C" { pub fn lua_version(L: *mut lua_State) -> *const lua_Number; + /// Returns the index of the top element in the stack. Because indices start + /// at 1, this result is equal to the number of elements in the stack (and + /// so 0 means an empty stack). + /// *[-0, +0, -]* pub fn lua_gettop(l: *mut lua_State) -> c_int; - pub fn lua_settop(l: *mut lua_State, idx: c_int); + pub fn lua_settop(l: *mut lua_State, index: c_int); pub fn lua_pushboolean(l: *mut lua_State, n: c_int); pub fn lua_pushlstring(l: *mut lua_State, s: *const libc::c_char, l: libc::size_t); + + /// Pushes the zero-terminated string pointed to by `s` onto the stack. Lua + /// makes (or reuses) an internal copy of the given string, so the memory at + /// s can be freed or reused immediately after the function returns. The + /// string cannot contain embedded zeros; it is assumed to end at the first + /// zero. + /// *[-0, +1, m]* pub fn lua_pushstring(l: *mut lua_State, s: *const c_schar) -> *const c_schar; pub fn lua_pushinteger(l: *mut lua_State, n: isize); pub fn lua_pushnumber(l: *mut lua_State, n: c_double); + + /// Pushes a new C closure onto the stack. + /// *[-n, +1, m]* + /// + /// When a C function is created, it is possible to associate some values + /// with it, thus creating a C closure; these values are then accessible to + /// the function whenever it is called. To associate values with a C + /// function, first these values should be pushed onto the stack (when there + /// are multiple values, the first value is pushed first). Then + /// lua_pushcclosure is called to create and push the C function onto the + /// stack, with the argument `n` telling how many values should be + /// associated with the function. lua_pushcclosure also pops these values + /// from the stack. + /// + /// The maximum value for `n` is 255. pub fn lua_pushcclosure(l: *mut lua_State, fun: lua_CFunction, n: c_int); pub fn lua_pushnil(l: *mut lua_State); - /// [-0, +1, -] - /// + /// Pushes a copy of the element at the given valid `index` onto the stack. + /// *[-0, +1, -]* pub fn lua_pushvalue(l: *mut lua_State, index: c_int); - pub fn lua_tointeger(l: *mut lua_State, idx: c_int) -> isize; - pub fn lua_toboolean(l: *mut lua_State, idx: c_int) -> c_int; - pub fn lua_tolstring(l: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_schar; - pub fn lua_touserdata(l: *mut lua_State, idx: c_int) -> *mut libc::c_void; - pub fn lua_setfield(l: *mut lua_State, idx: c_int, s: *const c_schar); - pub fn lua_getfield(l: *mut lua_State, idx: c_int, s: *const c_schar); + pub fn lua_tointeger(l: *mut lua_State, index: c_int) -> isize; + pub fn lua_toboolean(l: *mut lua_State, index: c_int) -> c_int; + + /// Converts the Lua value at the given acceptable `index` to a C string. If + /// `len` is not NULL, it also sets `*len` with the string length. The Lua + /// value must be a string or a number; otherwise, the function returns + /// NULL. If the value is a number, then `lua_tolstring` also changes the + /// actual value in the stack to a string. (This change confuses + /// [`lua_next`] when `lua_tolstring` is applied to keys during a table + /// traversal.) + /// *[-0, +0, m]* + /// + /// `lua_tolstring` returns a fully aligned pointer to a string inside the + /// Lua state. This string always has a zero ('\0') after its last character + /// (as in C), but can contain other zeros in its body. Because Lua has + /// garbage collection, there is no guarantee that the pointer returned by + /// `lua_tolstring` will be valid after the corresponding value is removed + /// from the stack. + pub fn lua_tolstring(l: *mut lua_State, index: c_int, len: *mut usize) -> *const c_schar; + + /// If the value at the given acceptable `index` is a full userdata, returns + /// its block address. If the value is a light userdata, returns its + /// pointer. Otherwise, returns `NULL`. + /// *[-0, +0, -]* + pub fn lua_touserdata(l: *mut lua_State, index: c_int) -> *mut libc::c_void; + + /// Does the equivalent to `t[k] = v`, where `t` is the value at the given + /// valid index and `v` is the value at the top of the stack. + /// *[-1, +0, e]* + /// + /// This function pops the value from the stack. As in Lua, this function + /// may trigger a metamethod for the "newindex" event + pub fn lua_setfield(l: *mut lua_State, index: c_int, k: *const c_schar); + + /// Pushes onto the stack the value `t[k]`, where `t` is the value at the + /// given valid `index`. As in Lua, this function may trigger a metamethod + /// for the "index" event + /// *[-0, +1, e]* + pub fn lua_getfield(l: *mut lua_State, index: c_int, k: *const c_schar); + pub fn lua_createtable(l: *mut lua_State, narr: c_int, nrec: c_int); - pub fn lua_newuserdata(l: *mut lua_State, sz: libc::size_t) -> *mut libc::c_void; - /// [-1, +1, e] + + /// This function allocates a new block of memory with the given size, + /// pushes onto the stack a new full userdata with the block address, and + /// returns this address. + /// *[-0, +1, m]* + /// + /// Userdata represent C values in Lua. A full userdata represents a block + /// of memory. It is an object (like a table): you must create it, it can + /// have its own metatable, and you can detect when it is being collected. A + /// full userdata is only equal to itself (under raw equality). /// + /// When Lua collects a full userdata with a gc metamethod, Lua calls the + /// metamethod and marks the userdata as finalized. When this userdata is + /// collected again then Lua frees its corresponding memory. + pub fn lua_newuserdata(l: *mut lua_State, sz: libc::size_t) -> *mut libc::c_void; + /// Pushes onto the stack the value `t[k]`, where `t` is the value at the /// given valid `index` and `k` is the value at the top of the stack. + /// *[-1, +1, e]* /// /// This function pops the key from the stack (putting the resulting value /// in its place). As in Lua, this function may trigger a metamethod for the /// "index" event pub fn lua_gettable(l: *mut lua_State, index: c_int); - pub fn lua_settable(l: *mut lua_State, idx: c_int); + + /// Does the equivalent to `t[k] = v`, where `t` is the value at the given + /// valid `index`, `v` is the value at the top of the stack, and `k` is the + /// value just below the top. + /// *[-2, +0, e]* + /// + /// This function pops both the key and the value from the stack. As in Lua, + /// this function may trigger a metamethod for the "newindex" event. + pub fn lua_settable(l: *mut lua_State, index: c_int); + + /// Returns the type of the value in the given acceptable `index`, or + /// [`LUA_TNONE`] for a non-valid index (that is, an index to an "empty" + /// stack position). The types returned by lua_type are coded by the + /// following constants: [`LUA_TNIL`], [`LUA_TNUMBER`], [`LUA_TBOOLEAN`], + /// [`LUA_TSTRING`], [`LUA_TTABLE`], [`LUA_TFUNCTION`], [`LUA_TUSERDATA`], + /// [`LUA_TTHREAD`], and [`LUA_TLIGHTUSERDATA`]. + /// *[-0, +0, -]* pub fn lua_type(state: *mut lua_State, index: c_int) -> c_int; + + /// Returns the name of the type encoded by the value `tp`, which must be + /// one the values returned by [`lua_type`]. + /// *[-0, +0, -]* pub fn lua_typename(state: *mut lua_State, tp: c_int) -> *mut c_schar; - pub fn lua_setmetatable(l: *mut lua_State, objindex: c_int) -> c_int; - pub fn lua_getmetatable(l: *mut lua_State, objindex: c_int) -> c_int; - pub fn lua_tonumberx(l: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number; - pub fn lua_tointegerx(l: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer; + /// Pops a table from the stack and sets it as the new metatable for the + /// value at the given acceptable `index`. + /// *[-1, +0, -]* + pub fn lua_setmetatable(l: *mut lua_State, index: c_int) -> c_int; + pub fn lua_getmetatable(l: *mut lua_State, index: c_int) -> c_int; + + pub fn lua_tonumberx(l: *mut lua_State, index: c_int, isnum: *mut c_int) -> lua_Number; + pub fn lua_tointegerx(l: *mut lua_State, index: c_int, isnum: *mut c_int) -> lua_Integer; - pub fn lua_pcall(l: *mut lua_State, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int; + /// Calls a function in protected mode. + /// *[-(nargs + 1), +(nresults|1), -]* + /// + /// Both `nargs` and `nresults` have the same meaning as in `lua_call`. If + /// there are no errors during the call, `lua_pcall` behaves exactly like + /// `lua_call`. However, if there is any error, `lua_pcall` catches it, + /// pushes a single value on the stack (the error message), and returns an + /// error code. Like lua_call, `lua_pcall` always removes the function and + /// its arguments from the stack. + /// + /// If `errfunc` is 0, then the error message returned on the stack is + /// exactly the original error message. Otherwise, `errfunc` is the stack + /// index of an error handler function. (In the current implementation, this + /// index cannot be a pseudo-index.) In case of runtime errors, this + /// function will be called with the error message and its return value will + /// be the message returned on the stack by `lua_pcall`. + /// + /// Typically, the error handler function is used to add more debug + /// information to the error message, such as a stack traceback. Such + /// information cannot be gathered after the return of `lua_pcall`, since by + /// then the stack has unwound. + /// + /// The `lua_pcall` function returns 0 in case of success or one of the + /// following error codes: + /// - [`LUA_ERRRUN`]: a runtime error. + /// + /// - [`LUA_ERRMEM`]: memory allocation error. For such errors, Lua does not + /// call the error handler function. + /// + /// - [`LUA_ERRERR`]: error while running the error handler function. + pub fn lua_pcall(l: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int; pub fn lua_load(l: *mut lua_State, reader: lua_Reader, dt: *mut libc::c_void, chunkname: *const libc::c_char, mode: *const libc::c_char) -> c_int; pub fn lua_dump(l: *mut lua_State, writer: lua_Writer, data: *mut libc::c_void) -> c_int; + /// Generates a Lua error. The error message (which can actually be a Lua + /// value of any type) must be on the stack top. This function does a long + /// jump, and therefore never returns. (see [`luaL_error`]). + /// *[-1, +0, v]* pub fn lua_error(l: *mut lua_State) -> c_int; - pub fn lua_next(l: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_next(l: *mut lua_State, index: c_int) -> c_int; pub fn lua_concat(l: *mut lua_State, n: c_int); - pub fn lua_len(l: *mut lua_State, idx: c_int); + pub fn lua_len(l: *mut lua_State, index: c_int); - pub fn lua_insert(l: *mut lua_State, idx: c_int); - pub fn lua_remove(l: *mut lua_State, idx: c_int); + pub fn lua_insert(l: *mut lua_State, index: c_int); + pub fn lua_remove(l: *mut lua_State, index: c_int); pub fn luaopen_base(l: *mut lua_State); pub fn luaopen_bit(l: *mut lua_State); @@ -144,18 +310,31 @@ extern "C" { // lauxlib functions. pub fn luaL_newstate() -> *mut lua_State; pub fn luaL_register(l: *mut lua_State, libname: *const c_schar, lr: *const luaL_Reg); + + /// Raises an error. The error message format is given by `fmt` plus any + /// extra arguments, following the same rules of `lua_pushfstring`. It also + /// adds at the beginning of the message the file name and the line number + /// where the error occurred, if this information is available. + /// *[-0, +0, v]* + /// + /// This function never returns, but it is an idiom to use it in C functions + /// as return `luaL_error(args)`. pub fn luaL_error(l: *mut lua_State, fmt: *const c_schar, ...) -> c_int; pub fn luaL_openlibs(L: *mut lua_State); } #[inline(always)] -pub unsafe fn lua_getglobal(state: *mut lua_State, s: *const c_schar) { - lua_getfield(state, LUA_GLOBALSINDEX, s); +/// Pushes onto the stack the value of the global `name`. +/// *[-0, +1, e]* +pub unsafe fn lua_getglobal(state: *mut lua_State, name: *const c_schar) { + lua_getfield(state, LUA_GLOBALSINDEX, name); } #[inline(always)] -pub unsafe fn lua_setglobal(state: *mut lua_State, s: *const c_schar) { - lua_setfield(state, LUA_GLOBALSINDEX, s); +/// Pops a value from the stack and sets it as the new value of global `name`. +/// *[-1, +0, e]* +pub unsafe fn lua_setglobal(state: *mut lua_State, name: *const c_schar) { + lua_setfield(state, LUA_GLOBALSINDEX, name); } #[inline(always)] @@ -164,6 +343,13 @@ pub unsafe fn lua_pop(state: *mut lua_State, n: c_int) { } #[inline(always)] +/// Pushes a C function onto the stack. This function receives a pointer to a C +/// function and pushes onto the stack a Lua value of type function that, when +/// called, invokes the corresponding C function. +/// `[-0, +1, m]` +/// +/// Any function to be registered in Lua must follow the correct protocol to +/// receive its parameters and return its results (see [`lua_CFunction`]). pub unsafe fn lua_pushcfunction(state: *mut lua_State, f: lua_CFunction) { lua_pushcclosure(state, f, 0); } @@ -179,23 +365,34 @@ pub unsafe fn lua_newtable(state: *mut lua_State) { } #[inline(always)] +/// When a C function is created, it is possible to associate some values with +/// it, thus creating a C closure; these values are called upvalues and are +/// accessible to the function whenever it is called (see [`lua_pushcclosure`]). +/// +/// Whenever a C function is called, its **upvalues** are located at specific +/// pseudo-indices. These pseudo-indices are produced by the function +/// `lua_upvalueindex`. The first value associated with a function is at +/// position `lua_upvalueindex(1)`, and so on. Any access to +/// `lua_upvalueindex(n)`, where n is greater than the number of upvalues of the +/// current function (but not greater than 256), produces an acceptable (but +/// invalid) index. pub fn lua_upvalueindex(i: c_int) -> c_int { LUA_GLOBALSINDEX - i } #[inline(always)] -pub unsafe fn lua_isfunction(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TFUNCTION +pub unsafe fn lua_isfunction(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TFUNCTION } #[inline(always)] -pub unsafe fn lua_istable(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TTABLE +pub unsafe fn lua_istable(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TTABLE } #[inline(always)] -pub unsafe fn lua_islightuserdata(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TLIGHTUSERDATA +pub unsafe fn lua_islightuserdata(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TLIGHTUSERDATA } #[inline(always)] @@ -204,23 +401,23 @@ pub unsafe fn lua_isnil(state: *mut lua_State, index: c_int) -> bool { } #[inline(always)] -pub unsafe fn lua_isboolean(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TBOOLEAN +pub unsafe fn lua_isboolean(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TBOOLEAN } #[inline(always)] -pub unsafe fn lua_isthread(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TTHREAD +pub unsafe fn lua_isthread(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TTHREAD } #[inline(always)] -pub unsafe fn lua_isnone(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) == LUA_TNONE +pub unsafe fn lua_isnone(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) == LUA_TNONE } #[inline(always)] -pub unsafe fn lua_isnoneornil(state: *mut lua_State, idx: c_int) -> bool { - lua_type(state, idx) <= 0 +pub unsafe fn lua_isnoneornil(state: *mut lua_State, index: c_int) -> bool { + lua_type(state, index) <= 0 } #[inline(always)] From 386b5b67ee328734f0225ec189638f590a3bd4c3 Mon Sep 17 00:00:00 2001 From: Georgy Moshkin Date: Fri, 22 Oct 2021 01:15:19 +0300 Subject: [PATCH 2/9] feat(lua_ffi): add luaL_ref related stuff --- hlua/ffi/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/hlua/ffi/src/lib.rs b/hlua/ffi/src/lib.rs index 662012df..54abd24c 100644 --- a/hlua/ffi/src/lib.rs +++ b/hlua/ffi/src/lib.rs @@ -8,6 +8,17 @@ use std::ptr::null_mut; /// 2. lauxlib /// 3. Lua utitlites, implemented in Tarantool +/// Lua provides a registry, a pre-defined table that can be used by any C code +/// to store whatever Lua value it needs to store. This table is always located +/// at pseudo-index `LUA_REGISTRYINDEX`. Any C library can store data into this +/// table, but it should take care to choose keys different from those used by +/// other libraries, to avoid collisions. Typically, you should use as key a +/// string containing your library name or a light userdata with the address of +/// a C object in your code. +/// +/// The integer keys in the registry are used by the reference mechanism, +/// implemented by the auxiliary library, and therefore should not be used for +/// other purposes. pub const LUA_REGISTRYINDEX: c_int = -10000; pub const LUA_ENVIRONINDEX: c_int = -10001; pub const LUA_GLOBALSINDEX: c_int = -10002; @@ -33,6 +44,9 @@ pub const LUA_TTHREAD: c_int = 8; pub const LUA_MINSTACK: c_int = 20; +pub const LUA_NOREF: c_int = -2; +pub const LUA_REFNIL: c_int = -1; + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct lua_State { @@ -321,6 +335,29 @@ extern "C" { /// as return `luaL_error(args)`. pub fn luaL_error(l: *mut lua_State, fmt: *const c_schar, ...) -> c_int; pub fn luaL_openlibs(L: *mut lua_State); + + /// Creates and returns a reference, in the table at index `t`, for the + /// object at the top of the stack (and pops the object). + /// *[-1, +0, m]* + /// + /// A reference is a unique integer key. As long as you do not manually add + /// integer keys into table t, `luaL_ref` ensures the uniqueness of the key + /// it returns. You can retrieve an object referred by reference r by + /// calling [`lua_rawgeti`]`(L, t, r)`. Function [`luaL_unref`] frees a + /// reference and its associated object. + /// + /// If the object at the top of the stack is nil, `luaL_ref` returns the + /// constant [`LUA_REFNIL`]. The constant [`LUA_NOREF`] is guaranteed to be + /// different from any reference returned by `luaL_ref`. + pub fn luaL_ref(l: *mut lua_State, t: c_int) -> c_int; + + /// Releases reference `r` from the table at index `t` (see [`luaL_ref`]). + /// The entry is removed from the table, so that the referred object can be + /// collected. The reference `r` is also freed to be used again. + /// *[-0, +0, -]* + /// + /// If ref is [`LUA_NOREF`] or [`LUA_REFNIL`], `luaL_unref` does nothing. + pub fn luaL_unref(l: *mut lua_State, t: c_int, r: c_int); } #[inline(always)] From 6179efe8ab76fe00eb2940fcc5d17c3d7e7e4e64 Mon Sep 17 00:00:00 2001 From: Georgy Moshkin Date: Tue, 26 Oct 2021 19:40:57 +0300 Subject: [PATCH 3/9] feat(lua_ffi): add raw table access function bindings Adding: - lua_rawget - lua_rawgeti - lua_rawset - lua_rawseti --- hlua/ffi/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/hlua/ffi/src/lib.rs b/hlua/ffi/src/lib.rs index 54abd24c..3f6d6e01 100644 --- a/hlua/ffi/src/lib.rs +++ b/hlua/ffi/src/lib.rs @@ -233,6 +233,17 @@ extern "C" { /// "index" event pub fn lua_gettable(l: *mut lua_State, index: c_int); + /// Similar to [`lua_gettable`], but does a raw access (i.e., without + /// metamethods). + /// *[-1, +1, -]* + pub fn lua_rawget(l: *mut lua_State, index: c_int); + + /// Pushes onto the stack the value `t[n]`, where `t` is the value at the + /// given valid `index`. The access is *raw*; that is, it does not invoke + /// metamethods. + /// *[-0, +1, -]* + pub fn lua_rawgeti(l: *mut lua_State, index: c_int, n: c_int); + /// Does the equivalent to `t[k] = v`, where `t` is the value at the given /// valid `index`, `v` is the value at the top of the stack, and `k` is the /// value just below the top. @@ -242,6 +253,19 @@ extern "C" { /// this function may trigger a metamethod for the "newindex" event. pub fn lua_settable(l: *mut lua_State, index: c_int); + /// Similar to [`lua_settable`], but does a raw assignment (i.e., without + /// metamethods). + /// *[-2, +0, m]* + pub fn lua_rawset(l: *mut lua_State, index: c_int); + + /// Does the equivalent of `t[n] = v`, where `t` is the value at the given + /// valid `index` and `v` is the value at the top of the stack. + /// *[-1, +0, m]* + /// + /// This function pops the value from the stack. The assignment is raw; that + /// is, it does not invoke metamethods. + pub fn lua_rawseti(l: *mut lua_State, index: c_int, n: c_int); + /// Returns the type of the value in the given acceptable `index`, or /// [`LUA_TNONE`] for a non-valid index (that is, an index to an "empty" /// stack position). The types returned by lua_type are coded by the From cb239bbc3c2627b55901116da4c2de5769aa5924 Mon Sep 17 00:00:00 2001 From: Georgy Moshkin Date: Tue, 26 Oct 2021 19:07:38 +0300 Subject: [PATCH 4/9] feat(fiber): implement deferred fibers without yields Current implementations uses the lua runtime to achieve this goal. This is necessary due to the limitation of tarantool ffi api. We are planning to fix this in the future by adding a single function to tarantool api. --- hlua/hlua/src/lib.rs | 5 +- hlua/hlua/src/macros.rs | 18 +++ hlua/hlua/src/userdata.rs | 37 +++++ tarantool/src/error.rs | 10 ++ tarantool/src/fiber.rs | 283 ++++++++++++++++++++++++++++++++++---- tests/src/test_fiber.rs | 8 +- 6 files changed, 333 insertions(+), 28 deletions(-) diff --git a/hlua/hlua/src/lib.rs b/hlua/hlua/src/lib.rs index d56f6ff9..04235096 100644 --- a/hlua/hlua/src/lib.rs +++ b/hlua/hlua/src/lib.rs @@ -125,9 +125,12 @@ pub use lua_tables::LuaTable; pub use lua_tables::LuaTableIterator; pub use tuples::TuplePushError; pub use userdata::UserdataOnStack; -pub use userdata::{push_userdata, read_userdata}; +pub use userdata::{push_userdata, read_userdata, push_some_userdata}; pub use values::StringInLua; +// Needed for `lua_error` macro +pub use ffi::luaL_error; + mod any; mod functions_write; mod lua_functions; diff --git a/hlua/hlua/src/macros.rs b/hlua/hlua/src/macros.rs index 4ca88f12..02f4cc12 100644 --- a/hlua/hlua/src/macros.rs +++ b/hlua/hlua/src/macros.rs @@ -60,3 +60,21 @@ macro_rules! implement_lua_read { } }; } + +#[macro_export] +macro_rules! c_ptr { + ($s:literal) => { + ::std::concat!($s, "\0").as_bytes().as_ptr() as *mut i8 + }; +} + +#[macro_export] +macro_rules! lua_error { + ($l:expr, $msg:literal) => { + { + $crate::luaL_error($l, c_ptr!($msg)); + unreachable!() + } + } +} + diff --git a/hlua/hlua/src/userdata.rs b/hlua/hlua/src/userdata.rs index dfc1e34d..11978bc5 100644 --- a/hlua/hlua/src/userdata.rs +++ b/hlua/hlua/src/userdata.rs @@ -13,8 +13,45 @@ use crate::{ LuaRead, InsideCallback, LuaTable, + c_ptr, }; +/// Pushes `value` of type `T` onto the stack as a userdata. The value is +/// put inside a `Option` so that it can be safely moved out of there. Useful +/// for example when passing `FnOnce` as a c closure, because it must be dropped +/// after the call. +/// *[0, +1, -]* +pub unsafe fn push_some_userdata(lua: *mut ffi::lua_State, value: T) { + type UDBox = Option; + let ud_ptr = ffi::lua_newuserdata(lua, std::mem::size_of::>()); + std::ptr::write(ud_ptr as *mut UDBox, Some(value)); + + if std::mem::needs_drop::() { + // Creating a metatable. + ffi::lua_newtable(lua); + + // Index "__gc" in the metatable calls the object's destructor. + ffi::lua_pushstring(lua, c_ptr!("__gc")); + ffi::lua_pushcfunction(lua, wrap_gc::); + ffi::lua_settable(lua, -3); + + ffi::lua_setmetatable(lua, -2); + } + + /// A callback for the "__gc" event. It checks if the value was moved out + /// and if not it drops the value. + unsafe extern "C" fn wrap_gc(lua: *mut ffi::lua_State) -> i32 { + let ud_ptr = ffi::lua_touserdata(lua, 1); + let ud = (ud_ptr as *mut UDBox) + .as_mut() + .expect("__gc called with userdata pointing to NULL"); + drop(ud.take()); + + 0 + } +} + + // Called when an object inside Lua is being dropped. #[inline] extern "C" fn destructor_wrapper(lua: *mut ffi::lua_State) -> libc::c_int { diff --git a/tarantool/src/error.rs b/tarantool/src/error.rs index 7151d216..c043ea4e 100644 --- a/tarantool/src/error.rs +++ b/tarantool/src/error.rs @@ -26,6 +26,7 @@ use rmp::decode::{MarkerReadError, NumValueReadError, ValueReadError}; use rmp::encode::ValueWriteError; use crate::ffi::tarantool as ffi; +use crate::hlua::LuaError; /// A specialized [`Result`] type for the crate pub type Result = std::result::Result; @@ -71,6 +72,9 @@ pub enum Error { #[cfg(feature = "net_box")] #[fail(display = "Sever respond with error: {}", _0)] Remote(crate::net_box::ResponseError), + + #[fail(display = "Lua error: {}", _0)] + LuaError(LuaError), } impl From for Error { @@ -142,6 +146,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: LuaError) -> Self { + Error::LuaError(error) + } +} + /// Transaction-related error cases #[derive(Debug, Fail)] pub enum TransactionError { diff --git a/tarantool/src/fiber.rs b/tarantool/src/fiber.rs index b28aec17..3f1c2505 100644 --- a/tarantool/src/fiber.rs +++ b/tarantool/src/fiber.rs @@ -15,10 +15,11 @@ use std::marker::PhantomData; use std::os::raw::c_void; use std::time::Duration; +use crate::hlua::{AsLua, c_ptr, lua_error}; use va_list::VaList; use crate::error::TarantoolError; -use crate::ffi::tarantool as ffi; +use crate::ffi::{tarantool as ffi, lua}; use crate::Result; /// *OBSOLETE*: This struct is being deprecated in favour of [`Immediate`], @@ -409,6 +410,248 @@ where } } +//////////////////////////////////////////////////////////////////////////////// +/// LuaFiber +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaFiber { + callee: C, +} + +impl LuaFiber +where + C: LuaCallee, + +{ + pub fn new(callee: C) -> Self { + Self { callee } + } + + pub fn spawn(self) -> Result { + let Self { callee } = self; + let fiber_ref = unsafe { + let l = ffi::luaT_state(); + // TODO don't require("fiber") everytime + lua::lua_getglobal(l, c_ptr!("require")); + lua::lua_pushstring(l, c_ptr!("fiber")); + if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN { + return Err(impl_details::lua_error_from_top(l).into()) + }; + lua::lua_getfield(l, -1, c_ptr!("new")); + hlua::push_some_userdata(l, callee.into_inner()); + lua::lua_pushcclosure(l, Self::trampoline, 1); + if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN { + return Err(impl_details::lua_error_from_top(l).into()) + }; + lua::lua_getfield(l, -1, c_ptr!("set_joinable")); + lua::lua_pushvalue(l, -2); + lua::lua_pushboolean(l, true as i32); + if lua::lua_pcall(l, 2, 0, 0) == lua::LUA_ERRRUN { + return Err(impl_details::lua_error_from_top(l).into()) + }; + let fiber_ref = lua::luaL_ref(l, lua::LUA_REGISTRYINDEX); + + // pop the fiber module from the stack + lua::lua_pop(l, 1); + + fiber_ref + }; + + Ok(C::join_handle(fiber_ref)) + } + + unsafe extern "C" fn trampoline(l: *mut lua::lua_State) -> i32 { + let ud_ptr = lua::lua_touserdata(l, lua::lua_upvalueindex(1)); + + let f = (ud_ptr as *mut Option).as_mut() + .unwrap_or_else(|| + // lua_touserdata returned NULL + lua_error!(l, "failed to extract upvalue") + ) + // put None back into userdata + .take() + .unwrap_or_else(|| + // userdata originally contained None + lua_error!(l, "rust FnOnce callback was called more than once") + ); + + // call f and drop it afterwards + let res = f(); + + // return results to lua + C::save_result(l, res) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaJoinHandle +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaJoinHandle { + fiber_ref: i32, + marker: PhantomData, +} + +impl LuaJoinHandle { + pub fn join(self) -> Result { + let Self { fiber_ref, .. } = self; + unsafe { + let guard = impl_details::lua_fiber_join(fiber_ref)?; + let l = guard.as_lua().state_ptr(); + let ud_ptr = lua::lua_touserdata(l, -1); + let res = (ud_ptr as *mut Option).as_mut() + .expect("fiber:join must return correct userdata") + .take() + .expect("data can only be taken once from the UDBox"); + Ok(res) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaUnitJoinHandle +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaUnitJoinHandle { + fiber_ref: i32, +} + +impl LuaUnitJoinHandle { + pub fn join(self) -> Result<()> { + let Self { fiber_ref, .. } = self; + unsafe { impl_details::lua_fiber_join(fiber_ref)? }; + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// impl_details +//////////////////////////////////////////////////////////////////////////////// + +mod impl_details { + use super::*; + use hlua::{AsMutLua, Lua, LuaError, PushGuard}; + + pub unsafe fn lua_error_from_top(l: *mut lua::lua_State) -> LuaError { + let mut len = std::mem::MaybeUninit::uninit(); + let data = lua::lua_tolstring(l, -1, len.as_mut_ptr()); + assert!(!data.is_null()); + let msg_bytes = std::slice::from_raw_parts( + data as *mut u8, len.assume_init() + ); + let msg = String::from_utf8_lossy(msg_bytes); + hlua::LuaError::ExecutionError(msg.into()).into() + } + + pub unsafe fn lua_fiber_join(f_ref: i32) -> Result>> { + let mut l = Lua::from_existing_state(ffi::luaT_state(), false); + let lptr = l.as_mut_lua().state_ptr(); + lua::lua_rawgeti(lptr, lua::LUA_REGISTRYINDEX, f_ref); + lua::lua_getfield(lptr, -1, c_ptr!("join")); + lua::lua_pushvalue(lptr, -2); + + if lua::lua_pcall(lptr, 1, 2, 0) == lua::LUA_ERRRUN { + let err = lua_error_from_top(lptr).into(); + // 2 values on the stack: + // 1) fiber; 2) error + let _guard = PushGuard::new(l, 2); + return Err(err) + }; + // 3 values on the stack that need to be dropped: + // 1) fiber; 2) join function; 3) fiber + let guard = PushGuard::new(l, 3); + + // check fiber return code + assert_ne!(lua::lua_toboolean(lptr, -2), 0); + + // fiber object and the 2 return values will need to be poped + Ok(guard) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaCallee +//////////////////////////////////////////////////////////////////////////////// + +pub trait LuaCallee { + /// Type of the callee + type Function: FnOnce() -> Self::Output; + + /// Return type of the callee + type Output; + + /// Type of the join handle returned by [`LuaFiber::spawn`] method + type JoinHandle; + + /// Extract the inner function + fn into_inner(self) -> Self::Function; + + /// Construct a `Self::JoinHandle` from a fiber reference + fn join_handle(fiber_ref: i32) -> Self::JoinHandle; + + /// This function is called within `LuaFiber::trampoline` to save the + /// return value after the callee's invocation + /// + /// This function is unsafe, because it is very easy to mess things up + /// when preparing arugments. + unsafe fn save_result(l: *mut lua::lua_State, res: Self::Output) -> i32; +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaFiberFunc +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaFiberFunc(pub F); + +impl LuaCallee for LuaFiberFunc +where + F: FnOnce() -> T, +{ + type Function = F; + type Output = T; + type JoinHandle = LuaJoinHandle; + + fn into_inner(self) -> F { + self.0 + } + + fn join_handle(fiber_ref: i32) -> Self::JoinHandle { + LuaJoinHandle { fiber_ref, marker: PhantomData } + } + + unsafe fn save_result(l: *mut lua::lua_State, res: T) -> i32 { + hlua::push_some_userdata(l, res); + 1 + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// LuaFiberProc +//////////////////////////////////////////////////////////////////////////////// + +pub struct LuaFiberProc(pub F); + +impl LuaCallee for LuaFiberProc +where + F: FnOnce(), +{ + type Function = F; + type Output = (); + type JoinHandle = LuaUnitJoinHandle; + + fn join_handle(fiber_ref: i32) -> Self::JoinHandle { + LuaUnitJoinHandle { fiber_ref } + } + + fn into_inner(self) -> F { + self.0 + } + + unsafe fn save_result(_: *mut lua::lua_State, _: ()) -> i32 { + 0 + } +} + //////////////////////////////////////////////////////////////////////////////// /// Callee //////////////////////////////////////////////////////////////////////////////// @@ -681,47 +924,39 @@ where Builder::new().proc(f).start().unwrap() } -#[cfg(any(feature = "defer", doc))] -/// Creates a new fiber and schedules it for exeution, returning a -/// [`JoinHandle`] for it. +/// Creates a new fiber and schedules it for execution, returning a +/// [`LuaJoinHandle`] for it. /// -/// **NOTE:** In the current implementation the current fiber performs a -/// **yield** to start the newly created fiber and then the new fiber -/// performs another **yield**. This means that the deferred fiber is **not -/// applicable for transactions** (which do not allow any context switches). -/// In the future we are planning to add a correct implementation. +/// **NOTE:** In the current implementation the fiber is constructed using the +/// lua api, so it's efficiency is far from perfect. /// /// **NOTE**: The argument `f` is a function that returns `T`. In case when `T = /// ()` (no return value) one should instead use [`defer_proc`]. /// -/// The new fiber can be joined by calling [`JoinHandle::join`] method on it's -/// join handle. -pub fn defer(f: F) -> JoinHandle +/// The new fiber can be joined by calling [`JuaJoinHandle::join`] method on +/// it's join handle. +pub fn defer(f: F) -> LuaJoinHandle where F: FnOnce() -> T, { - Builder::new().func(f).defer().unwrap() + LuaFiber::new(LuaFiberFunc(f)).spawn().unwrap() } -#[cfg(any(feature = "defer", doc))] -/// Creates a new proc fiber and schedules it for exeution, returning a -/// [`UnitJoinHandle`] for it. +/// Creates a new proc fiber and schedules it for execution, returning a +/// [`LuaUnitJoinHandle`] for it. /// -/// **NOTE:** In the current implementation the current fiber performs a -/// **yield** to start the newly created fiber and then the new fiber performs -/// another **yield**. This means that the deferred fiber is **not applicable -/// for transactions** (which do not allow any context switches). In the future -/// we are planning to add a correct implementation. +/// **NOTE:** In the current implementation the fiber is constructed using the +/// lua api, so it's efficiency is far from perfect. /// -/// The new fiber can be joined by calling [`UnitJoinHandle::join`] method on +/// The new fiber can be joined by calling [`LuaUnitJoinHandle::join`] method on /// it's join handle. /// /// This is an optimized version [`defer`]``. -pub fn defer_proc(f: F) -> UnitJoinHandle +pub fn defer_proc(f: F) -> LuaUnitJoinHandle where F: FnOnce(), { - Builder::new().proc(f).defer().unwrap() + LuaFiber::new(LuaFiberProc(f)).spawn().unwrap() } /// Make it possible or not possible to wakeup the current diff --git a/tests/src/test_fiber.rs b/tests/src/test_fiber.rs index e573c5ed..5db0bc55 100644 --- a/tests/src/test_fiber.rs +++ b/tests/src/test_fiber.rs @@ -246,7 +246,7 @@ pub fn test_deferred() { assert_eq!(jh.join(), 13); let jh = fiber::defer(|| 42); - assert_eq!(jh.join(), 42); + assert_eq!(jh.join().unwrap(), 42); } pub fn test_deferred_with_attrs() { @@ -273,7 +273,8 @@ pub fn test_multiple_deferred() { res.push(1); res.extend( fibers.into_iter() - .map(fiber::JoinHandle::join) + .map(fiber::LuaJoinHandle::join) + .map(Result::unwrap) .flatten() ); res.push(8); @@ -290,7 +291,7 @@ pub fn test_unit_deferred() { let res = std::cell::Cell::new(0); let jh = fiber::defer_proc(|| res.set(42)); assert_eq!(res.get(), 0); - jh.join(); + jh.join().unwrap(); assert_eq!(res.get(), 42); } @@ -320,6 +321,7 @@ pub fn test_multiple_unit_deferred() { res.borrow_mut().push(1); for f in fibers { f.join() + .unwrap() } res.borrow_mut().push(8); let res = res.borrow().iter().copied().collect::>(); From bea368e194d50b8aef064070b8a8efdef9d4be4e Mon Sep 17 00:00:00 2001 From: Georgy Moshkin Date: Thu, 28 Oct 2021 12:02:47 +0300 Subject: [PATCH 5/9] chore(fiber): add tests checking which fibers yield --- tests/src/lib.rs | 3 +++ tests/src/test_fiber.rs | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 20952bab..2caa491b 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -284,6 +284,9 @@ fn run_tests(cfg: TestConfig) -> Result { test_fiber::test_unit_deferred, test_fiber::test_unit_deferred_with_attrs, test_fiber::test_multiple_unit_deferred, + test_fiber::deferred_doesnt_yield, + test_fiber::immediate_yields, + test_box::test_space_get_by_name, test_box::test_space_get_system, test_box::test_index_get_by_name, diff --git a/tests/src/test_fiber.rs b/tests/src/test_fiber.rs index 5db0bc55..b34d622d 100644 --- a/tests/src/test_fiber.rs +++ b/tests/src/test_fiber.rs @@ -7,6 +7,7 @@ use std::{ use tarantool::fiber::{ self, fiber_yield, is_cancelled, sleep, Cond, Fiber, FiberAttr }; +use tarantool::{space, transaction, error::{Error, TransactionError}}; pub fn test_fiber_new() { let mut fiber = Fiber::new("test_fiber", &mut |_| 0); @@ -327,3 +328,41 @@ pub fn test_multiple_unit_deferred() { let res = res.borrow().iter().copied().collect::>(); assert_eq!(res, vec![1, 2, 3, 4, 5, 6, 7, 8]); } + +pub fn immediate_yields() { + let mut space = space::Space::find("test_s1").unwrap(); + space.truncate().unwrap(); + + let mut fib = None; + + let result = transaction::start_transaction(|| -> Result<(), Error> { + space.insert(&(1, "test".to_string()))?; + fib = Some(fiber::start(|| 69)); + Ok(()) + }); + + assert!(matches!( + result, + Err(Error::Transaction(TransactionError::FailedToCommit)), + )); + + assert_eq!(fib.map(|f| f.join()), Some(69)) +} + +pub fn deferred_doesnt_yield() { + let mut space = space::Space::find("test_s1").unwrap(); + space.truncate().unwrap(); + + let mut fib = None; + + let result = transaction::start_transaction(|| -> Result<(), Error> { + space.insert(&(1, "test".to_string()))?; + fib = Some(fiber::defer(|| 69)); + Ok(()) + }); + + assert!(result.is_ok()); + + assert!(matches!(fib.map(|f| f.join()), Some(Ok(69)))) +} + From efaaa265c90be00adf008361c94585a952872bcf Mon Sep 17 00:00:00 2001 From: Yaroslav Dynnikov Date: Thu, 28 Oct 2021 20:10:38 +0300 Subject: [PATCH 6/9] Simplify fiber yield testing --- tests/src/test_fiber.rs | 62 ++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/tests/src/test_fiber.rs b/tests/src/test_fiber.rs index b34d622d..e163d4bc 100644 --- a/tests/src/test_fiber.rs +++ b/tests/src/test_fiber.rs @@ -7,7 +7,10 @@ use std::{ use tarantool::fiber::{ self, fiber_yield, is_cancelled, sleep, Cond, Fiber, FiberAttr }; -use tarantool::{space, transaction, error::{Error, TransactionError}}; +use tarantool::hlua::{ + Lua, + LuaFunction +}; pub fn test_fiber_new() { let mut fiber = Fiber::new("test_fiber", &mut |_| 0); @@ -329,40 +332,43 @@ pub fn test_multiple_unit_deferred() { assert_eq!(res, vec![1, 2, 3, 4, 5, 6, 7, 8]); } -pub fn immediate_yields() { - let mut space = space::Space::find("test_s1").unwrap(); - space.truncate().unwrap(); - - let mut fib = None; +fn fiber_csw() -> i32 { + static mut FLAG: bool = false; + let mut lua: Lua = crate::hlua::global(); + + if unsafe { !FLAG } { + lua.execute::<()>(r#" + function fiber_csw() + local fiber = require('fiber') + return fiber.info()[fiber.id()].csw + end + "#).unwrap(); + unsafe { FLAG = true; } + } - let result = transaction::start_transaction(|| -> Result<(), Error> { - space.insert(&(1, "test".to_string()))?; - fib = Some(fiber::start(|| 69)); - Ok(()) - }); + return lua.get::, _>("fiber_csw").unwrap().call().unwrap(); +} - assert!(matches!( - result, - Err(Error::Transaction(TransactionError::FailedToCommit)), - )); +pub fn immediate_yields() { + let mut upvalue = 0; + let csw1 = fiber_csw(); + fiber::start(|| upvalue = 69); + let csw2 = fiber_csw(); - assert_eq!(fib.map(|f| f.join()), Some(69)) + assert_eq!(upvalue, 69); + assert_eq!(csw2, csw1+1); } pub fn deferred_doesnt_yield() { - let mut space = space::Space::find("test_s1").unwrap(); - space.truncate().unwrap(); - - let mut fib = None; - - let result = transaction::start_transaction(|| -> Result<(), Error> { - space.insert(&(1, "test".to_string()))?; - fib = Some(fiber::defer(|| 69)); - Ok(()) - }); + let mut upvalue = 0; + let csw1 = fiber_csw(); + fiber::defer(|| upvalue = 96); + let csw2 = fiber_csw(); - assert!(result.is_ok()); + assert_eq!(upvalue, 0); + assert_eq!(csw2, csw1); - assert!(matches!(fib.map(|f| f.join()), Some(Ok(69)))) + fiber::sleep(0.); + assert_eq!(upvalue, 96); } From 5d145bbcacac5ac37614ece9bf375264a19ba390 Mon Sep 17 00:00:00 2001 From: Yaroslav Dynnikov Date: Fri, 29 Oct 2021 11:50:51 +0300 Subject: [PATCH 7/9] Fix doc warnings --- hlua/ffi/src/lib.rs | 14 +++++++------- hlua/hlua/src/lib.rs | 22 +++++++++++----------- tarantool/src/fiber.rs | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/hlua/ffi/src/lib.rs b/hlua/ffi/src/lib.rs index 3f6d6e01..00b1e927 100644 --- a/hlua/ffi/src/lib.rs +++ b/hlua/ffi/src/lib.rs @@ -1,13 +1,13 @@ +//! Module provides FFI bindings for the following constants, +//! types and functions, realted to Lua C API: +//! 1. Plain lua C API +//! 2. lauxlib +//! 3. Lua utitlites, implemented in Tarantool + #![allow(non_camel_case_types)] use std::os::raw::{c_double, c_int, c_schar}; use std::ptr::null_mut; -/// Module provides FFI bindings for the following constants, -/// types and functions, realted to Lua C API: -/// 1. Plain lua C API -/// 2. lauxlib -/// 3. Lua utitlites, implemented in Tarantool - /// Lua provides a registry, a pre-defined table that can be used by any C code /// to store whatever Lua value it needs to store. This table is always located /// at pseudo-index `LUA_REGISTRYINDEX`. Any C library can store data into this @@ -70,7 +70,7 @@ pub type lua_Integer = libc::ptrdiff_t; /// (the first argument is pushed first). So, when the function starts, /// [`lua_gettop`]`(L)` returns the number of arguments received by the function. /// The first argument (if any) is at index 1 and its last argument is at index -/// [`lua_gettop`](L). To return values to Lua, a C function just pushes them +/// [`lua_gettop`]`(L)`. To return values to Lua, a C function just pushes them /// onto the stack, in direct order (the first result is pushed first), and /// returns the number of results. Any other value in the stack below the /// results will be properly discarded by Lua. Like a Lua function, a C function diff --git a/hlua/hlua/src/lib.rs b/hlua/hlua/src/lib.rs index 04235096..7fe2a2cf 100644 --- a/hlua/hlua/src/lib.rs +++ b/hlua/hlua/src/lib.rs @@ -372,7 +372,7 @@ pub trait PushOne: Push {} /// Type that cannot be instantiated. /// -/// Will be replaced with `!` eventually (https://github.com/rust-lang/rust/issues/35121). +/// Will be replaced with `!` eventually (). #[derive(Debug, Copy, Clone)] pub enum Void {} @@ -523,7 +523,7 @@ impl<'lua> Lua<'lua> { /// Opens all standard Lua libraries. /// /// See the reference for the standard library here: - /// https://www.lua.org/manual/5.2/manual.html#6 + /// /// /// This is done by calling `luaL_openlibs`. /// @@ -541,7 +541,7 @@ impl<'lua> Lua<'lua> { /// Opens base library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_base + /// #[inline] pub fn open_base(&mut self) { unsafe { ffi::luaopen_base(self.lua.0) } @@ -549,7 +549,7 @@ impl<'lua> Lua<'lua> { /// Opens bit32 library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_bit32 + /// #[inline] pub fn open_bit(&mut self) { unsafe { ffi::luaopen_bit(self.lua.0) } @@ -557,7 +557,7 @@ impl<'lua> Lua<'lua> { /// Opens debug library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_debug + /// #[inline] pub fn open_debug(&mut self) { unsafe { ffi::luaopen_debug(self.lua.0) } @@ -565,7 +565,7 @@ impl<'lua> Lua<'lua> { /// Opens io library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_io + /// #[inline] pub fn open_io(&mut self) { unsafe { ffi::luaopen_io(self.lua.0) } @@ -573,7 +573,7 @@ impl<'lua> Lua<'lua> { /// Opens math library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_math + /// #[inline] pub fn open_math(&mut self) { unsafe { ffi::luaopen_math(self.lua.0) } @@ -581,7 +581,7 @@ impl<'lua> Lua<'lua> { /// Opens os library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_os + /// #[inline] pub fn open_os(&mut self) { unsafe { ffi::luaopen_os(self.lua.0) } @@ -589,7 +589,7 @@ impl<'lua> Lua<'lua> { /// Opens package library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_package + /// #[inline] pub fn open_package(&mut self) { unsafe { ffi::luaopen_package(self.lua.0) } @@ -597,7 +597,7 @@ impl<'lua> Lua<'lua> { /// Opens string library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_string + /// #[inline] pub fn open_string(&mut self) { unsafe { ffi::luaopen_string(self.lua.0) } @@ -605,7 +605,7 @@ impl<'lua> Lua<'lua> { /// Opens table library. /// - /// https://www.lua.org/manual/5.2/manual.html#pdf-luaopen_table + /// #[inline] pub fn open_table(&mut self) { unsafe { ffi::luaopen_table(self.lua.0) } diff --git a/tarantool/src/fiber.rs b/tarantool/src/fiber.rs index 3f1c2505..dbe23683 100644 --- a/tarantool/src/fiber.rs +++ b/tarantool/src/fiber.rs @@ -933,7 +933,7 @@ where /// **NOTE**: The argument `f` is a function that returns `T`. In case when `T = /// ()` (no return value) one should instead use [`defer_proc`]. /// -/// The new fiber can be joined by calling [`JuaJoinHandle::join`] method on +/// The new fiber can be joined by calling [`LuaJoinHandle::join`] method on /// it's join handle. pub fn defer(f: F) -> LuaJoinHandle where From e09d688c306a0e778c60537108e45febcdb67355 Mon Sep 17 00:00:00 2001 From: Yaroslav Dynnikov Date: Fri, 29 Oct 2021 16:01:07 +0300 Subject: [PATCH 8/9] Enhance testing and fix bugs --- tarantool/src/fiber.rs | 11 ++-- tests/src/lib.rs | 2 + tests/src/test_fiber.rs | 124 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 128 insertions(+), 9 deletions(-) diff --git a/tarantool/src/fiber.rs b/tarantool/src/fiber.rs index dbe23683..e68162f6 100644 --- a/tarantool/src/fiber.rs +++ b/tarantool/src/fiber.rs @@ -435,22 +435,25 @@ where lua::lua_getglobal(l, c_ptr!("require")); lua::lua_pushstring(l, c_ptr!("fiber")); if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN { - return Err(impl_details::lua_error_from_top(l).into()) + let ret = Err(impl_details::lua_error_from_top(l).into()); + lua::lua_pop(l, 1); + return ret }; lua::lua_getfield(l, -1, c_ptr!("new")); hlua::push_some_userdata(l, callee.into_inner()); lua::lua_pushcclosure(l, Self::trampoline, 1); if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN { - return Err(impl_details::lua_error_from_top(l).into()) + let ret = Err(impl_details::lua_error_from_top(l).into()); + lua::lua_pop(l, 2); + return ret }; lua::lua_getfield(l, -1, c_ptr!("set_joinable")); lua::lua_pushvalue(l, -2); lua::lua_pushboolean(l, true as i32); if lua::lua_pcall(l, 2, 0, 0) == lua::LUA_ERRRUN { - return Err(impl_details::lua_error_from_top(l).into()) + panic!("{}", impl_details::lua_error_from_top(l)) }; let fiber_ref = lua::luaL_ref(l, lua::LUA_REGISTRYINDEX); - // pop the fiber module from the stack lua::lua_pop(l, 1); diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 2caa491b..cc9e408b 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -286,6 +286,8 @@ fn run_tests(cfg: TestConfig) -> Result { test_fiber::test_multiple_unit_deferred, test_fiber::deferred_doesnt_yield, test_fiber::immediate_yields, + test_fiber::start_error, + test_fiber::require_error, test_box::test_space_get_by_name, test_box::test_space_get_system, diff --git a/tests/src/test_fiber.rs b/tests/src/test_fiber.rs index e163d4bc..d695da4b 100644 --- a/tests/src/test_fiber.rs +++ b/tests/src/test_fiber.rs @@ -8,10 +8,13 @@ use tarantool::fiber::{ self, fiber_yield, is_cancelled, sleep, Cond, Fiber, FiberAttr }; use tarantool::hlua::{ + AsMutLua, Lua, LuaFunction }; +use tarantool::ffi::lua; + pub fn test_fiber_new() { let mut fiber = Fiber::new("test_fiber", &mut |_| 0); fiber.set_joinable(true); @@ -333,23 +336,57 @@ pub fn test_multiple_unit_deferred() { } fn fiber_csw() -> i32 { - static mut FLAG: bool = false; + static mut FUNCTION_DEFINED: bool = false; let mut lua: Lua = crate::hlua::global(); - if unsafe { !FLAG } { + if unsafe { !FUNCTION_DEFINED } { lua.execute::<()>(r#" function fiber_csw() - local fiber = require('fiber') - return fiber.info()[fiber.id()].csw + local fiber = require('fiber') + return fiber.info()[fiber.id()].csw end "#).unwrap(); - unsafe { FLAG = true; } + unsafe { FUNCTION_DEFINED = true; } } return lua.get::, _>("fiber_csw").unwrap().call().unwrap(); } +struct LuaStackIntegrityGuard { + name: &'static str, +} + +impl LuaStackIntegrityGuard { + fn new(name: &'static str) -> Self { + let mut lua: Lua = crate::hlua::global(); + let l = lua.as_mut_lua().state_ptr(); + unsafe { lua::lua_pushlstring(l, name.as_bytes().as_ptr() as *mut i8, name.len()) }; + Self{name} + } +} + +impl Drop for LuaStackIntegrityGuard { + fn drop(&mut self) { + let mut lua: Lua = crate::hlua::global(); + let l = lua.as_mut_lua().state_ptr(); + + let msg = unsafe { + let cstr = lua::lua_tostring(l, -1); + if cstr.is_null() { + panic!("Lua stack integrity violation"); + } + let msg = std::ffi::CStr::from_ptr(cstr).to_str().unwrap(); + lua::lua_pop(l, 1); + msg + }; + + assert_eq!(msg, self.name); + } +} + pub fn immediate_yields() { + let _guard = LuaStackIntegrityGuard::new("immediate_fiber_guard"); + let mut upvalue = 0; let csw1 = fiber_csw(); fiber::start(|| upvalue = 69); @@ -360,6 +397,8 @@ pub fn immediate_yields() { } pub fn deferred_doesnt_yield() { + let _guard = LuaStackIntegrityGuard::new("deferred_fiber_guard"); + let mut upvalue = 0; let csw1 = fiber_csw(); fiber::defer(|| upvalue = 96); @@ -372,3 +411,78 @@ pub fn deferred_doesnt_yield() { assert_eq!(upvalue, 96); } +pub fn start_error() { + let _guard = LuaStackIntegrityGuard::new("fiber_error_guard"); + + let _spoiler = LuaContextSpoiler::new(); + + match fiber::LuaFiber::new(fiber::LuaFiberFunc(|| ())).spawn() { + Err(e) => assert_eq!( + format!("{}", e), + "Lua error: Execution error: Artificial error" + ), + _ => panic!(), + } + + struct LuaContextSpoiler; + + impl LuaContextSpoiler { + fn new() -> Self { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + _fiber_new_backup = package.loaded.fiber.new + package.loaded.fiber.new = function() error("Artificial error", 0) end + "#).unwrap(); + Self + } + } + + impl Drop for LuaContextSpoiler { + fn drop(&mut self) { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + package.loaded.fiber.new = _fiber_new_backup + _fiber_new_backup = nil + "#).unwrap(); + } + } +} + +pub fn require_error() { + let _guard = LuaStackIntegrityGuard::new("fiber_error_guard"); + + let _spoiler = LuaContextSpoiler::new(); + + match fiber::LuaFiber::new(fiber::LuaFiberFunc(|| ())).spawn() { + Err(e) => assert_eq!( + format!("{}", e), + "Lua error: Execution error: Artificial require error" + ), + _ => panic!(), + } + + struct LuaContextSpoiler; + + impl LuaContextSpoiler { + fn new() -> Self { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + _fiber_backup = package.loaded.fiber + package.loaded.fiber = nil + package.preload.fiber = function() error("Artificial require error", 0) end + "#).unwrap(); + Self + } + } + + impl Drop for LuaContextSpoiler { + fn drop(&mut self) { + let mut lua: Lua = crate::hlua::global(); + lua.execute::<()>(r#" + package.preload.fiber = nil + package.loaded.fiber = _fiber_backup + _fiber_backup = nil + "#).unwrap(); + } + } +} From 5cbe60ecbab7236cbc23e9ec3e9d57994101739c Mon Sep 17 00:00:00 2001 From: Georgy Moshkin Date: Fri, 29 Oct 2021 18:04:17 +0300 Subject: [PATCH 9/9] chore(fiber): add comment explaining the situation --- tarantool/src/fiber.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tarantool/src/fiber.rs b/tarantool/src/fiber.rs index e68162f6..1b094381 100644 --- a/tarantool/src/fiber.rs +++ b/tarantool/src/fiber.rs @@ -418,6 +418,11 @@ pub struct LuaFiber { callee: C, } +/// Deferred non-yielding fiber implemented using **lua** api. This (hopefully) +/// temporary implementation is a workaround. Tarantool C API lacks the method +/// for passing the necessary information into the underlying `struct fiber` +/// reliably. In this case we need to be able to set the `void *f_arg` field to +/// be able to implement correct deferred fibers which don't yield. impl LuaFiber where C: LuaCallee, @@ -431,7 +436,6 @@ where let Self { callee } = self; let fiber_ref = unsafe { let l = ffi::luaT_state(); - // TODO don't require("fiber") everytime lua::lua_getglobal(l, c_ptr!("require")); lua::lua_pushstring(l, c_ptr!("fiber")); if lua::lua_pcall(l, 1, 1, 0) == lua::LUA_ERRRUN {