2024年5月1日 星期三

Rust: Rocket/Diesel 的 Connection 基於記憶體相同型別的強制轉型

由於本文當下 2024/05/01 在 Rocket Async Diesel 的 Library 底下還沒有支援從內部取得 Connection 的方法,若不想重新寫一個 Connection 當作 Wrap 的情況下,可以把記憶體直接裝到新的型別變數中。


強制轉型記憶體的方法 Unsafe


實際上在 Rocket 的範例中,要讓每個進入 Function 的 Router 同時接收 DbConn 參數,大多都是由 from_request [1] 這個地方從 "request: &'r rocket::Request<'_>" 這個方法裡面取的 DbConn,例如:

async fn from_request(
        request: &'r rocket::Request<'_>,
    ) -> rocket::request::Outcome<Self, Self::Error> {
	
	let mut db = request.guard::<DbConn>().await;
   

但是,除此之外,要不透過 http 的 API Router 打進來去執行取得共用的 DbConn 卻很困難,例如有外 Cronjob 或是其他 Threading 去控制背景工作。


以下是一個方法,透過 Rocket 啟動時的 on_ignite ,可以讓 MyDb 從 rocket 取得 context,但是由於缺少型標轉換,因此可以透過 unsafe { std::mem::transmute }} 的方法強制轉換到該變數中。


use rocket_db_pools::diesel::{
    pooled_connection::{
        deadpool::{self, Object},
        AsyncDieselConnectionManager,
    },
    AsyncConnection, AsyncMysqlConnection, MysqlPool, Pool, SimpleAsyncConnection,
};

#[derive(Database)]
#[database("my_db")]
pub struct MyDb(MysqlPool);

pub type DbConn = Connection<MainDb>;


rocket::build()
    .attach(MyDb::init())
    .attach(AdHoc::on_ignite("Seeding database", |rocket| async move {
    	let db = MyDb::fetch(&rocket).unwrap();
        
        // Object<AsyncDieselConnectionManager<AsyncMysqlConnection>>
        let mut conn = db.get().await.unwrap();
        
        let mut clone_conn = db.clone().get().await.unwrap();
        let mut casted_conn: DbConn = unsafe { std::mem::transmute(clone_conn) };
// 此時 Diesel 上的操作可以從這裡開始做了 })


從 conn 變數結構看兩者的內容是一致的,但是由於型別名稱不同,所以被視為不同的內容,可以透過這種方式強制轉換。


重寫 Connection


或者,可以重新寫一個 Conncetion 讓 DbConn 的 Type 是自己 Wrap 上來的:
use rocket_db_pools::{Database, Pool};
pub struct Connection<D: Database>(<D::Pool as Pool>::Connection);
impl<D: Database> Connection<D> {
    pub async fn get (db: &D) -> Self {
        Connection(db.get().await.unwrap().into())
    }
}

   

當這麼做以後,事實上 DbConn 接下來就可以直接使用 get 取得連線:

#[derive(Database)]
#[database("my_db")]
pub struct MyDb(MysqlPool);

pub type DbConn = Connection<MainDb>;

AdHoc::on_ignite("Init", |rocket| async move {
        let db = MyDb::fetch(&rocket).expect("Failed");
        let conn = DbConn::get(db).await.expect("Failed");
        tokio::spawn(async move {
            do_something(conn, tasks)
                .await
                .expect("Failed to run rescheduler");
        });
        rocket
    })

   



Reference:
[1]: https://api.rocket.rs/v0.5/rocket/request/trait.FromRequest

沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014