我一直在编写一个带有tide
框架async-std
风格的小型 Web 服务。该项目使用一个很少使用但贯穿整个项目的数据库。
我认为将它声明为全局变量是合理的,这样我就不必在几乎每个函数接口中都包含它,只是为了让它在未使用的情况下传递。在与编译器就我违反的 Rust 设计原则的数量进行了一番争论之后,我确定了以下声明:
pub static DATABASE: RwLock<Database> = const_rwlock(Database::new());
const_rwlock()
是来自crate的常量Rwlock
构造函数(据我所知,与 on nightly 相同)并且是我自己的类型,稍后会填充连接详细信息并以异步方式处理数据库的实际通信和初始化。parking_lot
RwLock
Database
一切看起来都很好,因为我可以像这样使用它:
let data = DATABASE.read().get_data(&key).await;
即使在异步函数中使用异步方法。
当我尝试将此功能传递给 Web 框架时,就会出现问题。我应该通过传入一个必须是的异步块闭包来做到这一点Send
,但是当我写这样的东西时:
// the actual function to start the servers looks essentially like this
start_server(|| async move {
// this is run asynchronously for every connection by the framework
let token = do_stuff();
let result = DATABASE.read().check(token).await;
});
我得到一个错误,因为整个未来不再是Send
. 这对我来说很有意义,因为我在.read()
调用中锁定了一个互斥体,然后将整个事情打包到未来并等待它。这意味着锁定的互斥锁可能会被发送到另一个线程,这似乎并不明智。
这让我认为我设计了整个全局变量是错误的,我没有更好的想法。
什么是表示异步代码中许多地方使用的资源的好方法?我想避免“无处不在”的路线。
这是一个最小的代码示例。我从板条箱中提取了相关代码tide
以尝试缩小此问题的范围。
use async_std::sync::Arc;
use parking_lot::{const_rwlock, RwLock};
use std::future::Future;
pub static DATABASE: RwLock<Database> = const_rwlock(Database::new());
// const mutex can only be constructed with new() on nightly
//pub static DATABASE: RwLock<Database> = RwLock::new(Database::new());
pub struct Database {
// Here would be the actual connection stored
pool: Option<()>,
}
impl Database {
const fn new() -> Self {
Database {
pool: None,
}
}
pub async fn init(&mut self) {
self.pool = Some(());
}
pub async fn check(&self, token: &str) -> bool {
if token == "vibe" {
return true;
} else {
return false;
}
}
}
#[async_std::main]
async fn main() {
DATABASE.read().init().await;
Server::new(|| async move {
// XXX There's the problem
let result = DATABASE.read().check("vibe").await;
if result {
Ok(())
} else {
Err(())
}
});
}
// Everything below is part of some library and can't be changed by me
struct Server<H> {
handler: Arc<H>
}
impl<H, Fut> Server<H>
where
// Those requirements are verbatim copied from the framework code
H: Fn() -> Fut + Sync + Send + 'static,
Fut: Future<Output = Result<(), ()>> + Send + 'static,
{
pub fn new(handler: H) -> Self {
Self {
handler: Arc::new(handler),
}
}
}
货物.toml:
[package]
name = "server"
version = "0.1.0"
edition = "2018"
[dependencies]
parking_lot = "0.11"
async-std = { version = "1.6", features = ["attributes"] }
在它上面运行后cargo check
,我得到了描述的错误:
Checking server v0.1.0 (/XXX/server)
error: future cannot be sent between threads safely
--> src/main.rs:36:5
|
36 | Server::new(|| async move {
| ^^^^^^^^^^^ future created by async block is not `Send`
...
60 | pub fn new(handler: H) -> Self {
| ------------------------------ required by `Server::<H>::new`
|
= help: within `impl Future`, the trait `std::marker::Send` is not implemented for `*mut ()`
note: future is not `Send` as this value is used across an await
--> src/main.rs:38:22
|
38 | let result = DATABASE.read().check("vibe").await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ first, await occurs here, with `DATABASE.read()` maybe used later...
note: `DATABASE.read()` is later dropped here
--> src/main.rs:38:57
|
38 | let result = DATABASE.read().check("vibe").await;
| --------------- ^
| |
| has type `parking_lot::lock_api::RwLockReadGuard<'_, parking_lot::RawRwLock, Database>` which is not `Send`
help: consider moving this into a `let` binding to create a shorter lived borrow
--> src/main.rs:38:22
|
38 | let result = DATABASE.read().check("vibe").await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我相信我在高级别正确解释了此错误(无法在线程之间移动锁保护),并且在此错误消息中在低级别(*mut ()
不是)进行了解释。Send