diff --git a/src/main.rs b/src/main.rs index dcaf14fe81f76147a6b28821aba90c3e47bd0c30..33a1270284501513fbb0f6b95afc01b15e522b64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,45 +10,29 @@ use ::tarantool::set_error; use ::tarantool::tlua; use ::tarantool::tuple::{FunctionArgs, FunctionCtx, Tuple}; use indoc::indoc; +use message::Message; +use std::convert::TryFrom; +use std::time::Duration; mod app; mod args; mod error; mod message; -pub mod stash; mod tarantool; mod tlog; mod traft; +inventory::collect!(InnerTest); + pub struct InnerTest { pub name: &'static str, pub body: fn(), } -inventory::collect!(InnerTest); -use message::Message; -use std::cell::Ref; -use std::cell::RefCell; -use std::convert::TryFrom; -use std::time::Duration; +static mut NODE: Option<&'static traft::Node> = None; -#[derive(Default)] -pub struct Stash { - raft_node: RefCell<Option<traft::Node>>, -} - -impl Stash { - pub fn access() -> &'static Self { - stash::access("_picolib_stash") - } - - pub fn set_raft_node(&self, raft_node: traft::Node) { - *self.raft_node.borrow_mut() = Some(raft_node); - } - - pub fn raft_node(&self) -> Ref<Option<traft::Node>> { - self.raft_node.borrow() - } +fn node() -> &'static traft::Node { + unsafe { NODE.expect("Picodata not running yet") } } fn picolib_setup(args: args::Run) { @@ -79,17 +63,17 @@ fn picolib_setup(args: args::Run) { // // Export public API luamod.set("run", tlua::function0(move || start(&args))); - luamod.set("raft_status", tlua::function0(raft_status)); + luamod.set("raft_status", tlua::function0(|| node().status())); luamod.set("raft_tick", tlua::function1(raft_tick)); luamod.set( "raft_propose_info", - tlua::function1(|x: String| raft_propose(Message::Info { msg: x })), + tlua::function1(|x: String| node().propose(&Message::Info { msg: x })), ); luamod.set( "raft_propose_eval", tlua::function2(|timeout: f64, x: String| { - raft_propose_wait_applied( - Message::EvalLua { code: x }, + node().propose_wait_applied( + &Message::EvalLua { code: x }, Duration::from_secs_f64(timeout), ) }), @@ -110,8 +94,6 @@ fn picolib_setup(args: args::Run) { } fn start(args: &args::Run) { - let stash = Stash::access(); - if tarantool::cfg().is_some() { // Already initialized return; @@ -175,8 +157,12 @@ fn start(args: &args::Run) { applied: traft::Storage::applied().unwrap().unwrap_or_default(), ..Default::default() }; - let node = traft::Node::new(&raft_cfg, handle_committed_data).unwrap(); - stash.set_raft_node(node); + + unsafe { + NODE = Some(Box::leak(Box::new( + traft::Node::new(&raft_cfg, handle_committed_data).unwrap(), + ))); + }; cfg.listen = Some(args.listen.clone()); tarantool::set_cfg(&cfg); @@ -191,35 +177,12 @@ fn start(args: &args::Run) { } fn raft_tick(n_times: u32) { - let stash = Stash::access(); - let raft_ref = stash.raft_node(); - let raft_node = raft_ref.as_ref().expect("Picodata not running yet"); + let raft_node = node(); for _ in 0..n_times { raft_node.tick(); } } -fn raft_status() -> traft::Status { - let stash = Stash::access(); - let raft_ref = stash.raft_node(); - let raft_node = raft_ref.as_ref().expect("Picodata not running yet"); - raft_node.status() -} - -fn raft_propose(msg: Message) { - let stash = Stash::access(); - let raft_ref = stash.raft_node(); - let raft_node = raft_ref.as_ref().expect("Picodata not running yet"); - raft_node.propose(&msg) -} - -fn raft_propose_wait_applied(msg: Message, timeout: Duration) -> bool { - let stash = Stash::access(); - let raft_ref = stash.raft_node(); - let raft_node = raft_ref.as_ref().expect("Picodata not running yet"); - raft_node.propose_wait_applied(&msg, timeout) -} - fn handle_committed_data(data: &[u8]) { use Message::*; @@ -246,11 +209,7 @@ fn init_handlers() { #[no_mangle] pub extern "C" fn raft_interact(_: FunctionCtx, args: FunctionArgs) -> c_int { - let stash = Stash::access(); - let raft_ref = stash.raft_node(); - let raft_node = raft_ref - .as_ref() - .expect("picodata should already be running"); + let raft_node = node(); // Conversion pipeline: // FunctionArgs -> Tuple -?-> traft::row::Message -?-> raft::Message; diff --git a/src/stash.rs b/src/stash.rs deleted file mode 100644 index 51246f03ce887bbe17820c11b512400a57d88526..0000000000000000000000000000000000000000 --- a/src/stash.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Tarantool-flavored singleton pattern implementation. -//! -//! Tarantool has a peculiarity - when calling stored procedures with `{language = "C"}` option, it -//! disregards the shared object (`.so` / `.dylib`) already loaded by Lua and makes the second -//! independent `dlopen`. As a result, Lua and C stored procedures can't share state, because even -//! static variables point to different memory locations. -//! -//! As a workaround, this module provides the API for hiding the state inside `lua_State`. Under the -//! hood, the stash consumes a custom struct and leaks the wrapping box. Inside Lua, it's -//! represented by a userdata, which the `access()` function provides access to. -//! -//! Example: -//! -//! ``` -//! use std::cell::Ref; -//! use std::cell::RefCell; -//! -//! /// The particular singleton structure -//! #[derive(Default)] -//! struct Stash { -//! x: RefCell<u32>, -//! } -//! -//! unsafe impl Send for Stash {} -//! unsafe impl Sync for Stash {} -//! impl Stash { -//! pub fn access() -> &'static Self { -//! stash::access("my_stash") -//! } -//! pub fn set_x(&self, x: u32) { -//! *self.x.borrow_mut() = x; -//! } -//! pub fn x(&self) -> Ref<u32> { -//! self.x.borrow() -//! } -//! } -//! -//! #[no_mangle] -//! pub extern "C" fn luaopen_easy(_l: std::ffi::c_void) -> std::os::raw::c_int { -//! // Tarantool calls this function upon require("easy") -//! let stash = Stash::access(); -//! stash.set_x(100); -//! assert_eq!(*stash.x(), 100); -//! 0 -//! } -//! ``` -//! - -use ::tarantool::tlua; -use std::ops::Deref; - -#[derive(Default)] -struct Inner<S>(S); - -impl<S> std::ops::Deref for Inner<S> { - type Target = S; - - #[inline] - fn deref(&self) -> &S { - &self.0 - } -} - -impl<L, S> tlua::PushInto<L> for Inner<S> -where - L: tlua::AsLua, - S: 'static, -{ - type Err = tlua::Void; - fn push_into_lua(self, lua: L) -> Result<tlua::PushGuard<L>, (tlua::Void, L)> { - let boxed = Box::new(self); - let ptr: &'static Inner<S> = Box::leak(boxed); - Ok(tlua::push_userdata(ptr, lua, |_| {})) - } -} - -impl<L, S> tlua::PushOneInto<L> for Inner<S> -where - L: tlua::AsLua, - S: 'static, -{ -} - -impl<L, S> tlua::LuaRead<L> for &Inner<S> -where - L: tlua::AsLua, -{ - fn lua_read_at_position(lua: L, index: std::num::NonZeroI32) -> Result<&'static Inner<S>, L> { - let val: tlua::UserdataOnStack<&Inner<S>, _> = - tlua::LuaRead::lua_read_at_position(lua, index)?; - Ok(val.deref()) - } -} - -/// Returns reference to the particular singleton structure. -/// -/// When called for the first time, initializes it with the default values. -pub fn access<S>(name: &str) -> &'static S -where - S: Default, -{ - // TODO: this place is a potential performance bottleneck. - // Every access to the stash implies a call to `lua_newthread()` - // and further garbage collection. - let l = tarantool::lua_state(); - let get = || l.get::<&Inner<S>, _>(name); - match get() { - Some(Inner(v)) => v, - None => { - l.set(name, Inner::<S>::default()); - get().expect("impossible") - } - } -}