添加qiming-rcoder模块

This commit is contained in:
Codex
2026-06-01 13:54:52 +08:00
parent 8092c4b1f8
commit 4b1a580132
539 changed files with 151650 additions and 0 deletions

View File

@@ -0,0 +1,600 @@
//! 统一的容器停止模块
//!
//! 提供两种容器停止策略:
//! 1. 启动时清理startup_cleanup用于服务启动时清理遗留容器
//! - 使用5秒超时
//! - 过滤409冲突错误容器已在删除中
//! - 不阻塞服务启动
//!
//! 2. 运行时清理runtime_cleanup用于运行时快速清理容器
//! - 使用3秒优雅停止超时
//! - 超时后立即强制停止
//! - 快速释放资源
//!
//! # 使用示例
//!
//! ```rust,no_run
//! use docker_manager::container_stop;
//! use docker_manager::DockerManager;
//! use std::sync::Arc;
//!
//! # async fn example() -> anyhow::Result<()> {
//! # let docker_manager = Arc::new(DockerManager::new(Default::default()).await?);
//!
//! // 启动时清理
//! let result = container_stop::startup_cleanup_containers(
//! &docker_manager,
//! "rcoder-agent-*"
//! ).await?;
//!
//! // 运行时清理单个容器
//! container_stop::runtime_cleanup_container(
//! &docker_manager,
//! "container_id_123"
//! ).await?;
//! # Ok(())
//! # }
//! ```
use crate::{CleanupResult, ContainerRemovalFailure, DockerError, DockerManager, DockerResult};
use std::sync::Arc;
use std::time::Instant;
use tracing::{info, warn};
/// 启动清理超时时间(秒)
///
/// 启动时使用较短的超时时间,快速清理遗留容器
const STARTUP_CLEANUP_TIMEOUT_SECONDS: u64 = 5;
/// 运行时清理超时时间(秒)
///
/// 运行时给容器3秒优雅退出时间然后强制停止
const RUNTIME_CLEANUP_TIMEOUT_SECONDS: u64 = 3;
/// 容器停止后的等待时间(毫秒)
///
/// 给Docker一些时间完成清理操作
const POST_STOP_WAIT_MS: u64 = 100;
/// 启动时容器清理策略
///
/// 用于服务启动时清理遗留的容器。此函数会:
/// - 查找匹配指定模式的所有容器
/// - 🚀 并发停止所有容器(提高清理速度)
/// - 使用5秒超时停止每个容器
/// - 过滤409冲突错误容器已在删除中
/// - 返回详细的清理统计信息
///
/// # Arguments
///
/// * `docker_manager` - Docker管理器实例
/// * `pattern` - 容器名称匹配模式(如 "rcoder-agent-*"
///
/// # Returns
///
/// 返回 `CleanupResult` 包含清理统计信息
///
/// # Examples
///
/// ```rust,no_run
/// use docker_manager::container_stop;
/// use docker_manager::DockerManager;
/// use std::sync::Arc;
///
/// # async fn example() -> anyhow::Result<()> {
/// # let docker_manager = Arc::new(DockerManager::new(Default::default()).await?);
///
/// let result = container_stop::startup_cleanup_containers(
/// &docker_manager,
/// "rcoder-agent-*"
/// ).await?;
///
/// println!("cleanup message {} message container", result.successfully_removed);
/// # Ok(())
/// # }
/// ```
pub async fn startup_cleanup_containers(
docker_manager: &Arc<DockerManager>,
pattern: &str,
) -> DockerResult<CleanupResult> {
info!(
"[STARTUP_CLEANUP] Starting cleanup container: pattern={}",
pattern
);
let start_time = Instant::now();
// 查找匹配模式的容器
let matched_containers = docker_manager.list_containers_with_pattern(pattern).await?;
let total_found = matched_containers.len();
info!(
"[STARTUP_CLEANUP] Found {} containers",
total_found
);
if total_found == 0 {
return Ok(CleanupResult {
total_found: 0,
successfully_removed: 0,
failed_removals: 0,
skipped_running: 0,
removed_container_ids: Vec::new(),
failed_removals_details: Vec::new(),
duration_ms: start_time.elapsed().as_millis() as u64,
});
}
let mut successfully_removed = 0;
let mut failed_removals = 0;
let mut removed_container_ids = Vec::new();
let mut failed_removals_details = Vec::new();
// 🚀 并发停止所有容器
let mut tasks = Vec::new();
for container in &matched_containers {
if let Some(container_id) = &container.id {
let container_name = container
.names
.as_ref()
.and_then(|names| names.first())
.map(|n| n.trim_start_matches('/'))
.unwrap_or("unknown")
.to_string();
let docker_manager_clone = Arc::clone(docker_manager);
let container_id_clone = container_id.clone();
let task = tokio::spawn(async move {
let result =
stop_container_startup_mode(&docker_manager_clone, &container_id_clone).await;
(container_id_clone, container_name, result)
});
tasks.push(task);
}
}
// 等待所有任务完成
for task in tasks {
if let Ok((container_id, container_name, result)) = task.await {
match result {
Ok(_) => {
successfully_removed += 1;
removed_container_ids.push(container_id.clone());
info!(
"[STARTUP_CLEANUP] Container cleanup succeeded: container_id={}, name={}",
container_id, container_name
);
}
Err(e) => {
// 检查是否为409冲突错误
if is_409_conflict_error(&e) {
info!(
"[STARTUP_CLEANUP] Container already being removed, skipping: container_id={}, name={}",
container_id, container_name
);
// 409错误不计入失败统计
successfully_removed += 1;
removed_container_ids.push(container_id);
} else {
failed_removals += 1;
warn!(
"[STARTUP_CLEANUP] Container cleanup failed: container_id={}, name={}, error={}",
container_id, container_name, e
);
failed_removals_details.push(ContainerRemovalFailure {
container_id,
container_name,
error_message: e.to_string(),
});
}
}
}
}
}
let duration_ms = start_time.elapsed().as_millis() as u64;
info!(
"[STARTUP_CLEANUP] Cleanup completed: total={}, success={}, failed={}, duration={}ms",
total_found, successfully_removed, failed_removals, duration_ms
);
Ok(CleanupResult {
total_found,
successfully_removed,
failed_removals,
skipped_running: 0, // 启动清理不跳过运行中的容器
removed_container_ids,
failed_removals_details,
duration_ms,
})
}
/// 停止单个容器(启动模式)
///
/// 使用启动清理的超时设置停止容器
///
/// # Arguments
///
/// * `docker_manager` - Docker管理器实例
/// * `container_id` - 容器ID
///
/// # Returns
///
/// 成功返回 `Ok(())`,失败返回 `DockerError`
async fn stop_container_startup_mode(
docker_manager: &Arc<DockerManager>,
container_id: &str,
) -> DockerResult<()> {
docker_manager
.stop_container_by_id_with_timeout(container_id, STARTUP_CLEANUP_TIMEOUT_SECONDS)
.await?;
// 给Docker一些时间完成清理
tokio::time::sleep(tokio::time::Duration::from_millis(POST_STOP_WAIT_MS)).await;
Ok(())
}
/// 检查是否为409冲突错误
///
/// 409错误表示容器已经在删除过程中这在启动清理时是正常情况
///
/// # Arguments
///
/// * `error` - Docker错误
///
/// # Returns
///
/// 如果是409冲突错误返回 `true`,否则返回 `false`
fn is_409_conflict_error(error: &DockerError) -> bool {
// 检查是否是 Docker 409 冲突错误(容器删除已在进行中)
matches!(
error,
DockerError::BollardError(bollard::errors::Error::DockerResponseServerError {
status_code: 409,
..
})
)
}
/// 运行时容器清理策略(单个容器)
///
/// 用于运行时快速清理单个容器。此函数会:
/// - 使用3秒优雅停止超时
/// - 超时后立即强制停止
/// - 快速释放资源
///
/// # Arguments
///
/// * `docker_manager` - Docker管理器实例
/// * `container_id` - 容器ID
///
/// # Returns
///
/// 成功返回 `Ok(())`,失败返回 `DockerError`
///
/// # Examples
///
/// ```rust,no_run
/// use docker_manager::container_stop;
/// use docker_manager::DockerManager;
/// use std::sync::Arc;
///
/// # async fn example() -> anyhow::Result<()> {
/// # let docker_manager = Arc::new(DockerManager::new(Default::default()).await?);
///
/// container_stop::runtime_cleanup_container(
/// &docker_manager,
/// "container_id_123"
/// ).await?;
/// # Ok(())
/// # }
/// ```
pub async fn runtime_cleanup_container(
docker_manager: &Arc<DockerManager>,
container_id: &str,
) -> DockerResult<()> {
info!(
"[RUNTIME_CLEANUP] Starting to stop container: container_id={}",
container_id
);
match stop_container_runtime_mode(docker_manager, container_id).await {
Ok(_) => {
info!(
"[RUNTIME_CLEANUP] Container stop succeeded: container_id={}",
container_id
);
Ok(())
}
Err(e) => {
warn!(
"[RUNTIME_CLEANUP] Container stop failed: container_id={}, error={}",
container_id, e
);
Err(e)
}
}
}
/// 运行时容器清理策略(批量)
///
/// 用于运行时批量清理多个容器。此函数会:
/// - 🚀 并发停止所有容器(提高清理速度)
/// - 使用3秒优雅停止超时
/// - 超时后立即强制停止
/// - 返回详细的清理统计信息
///
/// # Arguments
///
/// * `docker_manager` - Docker管理器实例
/// * `container_ids` - 容器ID列表
///
/// # Returns
///
/// 返回 `CleanupResult` 包含清理统计信息
///
/// # Examples
///
/// ```rust,no_run
/// use docker_manager::container_stop;
/// use docker_manager::DockerManager;
/// use std::sync::Arc;
///
/// # async fn example() -> anyhow::Result<()> {
/// # let docker_manager = Arc::new(DockerManager::new(Default::default()).await?);
///
/// let container_ids = vec!["id1".to_string(), "id2".to_string()];
/// let result = container_stop::runtime_cleanup_containers(
/// &docker_manager,
/// container_ids
/// ).await?;
/// # Ok(())
/// # }
/// ```
pub async fn runtime_cleanup_containers(
docker_manager: &Arc<DockerManager>,
container_ids: Vec<String>,
) -> DockerResult<CleanupResult> {
info!(
"[RUNTIME_CLEANUP] Starting batch container cleanup: count={}",
container_ids.len()
);
let start_time = Instant::now();
let total_found = container_ids.len();
let mut successfully_removed = 0;
let mut failed_removals = 0;
let mut removed_container_ids = Vec::new();
let mut failed_removals_details = Vec::new();
// 🚀 并发停止所有容器
let mut tasks = Vec::new();
for container_id in &container_ids {
let docker_manager_clone = Arc::clone(docker_manager);
let container_id_clone = container_id.clone();
let task = tokio::spawn(async move {
let result =
stop_container_runtime_mode(&docker_manager_clone, &container_id_clone).await;
(container_id_clone, result)
});
tasks.push(task);
}
// 等待所有任务完成
for task in tasks {
if let Ok((container_id, result)) = task.await {
match result {
Ok(_) => {
successfully_removed += 1;
removed_container_ids.push(container_id.clone());
info!(
"[RUNTIME_CLEANUP] Container cleanup succeeded: container_id={}",
container_id
);
}
Err(e) => {
failed_removals += 1;
warn!(
"[RUNTIME_CLEANUP] Container cleanup failed: container_id={}, error={}",
container_id, e
);
failed_removals_details.push(ContainerRemovalFailure {
container_id: container_id.clone(),
container_name: container_id.clone(), // 批量清理时可能不知道名称
error_message: e.to_string(),
});
}
}
}
}
let duration_ms = start_time.elapsed().as_millis() as u64;
info!(
"[RUNTIME_CLEANUP] Batch cleanup completed: total={}, success={}, failed={}, duration={}ms",
total_found, successfully_removed, failed_removals, duration_ms
);
Ok(CleanupResult {
total_found,
successfully_removed,
failed_removals,
skipped_running: 0, // 运行时清理不跳过运行中的容器
removed_container_ids,
failed_removals_details,
duration_ms,
})
}
/// 停止单个容器(运行时模式)
///
/// 使用运行时清理的超时设置停止容器
///
/// # Arguments
///
/// * `docker_manager` - Docker管理器实例
/// * `container_id` - 容器ID
///
/// # Returns
///
/// 成功返回 `Ok(())`,失败返回 `DockerError`
async fn stop_container_runtime_mode(
docker_manager: &Arc<DockerManager>,
container_id: &str,
) -> DockerResult<()> {
docker_manager
.stop_container_by_id_with_timeout(container_id, RUNTIME_CLEANUP_TIMEOUT_SECONDS)
.await?;
// 给Docker一些时间完成清理
tokio::time::sleep(tokio::time::Duration::from_millis(POST_STOP_WAIT_MS)).await;
Ok(())
}
/// 为所有启用的服务构建容器清理模式列表
///
/// 从多镜像配置中获取所有启用的服务类型,并生成对应的容器名称模式。
/// 使用 ServiceImageConfig.container_prefix() 获取配置的前缀,确保与容器创建时使用的前缀一致。
///
/// # Arguments
///
/// * `multi_image_config` - 多镜像配置
///
/// # Returns
///
/// 返回容器名称模式列表,如 `["rcoder-agent-*", "rcoder-computer-agent-runner-*"]`
///
/// # Examples
///
/// ```rust,no_run
/// use docker_manager::container_stop;
/// use shared_types;
///
/// let config = shared_types::create_default_multi_image_config();
/// let patterns = container_stop::get_container_patterns_for_enabled_services(&config);
/// println!("container message : {:?}", patterns);
/// ```
pub fn get_container_patterns_for_enabled_services(
multi_image_config: &shared_types::MultiImageConfig,
) -> Vec<String> {
// 直接遍历 services获取启用的服务配置并使用其 container_prefix()
multi_image_config
.services
.values()
.filter(|config| config.enabled)
.map(|config| {
let prefix = config.container_prefix();
let pattern = format!("{}-*", prefix);
tracing::debug!(
"🔍 [CLEANUP_PATTERN] Service type: {:?}, using prefix: {}, pattern: {}",
config.service_type,
prefix,
pattern
);
pattern
})
.collect()
}
/// 清理所有启用服务的容器(启动时清理)
///
/// 自动从配置中获取所有启用的服务,并清理对应的容器。此函数会:
/// - 从配置中读取启用的服务类型
/// - 为每个服务类型生成容器模式
/// - 并行清理多个服务类型的容器
/// - 聚合所有清理结果
///
/// # Arguments
///
/// * `docker_manager` - Docker管理器实例
/// * `multi_image_config` - 多镜像配置
///
/// # Returns
///
/// 返回聚合的 `CleanupResult` 包含所有服务的清理统计信息
///
/// # Examples
///
/// ```rust,no_run
/// use docker_manager::container_stop;
/// use docker_manager::DockerManager;
/// use shared_types;
/// use std::sync::Arc;
///
/// # async fn example() -> anyhow::Result<()> {
/// # let docker_manager = Arc::new(DockerManager::new(Default::default()).await?);
/// let config = shared_types::create_default_multi_image_config();
///
/// let result = container_stop::startup_cleanup_all_enabled_services(
/// &docker_manager,
/// &config
/// ).await?;
///
/// println!("cleanup message {} message container", result.successfully_removed);
/// # Ok(())
/// # }
/// ```
pub async fn startup_cleanup_all_enabled_services(
docker_manager: &Arc<DockerManager>,
multi_image_config: &shared_types::MultiImageConfig,
) -> DockerResult<CleanupResult> {
let patterns = get_container_patterns_for_enabled_services(multi_image_config);
if patterns.is_empty() {
warn!("No patterns, skip container cleanup");
return Ok(CleanupResult::default());
}
info!("🧹 Starting cleanup container: {:?}", patterns);
let start_time = Instant::now();
// 并行清理多个服务类型的容器
let cleanup_tasks: Vec<_> = patterns
.into_iter()
.map(|pattern| {
let docker_manager = docker_manager.clone();
let pattern_clone = pattern.clone();
tokio::spawn(async move {
startup_cleanup_containers(&docker_manager, &pattern_clone).await
})
})
.collect();
// 聚合所有清理结果
let mut aggregated_result = CleanupResult::default();
for task in cleanup_tasks {
match task.await {
Ok(Ok(result)) => {
aggregated_result.total_found += result.total_found;
aggregated_result.successfully_removed += result.successfully_removed;
aggregated_result.failed_removals += result.failed_removals;
aggregated_result.skipped_running += result.skipped_running;
aggregated_result
.removed_container_ids
.extend(result.removed_container_ids);
aggregated_result
.failed_removals_details
.extend(result.failed_removals_details);
}
Ok(Err(e)) => {
warn!("cleanup container failed: {}", e);
}
Err(e) => {
warn!("cleanup failed: {}", e);
}
}
}
aggregated_result.duration_ms = start_time.elapsed().as_millis() as u64;
info!(
"[MULTI_SERVICE_CLEANUP] Multi-service cleanup completed: total={}, success={}, failed={}, duration={}ms",
aggregated_result.total_found,
aggregated_result.successfully_removed,
aggregated_result.failed_removals,
aggregated_result.duration_ms
);
Ok(aggregated_result)
}