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 }
问题原因
- Rust-Bert模型内部的Runtime冲突:
SentimentModel::new()初始化时会创建独立的Tokio Runtime,用于处理模型推理的阻塞操作。当模型在异步函数中被销毁时,其drop逻辑会尝试关闭自身Runtime,但此时当前线程正处于#[tokio::main]创建的主异步Runtime上下文中,导致Runtime销毁操作违反Tokio的线程安全规则。 - 异步函数的错误封装:
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
相关产品推荐
相关产品推荐

