添加qiming-rcoder模块
This commit is contained in:
599
qiming-rcoder/crates/agent_runner/src/main.rs
Normal file
599
qiming-rcoder/crates/agent_runner/src/main.rs
Normal file
@@ -0,0 +1,599 @@
|
||||
use clap::Parser;
|
||||
use dashmap::DashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
// 🆕 使用共享的遥测模块
|
||||
use rcoder_telemetry::{TelemetryConfig, TelemetryGuard};
|
||||
|
||||
mod agent_runtime;
|
||||
mod api_key_manager;
|
||||
mod config;
|
||||
mod grpc;
|
||||
mod handler;
|
||||
mod model;
|
||||
mod process_reaper;
|
||||
mod proxy_agent;
|
||||
|
||||
// 🔥 Pyroscope Profiler 模块(可选:需要 pyroscope feature)
|
||||
#[cfg(feature = "pyroscope")]
|
||||
mod profiler;
|
||||
|
||||
// 🔥 OpenTelemetry 追踪模块(可选:保留用于向后兼容)
|
||||
#[allow(dead_code)]
|
||||
mod otel_tracing;
|
||||
|
||||
mod router;
|
||||
mod service;
|
||||
mod utils;
|
||||
|
||||
// HTTP 服务器模块 (仅在 http-server feature 启用时)
|
||||
#[cfg(feature = "http-server")]
|
||||
mod http_server;
|
||||
|
||||
use agent_runtime::AgentRuntime;
|
||||
use config::{CliArgs, load_config_with_args};
|
||||
use model::*;
|
||||
use proxy_agent::cleanup_task::{CleanupConfig, start_cleanup_task};
|
||||
#[cfg(feature = "proxy")]
|
||||
use rcoder_proxy::{PingoraServerManager, ProxyConfig};
|
||||
use router::AppState;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::panic;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
|
||||
/// 🔥 设置自定义 Panic Hook
|
||||
///
|
||||
/// 当 agent_runner panic 时,将完整的 panic 信息(包括 backtrace)写入日志文件
|
||||
/// 这样即使容器被销毁,也能通过挂载的日志目录找到崩溃原因
|
||||
fn set_panic_hook() {
|
||||
let default_hook = panic::take_hook();
|
||||
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
// 🔥 立即写入日志文件(不依赖 tracing,确保在 panic 时也能写入)
|
||||
if let Err(e) = write_panic_to_file(panic_info) {
|
||||
// 如果文件写入失败,尝试输出到 stderr
|
||||
eprintln!("❌ [PANIC] Failed to write panic log file: {}", e);
|
||||
}
|
||||
|
||||
// 🔥 同时输出到 stderr(Docker 会捕获到容器日志)
|
||||
eprintln!("═══════════════════════════════════════════════════════════");
|
||||
eprintln!("❌ [PANIC] agent_runner encountered a fatal error!");
|
||||
eprintln!("═══════════════════════════════════════════════════════════");
|
||||
if let Some(location) = panic_info.location() {
|
||||
eprintln!(
|
||||
"panic.location: {}:{}:{}",
|
||||
location.file(),
|
||||
location.line(),
|
||||
location.column()
|
||||
);
|
||||
}
|
||||
eprintln!("panic.payload: {}", panic_info);
|
||||
eprintln!("═══════════════════════════════════════════════════════════");
|
||||
|
||||
// 调用默认 hook(会终止进程)
|
||||
default_hook(panic_info);
|
||||
}));
|
||||
}
|
||||
|
||||
/// 将 panic 信息写入日志文件
|
||||
fn write_panic_to_file(panic_info: &panic::PanicHookInfo) -> std::io::Result<()> {
|
||||
// 🔥 日志文件路径:/app/container-logs/agent_runner_panic.log(使用已有的挂载目录)
|
||||
let log_path = PathBuf::from("/app/container-logs/agent_runner_panic.log");
|
||||
|
||||
// 确保目录存在
|
||||
if let Some(parent) = log_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// 打开文件(追加模式)
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_path)?;
|
||||
|
||||
// 获取当前时间
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
|
||||
|
||||
// 写入 panic 信息
|
||||
writeln!(
|
||||
file,
|
||||
"═══════════════════════════════════════════════════════════"
|
||||
)?;
|
||||
writeln!(file, "❌ [PANIC] agent_runner encountered a fatal error!")?;
|
||||
writeln!(file, "time: {}", now)?;
|
||||
writeln!(
|
||||
file,
|
||||
"═══════════════════════════════════════════════════════════"
|
||||
)?;
|
||||
if let Some(location) = panic_info.location() {
|
||||
writeln!(
|
||||
file,
|
||||
"panic.location: {}:{}:{}",
|
||||
location.file(),
|
||||
location.line(),
|
||||
location.column()
|
||||
)?;
|
||||
}
|
||||
writeln!(file, "panic.payload: {}", panic_info)?;
|
||||
|
||||
// 写入 backtrace(如果启用)
|
||||
#[cfg(feature = "backtrace")]
|
||||
{
|
||||
if let Ok(backtrace) = std::backtrace::Backtrace::capture() {
|
||||
writeln!(file, "Backtrace:\n{}", backtrace)?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
file,
|
||||
"═══════════════════════════════════════════════════════════\n"
|
||||
)?;
|
||||
|
||||
// 强制刷新到磁盘
|
||||
file.flush()?;
|
||||
|
||||
eprintln!("✅ Panic info written to: {}", log_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 🔥 设置优雅关闭信号处理器
|
||||
///
|
||||
/// 监听系统信号,实现优雅关闭:
|
||||
/// - Unix: SIGTERM (Docker stop) + SIGINT (Ctrl+C)
|
||||
/// - Windows: Ctrl+C
|
||||
fn setup_shutdown_handler() -> tokio::task::JoinHandle<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
// 监听 SIGTERM(Docker stop)
|
||||
let mut sigterm = match signal(SignalKind::terminate()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("❌ [SIGNAL] Failed to register SIGTERM handler: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听 SIGINT(Ctrl+C)
|
||||
let mut sigint = match signal(SignalKind::interrupt()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("❌ [SIGNAL] Failed to register SIGINT handler: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
_ = sigterm.recv() => {
|
||||
eprintln!("📨 [SIGNAL] Received SIGTERM (Docker stop), starting graceful shutdown...");
|
||||
write_shutdown_log("SIGTERM");
|
||||
}
|
||||
_ = sigint.recv() => {
|
||||
eprintln!("📨 [SIGNAL] Received SIGINT (Ctrl+C), starting graceful shutdown...");
|
||||
write_shutdown_log("SIGINT");
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("🧹 [SIGNAL] Cleaning up resources...");
|
||||
eprintln!("✅ [SIGNAL] Graceful shutdown completed, exiting");
|
||||
std::process::exit(0);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
// Windows: 仅监听 Ctrl+C
|
||||
if let Ok(()) = tokio::signal::ctrl_c().await {
|
||||
eprintln!("📨 [SIGNAL] Received Ctrl+C, starting graceful shutdown...");
|
||||
write_shutdown_log("Ctrl+C");
|
||||
}
|
||||
|
||||
eprintln!("🧹 [SIGNAL] Cleaning up resources...");
|
||||
eprintln!("✅ [SIGNAL] Graceful shutdown completed, exiting");
|
||||
std::process::exit(0);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 将关闭事件写入日志文件
|
||||
fn write_shutdown_log(signal: &str) {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
let log_path = PathBuf::from("/app/container-logs/agent_runner_shutdown.log");
|
||||
|
||||
if let Some(parent) = log_path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
|
||||
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
|
||||
let _ = writeln!(
|
||||
file,
|
||||
"═══════════════════════════════════════════════════════════"
|
||||
);
|
||||
let _ = writeln!(
|
||||
file,
|
||||
"📨 [SHUTDOWN] agent_runner received a shutdown signal"
|
||||
);
|
||||
let _ = writeln!(file, "signal: {}", signal);
|
||||
let _ = writeln!(file, "time: {}", now);
|
||||
let _ = writeln!(
|
||||
file,
|
||||
"═══════════════════════════════════════════════════════════\n"
|
||||
);
|
||||
let _ = file.flush();
|
||||
eprintln!("✅ Shutdown info written to: {}", log_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
// 路由创建函数已移动到 handler 模块
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// 🔥 设置自定义 Panic Hook,确保 panic 信息被记录
|
||||
set_panic_hook();
|
||||
|
||||
// 🔥 设置信号处理器,实现优雅关闭(Docker stop、Ctrl+C)
|
||||
let _shutdown_handle = setup_shutdown_handler();
|
||||
|
||||
// ✅ 初始化 Rustls CryptoProvider(必须在最前面,在任何可能使用 TLS 的代码之前)
|
||||
// 🔥 如果这里失败,会导致 panic,但 panic hook 会捕获并记录
|
||||
rustls::crypto::ring::default_provider()
|
||||
.install_default()
|
||||
.expect(
|
||||
"❌ [FATAL] Rustls CryptoProvider initialization failed. The process cannot continue. This is usually an environment issue.",
|
||||
);
|
||||
|
||||
// 🆕 Initializing telemetry system(使用 rcoder-telemetry,包含控制台 + 文件日志)
|
||||
let telemetry_config = TelemetryConfig::from_env("agent_runner").with_file_log("agent-runner"); // 启用文件日志,前缀为 agent-runner
|
||||
let telemetry: TelemetryGuard = rcoder_telemetry::init(telemetry_config).await?;
|
||||
let telemetry = Arc::new(telemetry);
|
||||
|
||||
// 🆕 Pyroscope Profiler 初始化(可选:需要 pyroscope feature)
|
||||
#[cfg(feature = "pyroscope")]
|
||||
let _pyroscope_guard: Option<profiler::ProfilerGuard> = {
|
||||
info!("Pyroscope profiling feature enabled");
|
||||
match profiler::init_pyroscope_profiler_default() {
|
||||
Ok(guard) => {
|
||||
info!("Pyroscope profiler initialized successfully");
|
||||
Some(guard)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to initialize Pyroscope profiler: {}", e);
|
||||
warn!("Continuing without Pyroscope profiling");
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "pyroscope"))]
|
||||
let _pyroscope_guard: Option<()> = None;
|
||||
|
||||
info!("Starting rcoder - AI-powered development platform");
|
||||
|
||||
// 解析命令行参数
|
||||
let cli_args = CliArgs::parse();
|
||||
|
||||
// 加载配置(包含命令行参数)
|
||||
let config = load_config_with_args(cli_args);
|
||||
|
||||
// 🔥 初始化并发限制(从配置读取)
|
||||
if let Some(ref concurrency_config) = config.agent_concurrency {
|
||||
agent_runtime::init_concurrency_limit(concurrency_config.concurrency_limit);
|
||||
}
|
||||
|
||||
// 🔥 创建 AgentRuntime(新架构)
|
||||
let (agent_runtime, task_receiver) = AgentRuntime::new(1000);
|
||||
let agent_runtime = Arc::new(agent_runtime);
|
||||
info!("🔧 [MAIN] AgentRuntime created");
|
||||
|
||||
// 🔥 启动 Worker(在主运行时中,无需独立线程)
|
||||
agent_runtime.start(task_receiver).await;
|
||||
info!("📌 [MAIN] Agent Worker started");
|
||||
|
||||
// 🔥 启动健康检查和重启任务
|
||||
let health_monitor = spawn_health_monitor(agent_runtime.clone());
|
||||
info!("[MAIN] Worker health monitor started");
|
||||
|
||||
// 🔥 启动僵尸进程回收器(PID 1 必须回收孤儿进程)
|
||||
let _reaper_handle = process_reaper::start_process_reaper();
|
||||
info!("🧹 [MAIN] Process reaper started (PID 1 mode)");
|
||||
|
||||
// 🆕 从配置中获取 Agent 清理配置,或使用默认值
|
||||
let agent_cleanup_config = config.agent_cleanup.clone().unwrap_or_default();
|
||||
let cleanup_config = CleanupConfig {
|
||||
idle_timeout: Duration::from_secs(agent_cleanup_config.idle_timeout_secs),
|
||||
cleanup_interval: Duration::from_secs(agent_cleanup_config.cleanup_interval_secs),
|
||||
};
|
||||
|
||||
info!(
|
||||
"🧹 [MAIN] Agent cleanup config: idle_timeout={}s, cleanup_interval={}s",
|
||||
agent_cleanup_config.idle_timeout_secs, agent_cleanup_config.cleanup_interval_secs
|
||||
);
|
||||
|
||||
// 在主异步运行时中启动清理任务
|
||||
let _cleanup_handle = start_cleanup_task(cleanup_config.clone());
|
||||
|
||||
// proxy_manager 不需要直接访问 app_state,通过参数传递即可
|
||||
|
||||
// 🔒 创建共享的 API 密钥 DashMap
|
||||
let shared_api_key_manager =
|
||||
Arc::new(dashmap::DashMap::<String, shared_types::ModelProviderConfig>::new());
|
||||
info!("🔑 [MAIN] Shared API key DashMap created");
|
||||
|
||||
// 🔥 创建 ApiKeyManager 包装器(包装共享 DashMap,消除双重存储)
|
||||
let api_key_manager = Arc::new(api_key_manager::ApiKeyManager::from_shared(
|
||||
shared_api_key_manager.clone(),
|
||||
));
|
||||
|
||||
// 🔒 project_id -> service_uuid 映射
|
||||
let project_uuid_map: Arc<DashMap<String, String>> = Arc::new(DashMap::new());
|
||||
|
||||
// 🔥 http-server 模式:启动 HTTP + (可选 gRPC) + Pingora
|
||||
#[cfg(feature = "http-server")]
|
||||
{
|
||||
use http_server::{HttpServerConfig, start_http_server};
|
||||
use proxy_agent::set_unlimited_mode;
|
||||
|
||||
// 设置为无限制模式(HTTP Server 部署,不限制槽位)
|
||||
set_unlimited_mode(true);
|
||||
|
||||
// 🔥 1. 可选:启动 gRPC 服务(当 grpc-server feature 启用时)
|
||||
#[cfg(feature = "grpc-server")]
|
||||
let grpc_handle = {
|
||||
info!("ℹ️ HTTP server mode: starting HTTP + gRPC + Pingora");
|
||||
|
||||
let grpc_port = shared_types::GRPC_DEFAULT_PORT;
|
||||
let grpc_addr = format!("[::]:{}", grpc_port)
|
||||
.parse()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse gRPC address: {}", e))?;
|
||||
|
||||
// 为 gRPC 创建 state
|
||||
let grpc_state = Arc::new(AppState {
|
||||
sessions: Arc::new(DashMap::new()),
|
||||
config: config.clone(),
|
||||
local_task_sender: agent_runtime.clone(),
|
||||
agent_runtime: agent_runtime.clone(),
|
||||
#[cfg(feature = "proxy")]
|
||||
pingora_service: None,
|
||||
api_key_manager: api_key_manager.clone(),
|
||||
shared_api_key_manager: shared_api_key_manager.clone(),
|
||||
project_uuid_map: project_uuid_map.clone(),
|
||||
});
|
||||
|
||||
// gRPC 消息大小限制
|
||||
let grpc_service = shared_types::grpc::agent_service_server::AgentServiceServer::new(
|
||||
grpc::AgentServiceImpl::new(grpc_state.clone()),
|
||||
)
|
||||
.max_decoding_message_size(shared_types::GRPC_MAX_MESSAGE_SIZE)
|
||||
.max_encoding_message_size(shared_types::GRPC_MAX_MESSAGE_SIZE);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
info!("gRPC service started, listening on port: {}", grpc_port);
|
||||
info!("gRPC endpoints (port {}):", grpc_port);
|
||||
info!(" agent.AgentService/Chat - gRPC chat");
|
||||
info!(" agent.AgentService/SubscribeProgress - gRPC progress stream");
|
||||
info!(" agent.AgentService/CancelSession - gRPC cancel");
|
||||
info!(" agent.AgentService/GetStatus - gRPC status");
|
||||
if let Err(e) = tonic::transport::Server::builder()
|
||||
.add_service(grpc_service)
|
||||
.serve(grpc_addr)
|
||||
.await
|
||||
{
|
||||
error!("gRPC server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Some(handle)
|
||||
};
|
||||
|
||||
// 无 gRPC 模式
|
||||
#[cfg(not(feature = "grpc-server"))]
|
||||
{
|
||||
info!("ℹ️ HTTP server mode: starting HTTP + Pingora only (no gRPC)");
|
||||
}
|
||||
|
||||
// 🔥 2. 创建 HttpServerConfig(包含所有配置)
|
||||
let http_config = HttpServerConfig {
|
||||
port: config.port,
|
||||
app_config: config.clone(),
|
||||
agent_runtime: agent_runtime.clone(),
|
||||
shared_api_key_manager: shared_api_key_manager.clone(),
|
||||
};
|
||||
|
||||
// 🔥 3. 启动 HTTP 服务器(内部会启动 Pingora)
|
||||
let _handle = start_http_server(http_config).await?;
|
||||
|
||||
// 🔥 4. 同时等待 gRPC(如果有)和信号
|
||||
info!("HTTP + Pingora services started; running until shutdown signal is received");
|
||||
|
||||
#[cfg(feature = "grpc-server")]
|
||||
{
|
||||
tokio::select! {
|
||||
_ = grpc_handle.unwrap() => {
|
||||
info!("gRPC service ended unexpectedly, shutting down...");
|
||||
}
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("📨 Received shutdown signal, preparing graceful shutdown...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "grpc-server"))]
|
||||
{
|
||||
tokio::signal::ctrl_c().await?;
|
||||
info!("📨 Received shutdown signal, preparing graceful shutdown...");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 🔥 non-http-server 模式:启动 gRPC + Pingora(用于 Docker 容器内)
|
||||
#[cfg(not(feature = "http-server"))]
|
||||
{
|
||||
info!("ℹ️ Container mode: starting gRPC + Pingora");
|
||||
|
||||
// 启动 gRPC 服务
|
||||
let grpc_port = shared_types::GRPC_DEFAULT_PORT;
|
||||
let grpc_addr = format!("[::]:{}", grpc_port)
|
||||
.parse()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse gRPC address: {}", e))?;
|
||||
|
||||
// 为 gRPC 创建 state
|
||||
let grpc_state = Arc::new(AppState {
|
||||
sessions: Arc::new(DashMap::new()),
|
||||
config: config.clone(),
|
||||
local_task_sender: agent_runtime.clone(),
|
||||
agent_runtime: agent_runtime.clone(),
|
||||
#[cfg(feature = "proxy")]
|
||||
pingora_service: None,
|
||||
api_key_manager: api_key_manager.clone(),
|
||||
shared_api_key_manager: shared_api_key_manager.clone(),
|
||||
project_uuid_map: project_uuid_map.clone(),
|
||||
});
|
||||
|
||||
// gRPC 消息大小限制
|
||||
let grpc_service = shared_types::grpc::agent_service_server::AgentServiceServer::new(
|
||||
grpc::AgentServiceImpl::new(grpc_state.clone()),
|
||||
)
|
||||
.max_decoding_message_size(shared_types::GRPC_MAX_MESSAGE_SIZE)
|
||||
.max_encoding_message_size(shared_types::GRPC_MAX_MESSAGE_SIZE);
|
||||
|
||||
let grpc_handle = tokio::spawn(async move {
|
||||
info!("gRPC service started, listening on port: {}", grpc_port);
|
||||
info!("gRPC endpoints (port {}):", grpc_port);
|
||||
info!(" agent.AgentService/Chat - gRPC chat");
|
||||
info!(" agent.AgentService/SubscribeProgress - gRPC progress stream");
|
||||
info!(" agent.AgentService/CancelSession - gRPC cancel");
|
||||
info!(" agent.AgentService/GetStatus - gRPC status");
|
||||
if let Err(e) = tonic::transport::Server::builder()
|
||||
.add_service(grpc_service)
|
||||
.serve(grpc_addr)
|
||||
.await
|
||||
{
|
||||
error!("gRPC server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// 启动轻量 HTTP 健康检查服务(供 docker_manager 健康检查使用)
|
||||
let health_port = config.port; // 默认 8086,来自 --port 参数
|
||||
let _health_handle = tokio::spawn(async move {
|
||||
use axum::{Json, Router, routing::get};
|
||||
|
||||
async fn health_check() -> Json<shared_types::HealthResponse> {
|
||||
Json(shared_types::HealthResponse::new("agent-runner"))
|
||||
}
|
||||
|
||||
let app = Router::new().route("/health", get(health_check));
|
||||
let addr = format!("0.0.0.0:{}", health_port);
|
||||
|
||||
info!(
|
||||
"🏥 HTTP health check service started, listening on port: {}",
|
||||
health_port
|
||||
);
|
||||
|
||||
let listener = match tokio::net::TcpListener::bind(&addr).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"❌ Failed to bind HTTP health check service: {} (port: {})",
|
||||
e, health_port
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = axum::serve(listener, app).await {
|
||||
error!("HTTP health check service error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// 启动 Pingora(如有配置且启用了 proxy feature)
|
||||
#[cfg(feature = "proxy")]
|
||||
let pingora_result = {
|
||||
use proxy_agent::start_pingora;
|
||||
|
||||
if let Some(proxy_config) = &config.proxy_config {
|
||||
Some(start_pingora(proxy_config, shared_api_key_manager.clone()))
|
||||
} else {
|
||||
info!("ℹ️ Pingora proxy service is not configured");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "proxy"))]
|
||||
let pingora_result: Option<()> = {
|
||||
info!("ℹ️ Pingora proxy service is disabled (proxy feature not enabled)");
|
||||
None
|
||||
};
|
||||
|
||||
// 等待 gRPC 服务
|
||||
let _ = grpc_handle.await;
|
||||
|
||||
// 停止 Pingora 服务
|
||||
#[cfg(feature = "proxy")]
|
||||
if let Some(mut result) = pingora_result {
|
||||
result.stop().await;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "proxy"))]
|
||||
let _ = pingora_result;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔥 健康监控任务 (新架构)
|
||||
///
|
||||
/// 定期检查 Agent Worker 健康状态,自动重启不健康的 Worker
|
||||
async fn spawn_health_monitor(runtime: Arc<AgentRuntime>) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
||||
let mut consecutive_failures: u32 = 0;
|
||||
const MAX_RESTART_ATTEMPTS: u32 = 5;
|
||||
const RESTART_COOLDOWN_SECS: u64 = 60;
|
||||
|
||||
info!("[HealthMonitor] Health monitor started");
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
// 检查健康状态
|
||||
if !runtime.check_health().await {
|
||||
error!("[HealthMonitor] Worker reported unhealthy");
|
||||
|
||||
// 检查冷却期
|
||||
if consecutive_failures >= MAX_RESTART_ATTEMPTS {
|
||||
warn!(
|
||||
"⏳ [HealthMonitor] {} consecutive restart failures, entering cooldown",
|
||||
consecutive_failures
|
||||
);
|
||||
tokio::time::sleep(Duration::from_secs(RESTART_COOLDOWN_SECS)).await;
|
||||
consecutive_failures = 0;
|
||||
info!("[HealthMonitor] Cooldown ended, reset failure counter");
|
||||
}
|
||||
|
||||
// 创建新的通道
|
||||
let (new_tx, new_rx) = tokio::sync::mpsc::channel(1000);
|
||||
|
||||
// 重启 worker
|
||||
runtime.restart(new_rx).await;
|
||||
consecutive_failures += 1;
|
||||
info!(
|
||||
"🔄 [HealthMonitor] Worker restart completed (attempt #{})",
|
||||
consecutive_failures
|
||||
);
|
||||
} else {
|
||||
consecutive_failures = 0;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user