You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中利用Drop trait执行DROP DATABASE时程序挂起问题求助

问题:PostgreSQL DROP DATABASE在Drop trait中执行挂起超时

我正在跟随《zero2prod Rust 实战手册》学习,目前处于第3章末尾,搭建集成测试时已经实现“每次测试运行创建独立数据库”的功能。现在希望扩展该功能,在每个测试结束后自动删除数据库,计划借助Drop trait执行DROP DATABASE命令(已存储数据库名称及连接/连接池用于后续访问)。但执行DROP DATABASE查询时程序会挂起,60秒后超时,且无法打印或调试连接状态。

现有可正常运行的代码

use futures::executor;
use sqlx::{Connection, Executor, PgConnection, PgPool};
use std::net::TcpListener;
use uuid::Uuid;
use zero2prod::configuration::{get_configuration, DatabaseSettings};

const BASE_URL: &str = "127.0.0.1";
pub struct TestApp {
    db_name: String,
    connection_string: String,
    pub address: String,
    pub db_pool: PgPool,
    pub connection: PgConnection,
}

/**
 * We need to refactor our project into a library and a binary: all our logic will live in the library crate
while the binary itself will be just an entrypoint with a very slim main function
 */

pub async fn init(url: &str) -> TestApp {
    let mut app = spawn_app().await;
    app.address = format!("{}{}", app.address, url);
    return app;
}

// Launch our application in the background ~somehow~
async fn spawn_app() -> TestApp {
    // We take the BASE_URL const and assign it a port 0. We then
    // pass the listener to the server
    let base_url = format!("{}:0", BASE_URL);
    let listener = TcpListener::bind(base_url).expect("Failed to bind random port");

    // We retrieve the port assigned by the OS
    let port = listener.local_addr().unwrap().port();

    let (connection, db_connection, db_name, connection_string) = init_db().await;

    // We pass the port now to our server
    let server = zero2prod::run(listener, db_connection.clone()).expect("Failed to bind address");
    let _ = actix_web::rt::spawn(server);
    let address = format!("http://{}:{}", BASE_URL, port);

    TestApp {
        db_name: String::from(db_name),
        address,
        db_pool: db_connection,
        connection,
        connection_string,
    }
}

async fn init_db() -> (PgConnection, PgPool, String, String) {
    let mut configuration = get_configuration().expect("Failed to read configuration");

    // We change the db name in each run as we need to run the test multiple times
    configuration.database.database_name = Uuid::new_v4().to_string();

    let (connection, pool) = configure_database(&configuration.database).await;

    return (
        connection,
        pool,
        String::from(&configuration.database.database_name),
        configuration.database.connection_string_without_db(),
    );
}

async fn configure_database(config: &DatabaseSettings) -> (PgConnection, PgPool) {

   // The following returns:
   //   format!(
   //       "postgres://{}:{}@{}:{}",
   //       self.username, self.password, self.host, self.port
   //   )
    let mut connection = PgConnection::connect(&config.connection_string_without_db())
        .await
        .expect("Failed to connect to Postgres.");

    connection
        .execute(format!(r#"CREATE DATABASE "{}""#, config.database_name).as_str())
        .await
        .expect("Failed to create the db");

    // Migrate the database

    let connection_pool = PgPool::connect(&config.connection_string())
        .await
        .expect("Failed to connect to Postgres");

    sqlx::migrate!("./migrations")
        .run(&connection_pool)
        .await
        .expect("Failed to migrate db");

    return (connection, connection_pool);
}

入口函数为init(),返回TestApp结构体(原本仅包含db_pool和address字段),上述代码均可正常运行。

尝试过的方案(均出现挂起超时问题)

方案1:使用Smol runtime在drop中初始化新Postgres连接

impl Drop for TestApp {
    fn drop(&mut self) {
        smol::block_on(async {
            let mut connection = PgConnection::connect(&self.connection_string)
                .await
                .expect("Failed to connect to Postgres.");

            let result = connection
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str())
                .await
                .expect("Error while querying the drop database");
            println!("{:?}", result);
        });
    }
}

方案2:使用Smol runtime在drop中复用现有db_pool

impl Drop for TestApp {
    fn drop(&mut self) {
        smol::block_on(async {
            let result = self
                .db_pool
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str())
                .await.expect("Error while querying");
            println!("{:?}", result);
        });
    }
}

方案3:使用Futures crate executor复用现有db_pool

impl Drop for TestApp {
    fn drop(&mut self) {
        let result = executor::block_on(
            self.db_pool
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str()),
        )
        .expect("Failed to drop database");

        println!("{:?}", result);
    }
}

方案4:使用Futures crate executor先获取连接再操作(挂起在db_pool.acquire处)

impl Drop for TestApp {
    fn drop(&mut self) {
        executor::block_on(self.db_pool.acquire()).expect("Failed to acquire pool");
        let result = executor::block_on(
            self.db_pool
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str()),
        )
        .expect("Failed to drop database");

        println!("{:?}", result);
    }
}

方案5:使用Futures crate executor复用现有connection

impl Drop for TestApp {
    fn drop(&mut self) {
        let result = executor::block_on(
            self.connection
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str()),
        )
        .expect("Failed to drop database");

        println!("{:?}", result);
    }
}

目前代码仅为测试方案,尚未优化。没有报错信息,无法定位问题,恳请提供排查思路或解决方案。

内容的提问来源于stack exchange,提问作者Jose A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 23:54:23