Files
qiming/qiming-rcoder/crates/docker_manager/src/container_state_actor.rs
2026-06-01 13:54:52 +08:00

365 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Container State Actor
//!
//! 使用 Actor 模式管理容器状态,避免 DashMap 跨 await 持有锁导致的死锁问题。
//!
//! # 架构
//! - `ContainerStateActor`: 独占 HashMap在独立 task 中运行,处理所有状态操作
//! - `ContainerStateHandle`: 可克隆的句柄,提供 async 方法与 Actor 通信
//!
//! # 优点
//! - 完全无锁,不会死锁
//! - 状态变更顺序化,更易调试
//! - 符合 Rust async 最佳实践
use crate::DockerContainerInfo;
use std::collections::HashMap;
use tokio::sync::{mpsc, oneshot};
use tracing::{debug, error, warn};
/// Actor 通道缓冲区大小
const CHANNEL_BUFFER_SIZE: usize = 256;
/// 容器状态操作命令
#[derive(Debug)]
pub enum ContainerStateCommand {
/// 获取容器信息
Get {
key: String,
reply: oneshot::Sender<Option<DockerContainerInfo>>,
},
/// 插入/更新容器信息
Insert {
key: String,
info: DockerContainerInfo,
},
/// 移除容器信息
Remove {
key: String,
reply: oneshot::Sender<Option<DockerContainerInfo>>,
},
/// 获取所有容器列表
List {
reply: oneshot::Sender<Vec<DockerContainerInfo>>,
},
/// 获取所有 key 列表
Keys { reply: oneshot::Sender<Vec<String>> },
/// 获取容器数量
Len { reply: oneshot::Sender<usize> },
/// 检查 key 是否存在
Contains {
key: String,
reply: oneshot::Sender<bool>,
},
/// 通过回调更新容器信息(用于原地更新)
UpdateWith {
key: String,
/// 更新后的信息(如果 key 存在)
updated_info: DockerContainerInfo,
reply: oneshot::Sender<bool>,
},
/// 条件移除:只有当 container_id 匹配时才移除
RemoveIfContainerId {
key: String,
container_id: String,
reply: oneshot::Sender<Option<DockerContainerInfo>>,
},
}
/// 容器状态 Actor
///
/// 独占 HashMap在独立 task 中运行
pub struct ContainerStateActor {
containers: HashMap<String, DockerContainerInfo>,
receiver: mpsc::Receiver<ContainerStateCommand>,
}
impl ContainerStateActor {
/// 创建新的 Actor 和 Handle
pub fn new() -> (Self, ContainerStateHandle) {
let (sender, receiver) = mpsc::channel(CHANNEL_BUFFER_SIZE);
let actor = Self {
containers: HashMap::new(),
receiver,
};
let handle = ContainerStateHandle { sender };
(actor, handle)
}
/// 运行 Actor 事件循环
///
/// 这个方法应该在 `tokio::spawn` 中调用
pub async fn run(mut self) {
debug!("🚀 [ACTOR] ContainerStateActor started");
while let Some(cmd) = self.receiver.recv().await {
self.handle_command(cmd);
}
debug!("🛑 [ACTOR] ContainerStateActor stopped (all senders dropped)");
}
/// 处理单个命令
fn handle_command(&mut self, cmd: ContainerStateCommand) {
match cmd {
ContainerStateCommand::Get { key, reply } => {
let result = self.containers.get(&key).cloned();
if reply.send(result).is_err() {
warn!("[ACTOR] Get reply channel closed");
}
}
ContainerStateCommand::Insert { key, info } => {
self.containers.insert(key, info);
}
ContainerStateCommand::Remove { key, reply } => {
let result = self.containers.remove(&key);
if reply.send(result).is_err() {
warn!("[ACTOR] Remove reply channel closed");
}
}
ContainerStateCommand::List { reply } => {
let result: Vec<_> = self.containers.values().cloned().collect();
if reply.send(result).is_err() {
warn!("[ACTOR] List reply channel closed");
}
}
ContainerStateCommand::Keys { reply } => {
let result: Vec<_> = self.containers.keys().cloned().collect();
if reply.send(result).is_err() {
warn!("[ACTOR] Keys reply channel closed");
}
}
ContainerStateCommand::Len { reply } => {
if reply.send(self.containers.len()).is_err() {
warn!("[ACTOR] Len reply channel closed");
}
}
ContainerStateCommand::Contains { key, reply } => {
if reply.send(self.containers.contains_key(&key)).is_err() {
warn!("[ACTOR] Contains reply channel closed");
}
}
ContainerStateCommand::UpdateWith {
key,
updated_info,
reply,
} => {
let existed = if let std::collections::hash_map::Entry::Occupied(mut e) =
self.containers.entry(key)
{
e.insert(updated_info);
true
} else {
false
};
if reply.send(existed).is_err() {
warn!("[ACTOR] UpdateWith reply channel closed");
}
}
ContainerStateCommand::RemoveIfContainerId {
key,
container_id,
reply,
} => {
let should_remove = if let Some(info) = self.containers.get(&key) {
info.container_id == container_id
} else {
false
};
let result = if should_remove {
self.containers.remove(&key)
} else {
None
};
if reply.send(result).is_err() {
warn!("[ACTOR] RemoveIfContainerId reply channel closed");
}
}
}
}
}
/// 容器状态句柄
///
/// 可克隆,用于与 Actor 通信
#[derive(Clone)]
pub struct ContainerStateHandle {
sender: mpsc::Sender<ContainerStateCommand>,
}
impl ContainerStateHandle {
/// 获取容器信息
pub async fn get(&self, key: &str) -> Option<DockerContainerInfo> {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::Get {
key: key.to_string(),
reply,
})
.await
.is_err()
{
error!("[HANDLE] Failed to send Get command - actor stopped");
return None;
}
rx.await.unwrap_or(None)
}
/// 插入/更新容器信息
pub async fn insert(&self, key: String, info: DockerContainerInfo) {
if self
.sender
.send(ContainerStateCommand::Insert { key, info })
.await
.is_err()
{
error!("[HANDLE] Failed to send Insert command - actor stopped");
}
}
/// 移除容器信息
pub async fn remove(&self, key: &str) -> Option<DockerContainerInfo> {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::Remove {
key: key.to_string(),
reply,
})
.await
.is_err()
{
error!("[HANDLE] Failed to send Remove command - actor stopped");
return None;
}
rx.await.unwrap_or(None)
}
/// 获取所有容器列表
pub async fn list(&self) -> Vec<DockerContainerInfo> {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::List { reply })
.await
.is_err()
{
error!("[HANDLE] Failed to send List command - actor stopped");
return Vec::new();
}
rx.await.unwrap_or_default()
}
/// 获取所有 key 列表
pub async fn keys(&self) -> Vec<String> {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::Keys { reply })
.await
.is_err()
{
error!("[HANDLE] Failed to send Keys command - actor stopped");
return Vec::new();
}
rx.await.unwrap_or_default()
}
/// 获取容器数量
pub async fn len(&self) -> usize {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::Len { reply })
.await
.is_err()
{
error!("[HANDLE] Failed to send Len command - actor stopped");
return 0;
}
rx.await.unwrap_or(0)
}
/// 检查是否为空
pub async fn is_empty(&self) -> bool {
self.len().await == 0
}
/// 检查 key 是否存在
pub async fn contains_key(&self, key: &str) -> bool {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::Contains {
key: key.to_string(),
reply,
})
.await
.is_err()
{
error!("[HANDLE] Failed to send Contains command - actor stopped");
return false;
}
rx.await.unwrap_or(false)
}
/// 条件更新:如果 key 存在则更新
///
/// 返回 true 表示Update succeededfalse 表示 key 不存在
pub async fn update_if_exists(&self, key: &str, info: DockerContainerInfo) -> bool {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::UpdateWith {
key: key.to_string(),
updated_info: info,
reply,
})
.await
.is_err()
{
error!("[HANDLE] Failed to send UpdateWith command - actor stopped");
return false;
}
rx.await.unwrap_or(false)
}
/// 条件移除:只有当 container_id 匹配时才移除
///
/// 防止在清理时误删刚重启的容器CAS 操作)
pub async fn remove_if_container_id(
&self,
key: &str,
container_id: &str,
) -> Option<DockerContainerInfo> {
let (reply, rx) = oneshot::channel();
if self
.sender
.send(ContainerStateCommand::RemoveIfContainerId {
key: key.to_string(),
container_id: container_id.to_string(),
reply,
})
.await
.is_err()
{
error!("[HANDLE] Failed to send RemoveIfContainerId command - actor stopped");
return None;
}
rx.await.unwrap_or(None)
}
}
impl std::fmt::Debug for ContainerStateHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContainerStateHandle")
.field("sender_closed", &self.sender.is_closed())
.finish()
}
}
// Tests removed intentionally.
// Integration tests should be implemented in `tests/` directory if needed.