modify: 内置插件 [workers.config] 两种模式语义修正(cache 模式初始化即读缓存)
- resolve_runtime_config 按模式落实单一事实来源:toml 模式以 config.toml 为准并覆盖 写入缓存(前端读缓存 → 随之显示文件值);cache 模式以缓存为准,缓存缺失才用 toml 引导并写回,此后跨重启保持。cache 模式下 toml 与缓存不一致时提示差异键与处置方式。 - 新增 resolve_startup_config:Plugin.create_workers 在 _bootstrap()/on_init() 之前调用, 使插件实例读到与 get_config/前端一致的来源。此前只在 WorkerPool 里合并缓存,插件实例 仍按 toml 建客户端(Ark/MQTT/DB 等),表现为“前端显示缓存值、实际跑 toml”,且只有 config_set 那一刻是缓存值、进程重启即回退。 - get_config 如实回报来源:新增 _read_worker_config_section_with_source(值来自缓存 vs 回退读 toml 种子),并在回复中带 mode(toml/cache),供网关与前端标注展示值来路。 - count 语义未改:toml 模式仍用文件值覆盖缓存,cache 模式缓存有值用缓存。
This commit is contained in:
@@ -66,7 +66,10 @@ from pathlib import Path
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
from chongming_worker.decorators import auto_bind_models
|
||||
from chongming_worker.runtime import create_workers as _create_workers
|
||||
from chongming_worker.runtime import (
|
||||
create_workers as _create_workers,
|
||||
resolve_startup_config,
|
||||
)
|
||||
|
||||
|
||||
class Plugin:
|
||||
@@ -152,6 +155,12 @@ class Plugin:
|
||||
"""
|
||||
插件入口 — 由 app lifespan 调用,返回 Worker 任务列表。
|
||||
|
||||
初始化前先按配置模式解析 [workers.config] 的单一事实来源(见
|
||||
:func:`chongming_worker.runtime.resolve_startup_config`):
|
||||
- cache 模式:插件必须读到缓存值(否则会按 toml 种子建客户端,
|
||||
出现“前端显示缓存值、实际跑 toml”);
|
||||
- toml 模式:用文件值并覆盖缓存,使前端显示与运行值一致。
|
||||
|
||||
Args:
|
||||
session: Zenoh 会话
|
||||
config: Worker 配置字典(含 [workers.config] 展平后的运行时配置)
|
||||
@@ -160,7 +169,10 @@ class Plugin:
|
||||
"""
|
||||
async with self._init_lock:
|
||||
if not self._initialized:
|
||||
self.config = config or {}
|
||||
config = resolve_startup_config(
|
||||
(config or {}).get("type", self.type), config or {}, worker_manager
|
||||
)
|
||||
self.config = config
|
||||
self.logger.info("[%s] 初始化开始 (count=%d)", self.type, count)
|
||||
if self.auto_bind:
|
||||
auto_bind_models(self.type)
|
||||
|
||||
@@ -13,7 +13,7 @@ import socket
|
||||
import time
|
||||
import tomllib
|
||||
import orjson
|
||||
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
||||
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import zenoh
|
||||
|
||||
@@ -372,12 +372,8 @@ def _resolve_mode(worker_type: str, worker_manager=None) -> str:
|
||||
return _worker_modes.get(worker_type, "toml")
|
||||
|
||||
|
||||
def _read_worker_config_section(cache, worker_type: str) -> Dict[str, Any]:
|
||||
"""读该类型 [workers.config](缓存优先,缓存缺失回退读 toml 种子)。"""
|
||||
if cache is not None:
|
||||
val = cache.get(_configs_cache_key(worker_type))
|
||||
if isinstance(val, dict):
|
||||
return dict(val)
|
||||
def _read_toml_config_section(worker_type: str) -> Dict[str, Any]:
|
||||
"""读插件 config.toml 的 [workers.config] 段(缺失/损坏按空 dict)。"""
|
||||
try:
|
||||
with open(_plugin_config_path(worker_type), "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
@@ -388,6 +384,26 @@ def _read_worker_config_section(cache, worker_type: str) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
def _read_worker_config_section_with_source(
|
||||
cache, worker_type: str
|
||||
) -> Tuple[Dict[str, Any], str]:
|
||||
"""读该类型 [workers.config] 并回报实际命中来源("cache" / "toml")。
|
||||
|
||||
缓存存在但该键缺失时会回退读 toml 种子,此时来源是 "toml" 而非 "cache"——
|
||||
get_config 据此如实回报,前端才能显示值到底来自哪一侧。
|
||||
"""
|
||||
if cache is not None:
|
||||
val = cache.get(_configs_cache_key(worker_type))
|
||||
if isinstance(val, dict):
|
||||
return dict(val), "cache"
|
||||
return _read_toml_config_section(worker_type), "toml"
|
||||
|
||||
|
||||
def _read_worker_config_section(cache, worker_type: str) -> Dict[str, Any]:
|
||||
"""读该类型 [workers.config](缓存优先,缓存缺失回退读 toml 种子)。"""
|
||||
return _read_worker_config_section_with_source(cache, worker_type)[0]
|
||||
|
||||
|
||||
def _persist_worker_config_section(cache, worker_type: str, updates: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""把 updates 合并写入该类型 [workers.config](缓存侧,全局唯一)。
|
||||
|
||||
@@ -414,18 +430,14 @@ def resolve_runtime_config(
|
||||
"""启动时按模式对齐 [workers.config] 的单一事实来源,返回覆盖后的运行时配置。
|
||||
|
||||
缓存配置([workers.config])在传入插件前被展平进运行配置顶层(与 _discover_plugins 一致)。
|
||||
- "toml"(默认):用 toml 的 [workers.config] 覆盖并修正缓存(缓存缺失时写入);
|
||||
- "cache":缓存有值则用缓存覆盖;缓存缺失才用 toml 引导并同步到缓存。
|
||||
- "toml"(默认):以 config/workers/<type>/config.toml 为准——启动时把 toml 的
|
||||
[workers.config] 覆盖写入缓存(前端读缓存,因此随之显示 toml 值);运行期经
|
||||
config_set 的改动仅本次运行有效,下次启动被 toml 覆盖;
|
||||
- "cache":以缓存为准——缓存有值就用缓存(toml 仅作差异提示);缓存缺失才用
|
||||
toml 引导并同步到缓存,此后跨重启保持。
|
||||
cache 为 None 时保持传入的 config 原样(不启用缓存语义)。
|
||||
"""
|
||||
try:
|
||||
with open(_plugin_config_path(worker_type), "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
workers = data.get("workers") or {}
|
||||
toml_cfg = workers.get("config") if isinstance(workers, dict) else None
|
||||
toml_cfg = dict(toml_cfg) if isinstance(toml_cfg, dict) else {}
|
||||
except Exception: # noqa: BLE001 - toml 缺失/损坏
|
||||
toml_cfg = {}
|
||||
toml_cfg = _read_toml_config_section(worker_type)
|
||||
|
||||
if cache is None:
|
||||
# 无缓存:保持 toml 种子(经典行为,无回写)
|
||||
@@ -434,18 +446,59 @@ def resolve_runtime_config(
|
||||
key = _configs_cache_key(worker_type)
|
||||
if mode == "cache":
|
||||
cached = cache.get(key)
|
||||
if not isinstance(cached, dict):
|
||||
cached = toml_cfg
|
||||
cache[key] = cached
|
||||
return {**config, **cached}
|
||||
# toml(默认):toml 仅作为「启动种子」。缓存缺失时写入;
|
||||
# 缓存已存在(可能被运行时 config_set 更新过)则保留,不再用 toml 覆盖,
|
||||
# 否则会丢掉前端/管理指令对内置插件(inline)写入的配置——即“改了不生效”。
|
||||
cached = cache.get(key)
|
||||
if not isinstance(cached, dict):
|
||||
cached = dict(toml_cfg)
|
||||
cache[key] = cached
|
||||
return {**config, **cached}
|
||||
if isinstance(cached, dict):
|
||||
_warn_config_divergence(worker_type, toml_cfg, cached)
|
||||
return {**config, **cached}
|
||||
# 缓存缺失:用 toml 引导并写回,之后以缓存为准
|
||||
cache[key] = dict(toml_cfg)
|
||||
return {**config, **toml_cfg}
|
||||
# toml(默认):以文件为准,覆盖写入缓存,使前端(读缓存)与插件运行值一致
|
||||
cache[key] = dict(toml_cfg)
|
||||
return {**config, **toml_cfg}
|
||||
|
||||
|
||||
def resolve_startup_config(
|
||||
worker_type: str,
|
||||
config: Dict[str, Any],
|
||||
worker_manager=None,
|
||||
) -> Dict[str, Any]:
|
||||
"""插件初始化(_bootstrap/on_init)前解析出的运行时配置。
|
||||
|
||||
把 [workers.config] 的单一事实来源按模式解析进来,供 Plugin.create_workers 在
|
||||
on_init 之前调用:cache 模式下插件必须读到缓存值(否则插件按 toml 建好客户端后,
|
||||
只有 RPC/管理通道读到缓存,出现“前端显示缓存值、实际跑 toml”);toml 模式下
|
||||
用文件值并覆盖缓存,使前端显示与运行值一致。
|
||||
|
||||
无缓存(未配置缓存后端)时原样返回传入的 config。
|
||||
"""
|
||||
conf = dict(config) if isinstance(config, dict) else {}
|
||||
cache = _resolve_cache(worker_type, worker_manager)
|
||||
if cache is None:
|
||||
return conf
|
||||
mode = _resolve_mode(worker_type, worker_manager)
|
||||
logger.info("[Worker] %s 插件初始化配置已按 %s 模式解析", worker_type, mode)
|
||||
return resolve_runtime_config(worker_type, conf, cache, mode)
|
||||
|
||||
|
||||
# 已提示过 toml↔缓存差异的类型(进程内去重,避免每次解析重复刷屏)
|
||||
_divergence_notified: set = set()
|
||||
|
||||
|
||||
def _warn_config_divergence(
|
||||
worker_type: str, toml_cfg: Dict[str, Any], cached: Dict[str, Any]
|
||||
) -> None:
|
||||
"""cache 模式下 toml 与缓存不一致时提示一次(缓存为准,便于排查"改了 toml 不生效")。"""
|
||||
if worker_type in _divergence_notified:
|
||||
return
|
||||
diff = sorted(k for k in set(toml_cfg) | set(cached) if toml_cfg.get(k) != cached.get(k))
|
||||
if not diff:
|
||||
return
|
||||
_divergence_notified.add(worker_type)
|
||||
logger.info(
|
||||
"[Worker] %s 的 config.toml 与缓存存在差异(键: %s),cache 模式以缓存为准;"
|
||||
"如需以 toml 为准,请删除缓存键 %s 后重启(或改用 toml 模式)",
|
||||
worker_type, ", ".join(diff), _configs_cache_key(worker_type),
|
||||
)
|
||||
|
||||
|
||||
def _merge_runtime_config(config: Dict[str, Any], updates: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -473,6 +526,8 @@ class WorkerController:
|
||||
self.current_worker_id: str = ""
|
||||
# 该副本所属类型的配置缓存句柄(由 WorkerPool 注入,供 admin 读写缓存)
|
||||
self.cache = None
|
||||
# 该副本所属类型的配置优先来源模式("toml" 默认 / "cache",由 WorkerPool 注入)
|
||||
self.mode = "toml"
|
||||
# 配置变更后待应用的新运行时配置(由 config_set 设置,重启时生效)
|
||||
self.pending_config: Optional[Dict[str, Any]] = None
|
||||
self.pending_updates: Optional[Dict[str, Any]] = None
|
||||
@@ -537,9 +592,11 @@ async def _handle_admin_command(
|
||||
controller.draining = True
|
||||
logger.info("[Worker] %s 收到优雅排空指令(将隔离新请求并等待在途请求完成)", worker_id)
|
||||
elif cmd == ADMIN_CMD_GET_CONFIG:
|
||||
# 查询该类型的 [workers.config](缓存优先,无缓存回退读 toml 种子)。
|
||||
# 查询该类型的 [workers.config](缓存优先,缓存缺失或无缓存回退读 toml 种子)。
|
||||
# source/mode 如实回报值实际来自哪一侧,供网关与前端展示——存在缓存对象但值
|
||||
# 来自 toml 种子时必须是 "toml",否则前端会把 toml 值当成缓存值。
|
||||
cache = getattr(controller, "cache", None)
|
||||
cfg = _read_worker_config_section(cache, worker_type)
|
||||
cfg, source = _read_worker_config_section_with_source(cache, worker_type)
|
||||
query.reply(
|
||||
key_expr=query.key_expr,
|
||||
payload=orjson.dumps({
|
||||
@@ -548,7 +605,8 @@ async def _handle_admin_command(
|
||||
"worker_type": worker_type,
|
||||
"worker_id": worker_id,
|
||||
"config": cfg,
|
||||
"source": "cache" if cache is not None else "toml",
|
||||
"source": source,
|
||||
"mode": getattr(controller, "mode", "toml"),
|
||||
}),
|
||||
)
|
||||
return
|
||||
@@ -1123,6 +1181,7 @@ class WorkerPool:
|
||||
self._next_index += 1
|
||||
controller = WorkerController(self.worker_type, index)
|
||||
controller.cache = self.cache
|
||||
controller.mode = self.mode
|
||||
task = asyncio.create_task(
|
||||
_managed_worker(
|
||||
self.session, self.worker_type, controller, self.config,
|
||||
|
||||
Reference in New Issue
Block a user