use crate::get_proxy_manager;
use crate::model::{CheckMcpStatusResponseStatus, GLOBAL_RESTART_TRACKER, McpType};
use crate::server::task::mcp_start_task::mcp_start_task;
use tokio::time::Duration;
use tracing::{error, info, warn};
const ONESHOT_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const MAX_PROBE_FAILURES: u32 = 3;
pub async fn schedule_check_mcp_live() {
let proxy_manager = get_proxy_manager();
let mcp_service_statuses = proxy_manager.get_all_mcp_service_status();
info!(
"There are currently {} mcp plug-in services running",
mcp_service_statuses.len()
);
for mcp_service_status in mcp_service_statuses {
let mcp_id = mcp_service_status.mcp_id.clone();
let mcp_type = mcp_service_status.mcp_type.clone();
let cancellation_token = mcp_service_status.cancellation_token.clone();
if let CheckMcpStatusResponseStatus::Error(_) =
mcp_service_status.check_mcp_status_response_status
{
if let Err(e) = proxy_manager.cleanup_resources(&mcp_id).await {
error!("Failed to cleanup resources for {}: {}", mcp_id, e);
}
continue;
}
match mcp_type {
McpType::Persistent => {
if cancellation_token.is_cancelled() {
info!(
"The persistent MCP service {mcp_id} has been manually canceled and resources are being cleaned up."
);
if let Err(e) = proxy_manager.cleanup_resources(&mcp_id).await {
error!("Failed to cleanup resources for {}: {}", mcp_id, e);
}
continue;
}
if let Some(handler) = proxy_manager.get_proxy_handler(&mcp_id)
&& handler.is_terminated_async().await
{
info!(
"The persistent MCP service {mcp_id} child process ended abnormally and cleaned up resources."
);
if let Err(e) = proxy_manager.cleanup_resources(&mcp_id).await {
error!("Failed to cleanup resources for {}: {}", mcp_id, e);
}
}
}
McpType::OneShot => {
let idle_time = mcp_service_status.last_accessed.elapsed();
if idle_time > ONESHOT_TIMEOUT {
info!(
"OneShot service {} idle timeout (idle time: {} seconds), clean up resources",
mcp_id,
idle_time.as_secs()
);
if let Err(e) = proxy_manager.cleanup_resources(&mcp_id).await {
error!("Failed to cleanup resources for {}: {}", mcp_id, e);
}
continue;
}
if !matches!(
mcp_service_status.check_mcp_status_response_status,
CheckMcpStatusResponseStatus::Ready
) {
continue;
}
let handler = proxy_manager.get_proxy_handler(&mcp_id);
if let Some(handler) = handler {
let is_terminated = handler.is_terminated_async().await;
if is_terminated {
let failures = proxy_manager.increment_probe_failures(&mcp_id);
info!(
"OneShot service {} health check failed ({}/{})",
mcp_id, failures, MAX_PROBE_FAILURES
);
if failures >= MAX_PROBE_FAILURES {
info!(
"OneShot service {} failed continuously {} times, triggering a restart",
mcp_id, failures
);
restart_mcp_service(&mcp_id, proxy_manager).await;
}
} else {
proxy_manager.reset_probe_failures(&mcp_id);
}
}
}
}
}
}
async fn restart_mcp_service(mcp_id: &str, proxy_manager: &crate::model::ProxyHandlerManager) {
if !GLOBAL_RESTART_TRACKER.can_restart(mcp_id) {
info!(
"Service {} is skipped during the restart cooling period.",
mcp_id
);
return;
}
let mcp_config = proxy_manager.get_mcp_config(mcp_id);
let mcp_config = match mcp_config {
Some(config) => Some(config),
None => proxy_manager.get_mcp_config_from_cache(mcp_id).await,
};
let Some(mcp_config) = mcp_config else {
warn!(
"Service {} has no configuration and cannot be restarted. Clean up resources.",
mcp_id
);
if let Err(e) = proxy_manager.cleanup_resources(mcp_id).await {
error!("Failed to cleanup resources for {}: {}", mcp_id, e);
}
return;
};
if let Err(e) = proxy_manager.cleanup_resources_for_restart(mcp_id).await {
error!("Cleanup service {} resource failed: {}", mcp_id, e);
return;
}
match mcp_start_task(mcp_config).await {
Ok((_router, _cancellation_token)) => {
proxy_manager.reset_probe_failures(mcp_id);
GLOBAL_RESTART_TRACKER.record_restart(mcp_id);
info!("Service {} restarted successfully", mcp_id);
}
Err(e) => {
error!("Service {} failed to restart: {}", mcp_id, e);
}
}
}