modify: rust 插件

This commit is contained in:
2026-08-31 17:18:03 +08:00
commit e59ecfc0e3
22 changed files with 3689 additions and 0 deletions
+227
View File
@@ -0,0 +1,227 @@
//! Worker 进程主流程(对齐 Python process.py `worker_serve`)
//!
//! 流程:
//! 1. 读全局 config.toml([zenoh].json5_path)→ 打开 Zenoh 会话
//! 2. 自读 config/workers/<type>/config.toml([workers] 节展平 + mqtt_topics + cache)
//! 3. 加载路由描述符,按 handler 注册表做契约自检(跳过无 handler 的端点)
//! 4. (可选)建立共享缓存,登记实例参与方并启动心跳(多实例全局分摊)
//! 5. 发布路由描述符 → 网关 RouteRegistry 动态挂载 HTTP 路由
//! 6. 建 WorkerPool:每副本槽位 = liveliness token + 业务 Queryable + __admin__ Queryable
//! 7. 每 10s 重发布描述符(自愈)
//! 8. SIGTERM/SIGINT → 摘除路由 → 优雅关停全部副本 → 摘除实例 → 关闭会话
use std::sync::Arc;
use serde_json::Value;
use tokio::signal::unix::{signal, SignalKind};
use tokio::sync::Mutex;
use crate::cache::Cache;
use crate::config::{load_global_config, load_plugin_config};
use crate::descriptor::load_routes_from_dir;
use crate::publisher::{announce_loop, RoutePublisher};
use crate::runtime::WorkerPool;
use crate::HandlerRegistry;
/// serve 入口参数
pub struct ServeOptions {
/// 全局配置文件路径(读取 [zenoh] 节)
pub config_path: String,
/// 插件配置目录(默认 "config/workers")
pub config_dir: String,
/// 副本数覆盖(None = 使用插件配置中的 count)
pub count: Option<usize>,
/// 外部进程实例标识(多实例全局分摊用;缺省 {hostname}:{pid})
pub instance: Option<String>,
}
impl Default for ServeOptions {
fn default() -> Self {
Self {
config_path: "config.toml".into(),
config_dir: "config/workers".into(),
count: None,
instance: None,
}
}
}
/// 实例参与者心跳间隔(秒,与网关/ Python 一致)
const INSTANCE_HEARTBEAT_SECS: u64 = 15;
/// 启动独立 Worker 进程(阻塞直到收到 SIGTERM/SIGINT 并完成优雅退出)
pub async fn serve(
worker_type: &str,
registry: Arc<HandlerRegistry>,
opts: ServeOptions,
) -> Result<(), String> {
// 1. 全局配置 → Zenoh 会话
let global = load_global_config(&opts.config_path)?;
tracing::info!("[{}] 读取 Zenoh 配置: {}", worker_type, global.zenoh_json5_path);
let zconfig = zenoh::Config::from_file(&global.zenoh_json5_path)
.map_err(|e| format!("加载 Zenoh 配置 {} 失败: {e}", global.zenoh_json5_path))?;
let session = zenoh::open(zconfig)
.await
.map_err(|e| format!("打开 Zenoh 会话失败: {e}"))?;
tracing::info!("[{}] Zenoh 会话已建立", worker_type);
// 2. 插件配置
let plugin = load_plugin_config(worker_type, &opts.config_dir)?;
if plugin.load_mode != "external" {
tracing::warn!(
"[{}] 插件配置 load_mode 不是 \"external\":网关也会在进程内加载该插件,\
外部进程将与网关内副本重复服务。",
worker_type,
);
}
// 2.5 共享缓存([workers.cache],进程插件自行管理)+ 实例参与方登记
let cache_cfg = plugin
.runtime
.get("workers")
.and_then(|w| w.get("cache"))
.cloned();
let (cache, backend) = match &cache_cfg {
Some(cfg) => Cache::from_config(cfg).await,
None => (Cache::None, "none".to_string()),
};
let mode = cache_cfg
.as_ref()
.and_then(|c| c.get("launch_worker_count").and_then(Value::as_str))
.unwrap_or("toml")
.to_lowercase();
let instance_id = opts.instance.clone().unwrap_or_else(default_instance_id);
if cache.enabled() {
crate::cache::heartbeat_instance(&cache, worker_type, &instance_id).await;
tracing::info!(
"[{}] 已启用配置缓存(backend={backend},mode={mode})并登记实例参与方 {}",
worker_type, instance_id,
);
}
// 3. 路由描述符 + 契约自检
let all_routes = load_routes_from_dir(worker_type, &opts.config_dir)?;
let routes = contract_self_check(worker_type, &all_routes, &registry)?;
// 4. 发布路由描述符
let publisher = Arc::new(RoutePublisher::new(session.clone(), worker_type));
publisher
.publish_routes(&routes, &plugin.mqtt_topics)
.await?;
// 5. 解析本进程应承载的副本数(多实例全局分摊)
let default_count = opts.count.unwrap_or(plugin.count).max(1);
let count = crate::cache::resolve_worker_count(
&cache,
worker_type,
default_count,
&mode,
None,
Some(&instance_id),
)
.await
.max(1);
// 6. 建 WorkerPool
let pool: Arc<Mutex<WorkerPool>> = Arc::new(Mutex::new(WorkerPool::new(
worker_type.to_string(),
session.clone(),
registry,
routes.clone(),
plugin.runtime.clone(),
opts.config_dir,
cache.clone(),
instance_id.clone(),
)));
{
let mut p = pool.lock().await;
p.set_target(count, &pool).await;
}
tracing::info!("[{worker_type}] Worker 池就绪({count} 副本)");
// 7. 周期重发布(自愈)+ 实例心跳
let announce = tokio::spawn(announce_loop(
publisher.clone(),
routes.clone(),
plugin.mqtt_topics.clone(),
));
let hb_cache = cache.clone();
let hb_type = worker_type.to_string();
let hb_instance = instance_id.clone();
let heartbeat = tokio::spawn(async move {
let mut interval =
tokio::time::interval(std::time::Duration::from_secs(INSTANCE_HEARTBEAT_SECS));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
interval.tick().await;
crate::cache::heartbeat_instance(&hb_cache, &hb_type, &hb_instance).await;
}
});
tracing::info!("[{worker_type}] Worker 进程就绪,等待退出信号...");
// 8. 等待退出信号
let mut sigterm = signal(SignalKind::terminate())
.map_err(|e| format!("注册 SIGTERM 失败: {e}"))?;
let mut sigint = signal(SignalKind::interrupt())
.map_err(|e| format!("注册 SIGINT 失败: {e}"))?;
tokio::select! {
_ = sigterm.recv() => tracing::info!("[{worker_type}] 收到 SIGTERM,优雅退出"),
_ = sigint.recv() => tracing::info!("[{worker_type}] 收到 SIGINT,优雅退出"),
}
// 9. 优雅退出:停周期任务 → 摘除路由 → 关停全部副本 → 摘除实例 → 关闭会话
announce.abort();
let _ = announce.await;
heartbeat.abort();
let _ = heartbeat.await;
publisher.unpublish().await;
{
let mut p = pool.lock().await;
p.shutdown().await;
}
if cache.enabled() {
crate::cache::unregister_instance(&cache, worker_type, &instance_id).await;
}
session
.close()
.await
.map_err(|e| format!("关闭 Zenoh 会话失败: {e}"))?;
tracing::info!("[{worker_type}] Worker 进程已退出");
Ok(())
}
/// 契约自检:跳过无 handler 的端点,无可用端点则报错
fn contract_self_check(
worker_type: &str,
all_routes: &[Value],
registry: &HandlerRegistry,
) -> Result<Vec<Value>, String> {
let mut routes: Vec<Value> = Vec::new();
for r in all_routes {
let subject = r.get("subject").and_then(Value::as_str).unwrap_or("");
let method = r.get("method").and_then(Value::as_str).unwrap_or("GET");
let path = r.get("path").and_then(Value::as_str).unwrap_or("?");
if registry.has(subject, method) {
routes.push(r.clone());
} else {
tracing::warn!(
"[{}] 跳过 {method} {path} (subject={subject}): 无对应 handler 注册",
worker_type,
);
}
}
if routes.is_empty() {
return Err(format!(
"[{worker_type}] 没有任何可发布的端点(handler 注册表为空或全部被跳过)"
));
}
tracing::info!("[{worker_type}] 契约自检通过,可发布 {} 个路由描述符", routes.len());
Ok(routes)
}
/// 默认实例 id:{hostname}:{pid}(同机多进程自动区分)
fn default_instance_id() -> String {
let host = std::env::var("HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
format!("{host}:{}", std::process::id())
}