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

Tokio运行时销毁报错:异步上下文不允许阻塞销毁问题求助

问题排查与解决:Tokio异步上下文下Rust-Bert的Runtime Drop Panic

错误信息

Finished dev [unoptimized + debuginfo] target(s) in 14.41s
Running `target/debug/stock-perception-api`
[2022-08-23T13:57:14Z INFO  stock_perception_api::config::db] Configuring Database...
thread 'main' panicked at 'Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.', /home/r/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.20.1/src/runtime/blocking/shutdown.rs:51:21
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fish: Job 1, 'cargo run' terminated by signal SIGSEGV (Address boundary error)

问题定位

报错触发于scrape_article函数调用get_sentiment的位置,相关代码如下:

scrape_article函数

pub async fn scrape_article(conn: &Pool) {
    let stocks_repo = StockRepo::new(conn);
    let sentiment_repo = SentimentRepo::new(conn);
    let article_repo = ArticleRepo::new(conn);

    let count = get_count_of_stocks(stocks_repo).unwrap();

    for item in 1..=count {
        let links = get_all_article_links_by_stock_id(article_repo, item as i32).unwrap();

        for link in links {
            let parsing_result = parse_article(&link).await.unwrap();
            let sentiment: Vec<Sentiment> = get_sentiment(&parsing_result.content).await.unwrap(); // 报错位置
            let new_sentiment;

            if sentiment[0].polarity == SentimentPolarity::Negative {
                new_sentiment = SentimentDTO::new(false, sentiment[0].score);
            } else {
                new_sentiment = SentimentDTO::new(true, sentiment[0].score);
            }

            create_sentiment(sentiment_repo, new_sentiment).unwrap();
        }
    }
    ()
}

get_sentiment函数

pub async fn get_sentiment(
    article_content: &str,
) -> Result<Vec<Sentiment>, Box<dyn std::error::Error>> {
    let sentiment_classifier: SentimentModel = SentimentModel::new(Default::default())?;
    let input: [&str; 1] = [article_content];

    let output: Vec<Sentiment> = sentiment_classifier.predict(&input);

    println!("{:?}", output[0]);

    Ok(output)
}

main.rs代码

#[tokio::main]
async fn main() -> io::Result<()> {
    dotenv().ok();
    env_logger::init_from_env(Env::default().default_filter_or("info"));

    let app_host = env::var(constants::APP_HOST).expect("APP_HOST not found.");
    let app_port = env::var(constants::APP_PORT).expect("APP_PORT not found.");
    let app_url = format!("{}:{}", &app_host, &app_port);
    let db_url = env::var(constants::DATABASE_URL).expect("DATABASE_URL not found.");
    let pool = config::db::migrate_and_config_db(&db_url);


    scrape_article(&pool).await;

    HttpServer::new(move || {
        App::new()
            .wrap(Cors::permissive())
            .app_data(web::Data::new(pool.clone()))
            .wrap(Logger::default())
            .wrap(Logger::new("%a %s %{User-Agent}i"))
            .configure(config::app::config_services)
    })
    .bind(app_url)?
    .run()
    .await
}

问题原因

  1. Rust-Bert模型内部的Runtime冲突:SentimentModel::new()初始化时会创建独立的Tokio Runtime,用于处理模型推理的阻塞操作。当模型在异步函数中被销毁时,其drop逻辑会尝试关闭自身Runtime,但此时当前线程正处于#[tokio::main]创建的主异步Runtime上下文中,导致Runtime销毁操作违反Tokio的线程安全规则。
  2. 异步函数的错误封装:get_sentiment被标记为async但内部实际是纯阻塞操作,且模型的创建/销毁未被正确隔离到Tokio的阻塞线程池,进一步加剧了上下文冲突。

解决方案

方案1:全局初始化模型(推荐)

模型初始化开销大,全局只创建一次,避免重复创建/销毁Runtime:

use once_cell::sync::Lazy;

// 全局静态模型实例,程序启动时初始化一次
static SENTIMENT_CLASSIFIER: Lazy<SentimentModel> = Lazy::new(|| {
    SentimentModel::new(Default::default()).expect("Failed to initialize sentiment model")
});

// 改为同步阻塞函数
pub fn get_sentiment(article_content: &str) -> Result<Vec<Sentiment>, Box<dyn std::error::Error>> {
    let input: [&str; 1] = [article_content];
    let output = SENTIMENT_CLASSIFIER.predict(&input);
    
    println!("{:?}", output[0]);
    Ok(output)
}

在scrape_article中用spawn_blocking调用该阻塞函数:

// 替换原get_sentiment调用
let sentiment = tokio::task::spawn_blocking(move || {
    get_sentiment(&parsing_result.content)
}).await??;

方案2:将模型操作完全隔离到阻塞线程池

若必须每次创建模型(不推荐),需把模型的创建、推理、销毁全流程放到spawn_blocking中:

pub async fn get_sentiment(article_content: &str) -> Result<Vec<Sentiment>, Box<dyn std::error::Error>> {
    // 克隆内容到闭包中,避免生命周期问题
    let content = article_content.to_string();
    tokio::task::spawn_blocking(move || {
        let classifier = SentimentModel::new(Default::default())?;
        let input: [&str; 1] = [&content];
        let output = classifier.predict(&input);
        
        println!("{:?}", output[0]);
        Ok(output)
    }).await?
}

额外优化建议

  • 移除代码中大量的unwrap(),改用?或match做错误处理,避免意外panic。
  • 数据库操作(如get_count_of_stocks、create_sentiment)若为阻塞IO,同样用spawn_blocking包裹,防止阻塞Tokio异步线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 16:15:34