问题: main.rs Ctrl+C handler 只置 AtomicBool, 但 manager.run() 阻塞在 rx.recv() 永久等待下一条消息, 导致 Ctrl+C 后需等任意消息到达才能退出. CLAUDE.md:162 记录的已知 P1 隐患 (王浩然发现). 修复 (3 处): 1. service_manager.rs run(): recv() -> recv_timeout(200ms), 超时则 continue 重新检查 self.running, 确保信号能被及时响应 2. service_manager.rs handle_manager_message(): Shutdown 分支广播后 置 self.running=false, 让主循环正常退出 (原本只广播不停机) 3. main.rs Ctrl+C handler: 除置 AtomicBool 外, 向 channel 注入 Message::Shutdown envelope, 唤醒 recv_timeout 并触发优雅停机 效果: Ctrl+C 后最多 200ms 退出, 而非等待下一条任意消息. 注: Windows 开发环境缺 pkg-config/libdbus, 无法本地 cargo check; 改动经 rust-analyzer 类型检查零诊断, 待目标机 (Linux ARM64) 验证.
145 lines
4.8 KiB
Rust
145 lines
4.8 KiB
Rust
use anyhow::Result;
|
||
use showen_v2::core::config::AppConfig;
|
||
use showen_v2::core::plugin_loader::PluginLoader;
|
||
use showen_v2::core::service_manager::ServiceManager;
|
||
#[cfg(not(test))]
|
||
use showen_v2::core::version_manager::VersionManager;
|
||
use showen_v2::plugins::{
|
||
ble::BlePlugin, device::DevicePlugin, http::HttpPlugin, screen::ScreenPlugin,
|
||
video::VideoPlugin, wifi::WifiPlugin,
|
||
};
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
use std::sync::Arc;
|
||
|
||
fn main() -> Result<()> {
|
||
// 解析命令行参数
|
||
let args: Vec<String> = std::env::args().collect();
|
||
|
||
// 处理 --validate 参数
|
||
if args.contains(&"--validate".to_string()) {
|
||
let config_path = args
|
||
.iter()
|
||
.skip_while(|arg| *arg != "--config")
|
||
.nth(1)
|
||
.cloned()
|
||
.unwrap_or_else(|| "configs/dog_state_machine.json".to_string());
|
||
|
||
println!("验证配置文件: {}", config_path);
|
||
let config = AppConfig::from_file(&config_path)?;
|
||
config.validate_paths()?;
|
||
println!("✓ 配置文件有效");
|
||
return Ok(());
|
||
}
|
||
|
||
// 获取配置文件路径
|
||
let config_path = args
|
||
.iter()
|
||
.skip_while(|arg| *arg != "--config")
|
||
.nth(1)
|
||
.cloned()
|
||
.or_else(|| args.get(1).cloned())
|
||
.unwrap_or_else(|| "configs/dog_state_machine.json".to_string());
|
||
|
||
println!("ShowenV2 — 数字生命窗口平台");
|
||
println!("加载配置: {}", config_path);
|
||
|
||
let config = AppConfig::from_file(&config_path)?;
|
||
config.validate_paths()?;
|
||
|
||
let mut manager = ServiceManager::new(config);
|
||
|
||
// 按依赖顺序注册插件
|
||
// 独立插件:device, screen, wifi, video, ble
|
||
// 依赖插件:http (依赖 video)
|
||
|
||
println!("注册插件...");
|
||
manager.register(Box::new(DevicePlugin::new_default()));
|
||
println!(" ✓ DevicePlugin");
|
||
|
||
manager.register(Box::new(ScreenPlugin::new()));
|
||
println!(" ✓ ScreenPlugin");
|
||
|
||
manager.register(Box::new(WifiPlugin::new()));
|
||
println!(" ✓ WifiPlugin");
|
||
|
||
manager.register(Box::new(VideoPlugin::new()));
|
||
println!(" ✓ VideoPlugin");
|
||
|
||
manager.register(Box::new(BlePlugin::new()));
|
||
println!(" ✓ BlePlugin");
|
||
|
||
manager.register(Box::new(HttpPlugin::new()));
|
||
println!(" ✓ HttpPlugin");
|
||
|
||
// 加载动态插件
|
||
let plugin_store = std::path::Path::new("plugin_store");
|
||
if plugin_store.exists() {
|
||
println!("扫描动态插件...");
|
||
#[cfg(not(test))]
|
||
manager.set_version_manager(VersionManager::new(PluginLoader::new(plugin_store)));
|
||
let loader = PluginLoader::new(plugin_store);
|
||
match loader.load_registry() {
|
||
Ok(registry) => {
|
||
for (plugin_id, entry) in ®istry.plugins {
|
||
if !entry.enabled {
|
||
println!(" - {plugin_id} (禁用)");
|
||
continue;
|
||
}
|
||
|
||
match loader.load_plugin(plugin_id, Some(&entry.active_version)) {
|
||
Ok((plugin, manifest)) => {
|
||
manager.register_dynamic_with_manifest(
|
||
Box::new(plugin),
|
||
manifest.error_policy,
|
||
entry.max_errors,
|
||
manifest.required_capabilities,
|
||
manifest.capabilities,
|
||
manifest.auto_test,
|
||
);
|
||
println!(" ✓ {} v{} (动态)", plugin_id, entry.active_version);
|
||
}
|
||
Err(e) => {
|
||
eprintln!(" ✗ {} v{} 加载失败: {e}", plugin_id, entry.active_version);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Err(e) => {
|
||
eprintln!("读取插件注册表失败: {e}");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置 Ctrl+C 信号处理
|
||
let running = Arc::new(AtomicBool::new(true));
|
||
let r = running.clone();
|
||
let shutdown_tx = manager.sender();
|
||
|
||
ctrlc::set_handler(move || {
|
||
println!("\n收到退出信号,正在关闭...");
|
||
r.store(false, Ordering::SeqCst);
|
||
// 注入 Shutdown 消息,唤醒主消息循环并触发优雅停机
|
||
let _ = shutdown_tx.send(showen_v2::core::message::Envelope {
|
||
from: "signal".to_string(),
|
||
to: showen_v2::core::message::Destination::Manager,
|
||
message: showen_v2::core::message::Message::Shutdown,
|
||
});
|
||
})?;
|
||
|
||
println!("启动所有插件...");
|
||
manager.start_all()?;
|
||
|
||
println!("ShowenV2 运行中... (按 Ctrl+C 退出)");
|
||
|
||
// 运行主循环
|
||
while running.load(Ordering::SeqCst) {
|
||
manager.run()?;
|
||
}
|
||
|
||
println!("正在停止所有插件...");
|
||
manager.stop_all()?;
|
||
println!("ShowenV2 已退出");
|
||
|
||
Ok(())
|
||
}
|