fix: eliminate LLM keyword deadlock with isolated worker thread
All checks were successful
BotServer CI/CD / build (push) Successful in 3m32s
All checks were successful
BotServer CI/CD / build (push) Successful in 3m32s
The previous fix used Handle::current().block_on() which deadlocks when the Rhai engine runs on a Tokio worker thread — it blocks the very thread the async task needs to make progress. New approach: spawn a dedicated background thread with its own single-threaded Tokio runtime, communicate via mpsc channel with a 45s timeout. This completely isolates the LLM runtime from the caller's runtime, eliminating any possibility of thread starvation or nested-runtime deadlock. Also remove unused 'trace' import from llm/mod.rs. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
b3fd90b056
commit
25d6d2fd57
2 changed files with 36 additions and 10 deletions
|
|
@ -1,10 +1,15 @@
|
||||||
use crate::core::shared::models::UserSession;
|
use crate::core::shared::models::UserSession;
|
||||||
use crate::core::shared::state::AppState;
|
use crate::core::shared::state::AppState;
|
||||||
use log::error;
|
|
||||||
use rhai::{Dynamic, Engine};
|
use rhai::{Dynamic, Engine};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
/// Register the LLM keyword with a deadlock-free execution model.
|
||||||
|
///
|
||||||
|
/// Uses a dedicated background thread with its own single-threaded Tokio runtime
|
||||||
|
/// to avoid blocking or starving the caller's runtime — the classic source of
|
||||||
|
/// LLM deadlocks in this codebase.
|
||||||
pub fn llm_keyword(state: Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
pub fn llm_keyword(state: Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
||||||
let state_clone = Arc::clone(&state);
|
let state_clone = Arc::clone(&state);
|
||||||
engine
|
engine
|
||||||
|
|
@ -21,15 +26,34 @@ pub fn llm_keyword(state: Arc<AppState>, _user: UserSession, engine: &mut Engine
|
||||||
let state_for_async = Arc::clone(&state_clone);
|
let state_for_async = Arc::clone(&state_clone);
|
||||||
let prompt = build_llm_prompt(&text);
|
let prompt = build_llm_prompt(&text);
|
||||||
|
|
||||||
let handle = tokio::runtime::Handle::current();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let result = handle.block_on(async move {
|
|
||||||
tokio::time::timeout(
|
|
||||||
Duration::from_secs(45),
|
|
||||||
execute_llm_generation(state_for_async, prompt)
|
|
||||||
).await
|
|
||||||
});
|
|
||||||
|
|
||||||
match result {
|
// Spawn a dedicated worker thread with its own runtime.
|
||||||
|
// This prevents deadlocks caused by blocking the caller's runtime
|
||||||
|
// while simultaneously trying to run async code on it.
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("llm-worker".into())
|
||||||
|
.spawn(move || {
|
||||||
|
let result = std::thread::Builder::new()
|
||||||
|
.name("llm-rt".into())
|
||||||
|
.spawn(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()?;
|
||||||
|
rt.block_on(execute_llm_generation(state_for_async, prompt))
|
||||||
|
});
|
||||||
|
let outcome = match result {
|
||||||
|
Ok(handle) => match handle.join() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => Err("LLM worker thread panicked".into()),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!("Failed to spawn LLM worker: {e}").into()),
|
||||||
|
};
|
||||||
|
let _ = tx.send(outcome);
|
||||||
|
})
|
||||||
|
.expect("LLM dispatcher thread");
|
||||||
|
|
||||||
|
match rx.recv_timeout(Duration::from_secs(45)) {
|
||||||
Ok(Ok(output)) => Ok(Dynamic::from(output)),
|
Ok(Ok(output)) => Ok(Dynamic::from(output)),
|
||||||
Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||||
e.to_string().into(),
|
e.to_string().into(),
|
||||||
|
|
@ -43,12 +67,14 @@ pub fn llm_keyword(state: Arc<AppState>, _user: UserSession, engine: &mut Engine
|
||||||
})
|
})
|
||||||
.expect("valid syntax registration");
|
.expect("valid syntax registration");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_llm_prompt(user_text: &str) -> String {
|
fn build_llm_prompt(user_text: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Você é um assistente virtual em português brasileiro. Responda sempre em português do Brasil, de forma clara e amigável.\n\nPedido do usuário: {}",
|
"Você é um assistente virtual em português brasileiro. Responda sempre em português do Brasil, de forma clara e amigável.\n\nPedido do usuário: {}",
|
||||||
user_text.trim()
|
user_text.trim()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_llm_generation(
|
pub async fn execute_llm_generation(
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
prompt: String,
|
prompt: String,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::{error, info, trace};
|
use log::{error, info};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{mpsc, RwLock};
|
use tokio::sync::{mpsc, RwLock};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue