我在想,我的 idea 是否可以实现:一个完全支持异步 rust 蚀的dynamic library plugin manager.
实施分为三个主要部分(github repo):
-
my-interface
、async_trait
(具有"正常"特征的方法似乎工作得很好,不知道在多大程度上)use async_trait::async_trait; #[async_trait] pub trait SayHelloService { async fn say_hello(&self); }
-
my-master
,加载.dll
/.so
并调用库中的创建函数,该函数在给定tokio::runtime::Handle
的情况下返回Box<dyn SayHelloService>
use my_interface::SayHelloService; use tokio::{self, runtime::Handle}; #[tokio::main] async fn main() { let lib = libloading::Library::new("target/debug/libmy_plugin.so").expect("load library"); let new_service: libloading::Symbol<fn(Handle) -> Box<dyn SayHelloService>> = unsafe { lib.get(b"new_service") }.expect("load symbol"); let service1 = new_service(Handle::current()); let service2 = new_service(Handle::current()); let _ = tokio::join!(service1.say_hello(), service2.say_hello()); }
-
my-plugin
,实现SayHelloService
,这是阻止我的代码use async_trait::async_trait; use my_interface::SayHelloService; use tokio::{self, runtime::Handle}; #[no_mangle] pub fn new_service(handle: Handle) -> Box<dyn SayHelloService> { Box::new(PluginSayHello::new(handle)) } pub struct PluginSayHello { id: String, handle: Handle, } impl PluginSayHello { fn new(handle: Handle) -> PluginSayHello { let id = format!("{:08x}", rand::random::<u32>()); println!("[{}] Created instance!", id); PluginSayHello { id, handle } } } #[async_trait] impl SayHelloService for PluginSayHello { // this errors with "future cannot be sent between threads safely" async fn say_hello(&self) { // this should enable you to call tokio::sleep but EnterGuard is not Send :( // https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.html#method.enter let _guard = self.handle.enter(); println!("[{}] Hello from plugin!", self.id); let _ = tokio::spawn(async move { let _ = tokio::time::sleep(std::time::Duration::from_secs(1)).await; println!("sleep 1"); }) .await; } }
Not being able to call self.handle.enter()
creates all sorts of strange behavior for example:
(if you want more info I'll attach the crash log)
#[async_trait]
impl SayHelloService for PluginSayHello {
async fn say_hello(&self) {
let id = self.id.clone();
let _ = self
.handle
.spawn_blocking(move || {
println!("[{}] Hello from plugin!", id);
// internal code of reqwest just crashes
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("body = {:?}", body);
})
.await;
}
}
我还实现了PluginSayHello
的工作实现,但对我来说这并不像是一个完全的胜利.
#[async_trait]
impl SayHelloService for PluginSayHello {
async fn say_hello(&self) {
let id = self.id.clone();
let _ = self
.handle
.spawn(async move {
println!("[{}] Hello from plugin!", id);
// calling tokio::time::sleep(std::time::Duration::from_secs(1)).await;
// errors with "there is no reactor running, must be called from the context of a Tokio 1.x runtime"
let _ = sleep(Duration::new(1, 0));
println!("slept 1");
println!("[{}] Hello again from plugin!", id);
})
.await;
}
}
要总结这些内容,请执行以下操作:
- 这种方法在Rust 的情况下可行吗?
- 你在我的逻辑中发现了什么错误?(我非常肯定有一大堆)
- 你能给我指出一个解决方案或一个完全不同的方法吗?