Using neovim/libuv's AsyncHandle from other threads #586
-
| I'm writing a neovim plugin that loads an mlua module. The plugin requires a tokio threadpool, so I need some way to get around the single-threaded nature of lua. My idea was to use neovim's async primitives, specifically a libuv async handle. The handle is a userdata function can be called from any thread to wake up the main neovim thread. My approach looks basically like this: // Create a channel that will accumulate events until they can be consumed by the main thread
let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel();
// Get a function that can create a uv_async_t for us
let create_handle = lua
  .load(
    r#"
    function(cb)
      return vim.uv.new_async(function()
        vim.schedule(cb)
      end)
    end
    "#,
  )
  .eval::<Function>()?;
let cb = lua.create_function(move |lua, ()| {
  // Just print out events for now
  let print = lua.globals().get::<Function>("print")?;
  // Inside the callback, we'll consume all events
  while let Ok(event) = event_rx.try_recv() {
    print.call::<()>("received an event!")?;
  }
  Ok(())
})?;
// Create the handle
let handle = create_handle.call::<AnyUserData>(cb)?;
// Sending events:
event_tx.send(...);
// Notify the async handle
handle.call_method::<()>("send", ())?;This works fine until I call send on the handle from another thread. After looking into the mlua source, I think I've realized the error here: While  My question is: Is there any way to call the handle safely from another thread? Could I somehow call it without having to lock the lua runtime? Can I "extract" the  | 
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
| Ok, so this is slightly insane, but the following works perfectly: use std::ffi::{CString, c_int, c_void};
use mlua::{AnyUserData, Function, Lua, Result};
use crate::lua_error;
/// A libuv [`uv_async_t`][1] handle that can be used to wake up the main thread and call a function.
/// The passed callback is automatically wrapped in `vim.schedule`.
///
/// [1]: https://docs.libuv.org/en/v1.x/async.html
#[derive(Clone)]
pub struct AsyncHandle(*mut c_void, AnyUserData);
// The `uv_async_t` is safe to be called from any thread.
// However, the AnyUserData reference must be called from the main thread, so all functions that
// act on it should accept a Lua handle and use it to call the function, since those can only be
// accquired on the main thread.
unsafe impl Send for AsyncHandle {}
// We don't need to get any information out of the handle, so we treat it as opaque
unsafe extern "C" {
  fn uv_async_send(uv_async_t: *mut c_void) -> c_int;
}
const CREATE_ASYNC_HANDLE: &str = r#"
function(cb)
  return vim.uv.new_async(function()
    vim.schedule(cb)
  end)
end
"#;
const CLOSE_ASYNC_HANDLE: &str = r#"
function(handle)
  handle:close()
end
"#;
impl AsyncHandle {
  pub fn new<F>(lua: &Lua, cb: F) -> Result<Self>
  where
    F: Fn(&Lua, ()) -> Result<()> + 'static,
  {
    let cb = lua.create_function(cb)?;
    let create_handle = lua.load(CREATE_ASYNC_HANDLE).eval::<Function>()?;
    let handle = create_handle.call::<AnyUserData>(cb)?;
    let typename = CString::new("uv_async").expect("create cstring");
    let mut uv_async: *mut c_void = std::ptr::null_mut();
    unsafe {
      lua.exec_raw::<()>(&handle, |lstate| {
        // The arg should always be at 1, but let's be safe
        let index = mlua::ffi::lua_gettop(lstate);
        // Make sure the userdata is actually `uv_async`
        let ud = mlua::ffi::luaL_checkudata(lstate, index, typename.as_ptr());
        if ud.is_null() {
          return;
        }
        // We get a pointer the userdata, which is in turn a pointer to `uv_async_t`.
        uv_async = *(ud as *mut *mut c_void);
      })?;
    }
    if uv_async.is_null() {
      lua_error!("failed to get uv_async handle");
    }
    Ok(AsyncHandle(uv_async, handle))
  }
  pub fn send(&self) -> Result<()> {
    let result = unsafe { uv_async_send(self.0) };
    if result < 0 {
      lua_error!("uv_async_send failed");
    }
    Ok(())
  }
  pub fn close(self, lua: &Lua) -> Result<()> {
    let close_handle = lua.load(CLOSE_ASYNC_HANDLE).eval::<Function>()?;
    close_handle.call::<()>(self.1)?;
    Ok(())
  }
}Still interested in any feedback or other solutions! | 
Beta Was this translation helpful? Give feedback.
Ok, so this is slightly insane, but the following works perfectly: