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:
2026-09-18 15:22:49 +08:00
parent 67f45edc3c
commit d85fbe567a
2 changed files with 105 additions and 34 deletions
+14 -2
View File
@@ -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)
+91 -32
View File
@@ -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,