From 7cc6aa5b236b16ef2b012053841006dec50ee02e Mon Sep 17 00:00:00 2001 From: iomgaa Date: Thu, 16 Jul 2026 05:25:36 -0400 Subject: [PATCH] fix: reject REDIS_CACHE_TTL<=0 (kill implicit never-expire) --- .env.example | 1 + adapters/redis_cache.py | 20 ++++++++++++++++++++ app/harness/video_split_cli.py | 8 +++++--- main.py | 7 ++++--- tests/unit/test_infra_settings.py | 16 ++++++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 tests/unit/test_infra_settings.py diff --git a/.env.example b/.env.example index bb48729..86352a8 100644 --- a/.env.example +++ b/.env.example @@ -46,6 +46,7 @@ LLM_CIRCUIT_BREAKER_COOLDOWN=60 LLM_TTFT_TIMEOUT=30 LLM_INTER_TOKEN_TIMEOUT=15 LLM_RETRY_MAX_DELAY=30.0 +# 正整数秒,禁止 0(0 会被拒绝启动);训练场景建议 >= 单次训练时长 REDIS_CACHE_TTL=86400 # 建树批量并行:全局 VLM/LLM 在途调用上限(Spec-2 工程配置) diff --git a/adapters/redis_cache.py b/adapters/redis_cache.py index 39420e6..dff36fc 100644 --- a/adapters/redis_cache.py +++ b/adapters/redis_cache.py @@ -12,6 +12,26 @@ from loguru import logger from core.types import LLMResponse +def _resolve_cache_ttl(ttl: int) -> int: + """校验 Redis 缓存 TTL:必须为正整数(消灭 0=永不过期 的隐式语义)。 + + Args: + ttl: 待校验的 TTL 秒数。 + + Returns: + 校验通过的正整数 TTL。 + + Raises: + ValueError: ttl <= 0。 + """ + if ttl <= 0: + raise ValueError( + f"REDIS_CACHE_TTL 必须为正整数秒,实际 {ttl}。" + "训练场景建议 >= 单次训练时长(如 86400)。" + ) + return ttl + + class RedisResponseCache: """基于 Redis 的 LLM 响应缓存。 diff --git a/app/harness/video_split_cli.py b/app/harness/video_split_cli.py index 90a7d4f..754e8f3 100644 --- a/app/harness/video_split_cli.py +++ b/app/harness/video_split_cli.py @@ -248,13 +248,15 @@ def _build_redis_cache(settings: Any) -> Any | None: """按 .env redis_url 构建响应缓存(不可用则降级 None,与 main 一致)。""" if not settings.redis_url: return None + + from adapters.redis_cache import RedisResponseCache, _resolve_cache_ttl + + # 配置校验 fail-loud(不属于 Redis 连接故障,不得被下方降级 except 吞掉) + ttl_s = _resolve_cache_ttl(settings.redis_cache_ttl) try: import redis.asyncio as aioredis - from adapters.redis_cache import RedisResponseCache - redis_client = aioredis.from_url(settings.redis_url, decode_responses=True) - ttl_s = settings.redis_cache_ttl if settings.redis_cache_ttl > 0 else None return RedisResponseCache(redis=redis_client, ttl_s=ttl_s) except Exception: logger.warning("Redis 缓存不可用,诊断降级为无缓存模式") diff --git a/main.py b/main.py index 1185e5a..9c1c551 100644 --- a/main.py +++ b/main.py @@ -84,13 +84,14 @@ def _build_adapters(settings: InfraSettings, embed_cfg: dict) -> _Adapters: cache = None if settings.redis_url: + from adapters.redis_cache import RedisResponseCache, _resolve_cache_ttl + + # 配置校验 fail-loud(不属于 Redis 连接故障,不得被下方降级 except 吞掉) + ttl_s = _resolve_cache_ttl(settings.redis_cache_ttl) try: import redis.asyncio as aioredis - from adapters.redis_cache import RedisResponseCache - redis_client = aioredis.from_url(settings.redis_url, decode_responses=True) - ttl_s = settings.redis_cache_ttl if settings.redis_cache_ttl > 0 else None cache = RedisResponseCache(redis=redis_client, ttl_s=ttl_s) except Exception: logger.warning("Redis 缓存不可用,降级为无缓存模式") diff --git a/tests/unit/test_infra_settings.py b/tests/unit/test_infra_settings.py new file mode 100644 index 0000000..9c9f512 --- /dev/null +++ b/tests/unit/test_infra_settings.py @@ -0,0 +1,16 @@ +"""工程配置校验单元测试(Redis 缓存 TTL fail-loud)。""" + +from __future__ import annotations + +import pytest + + +def test_redis_cache_ttl_zero_rejected() -> None: + """REDIS_CACHE_TTL<=0 必须启动即报错,消灭'0=永不过期'隐式语义。""" + from adapters.redis_cache import _resolve_cache_ttl + + with pytest.raises(ValueError, match="REDIS_CACHE_TTL"): + _resolve_cache_ttl(0) + with pytest.raises(ValueError, match="REDIS_CACHE_TTL"): + _resolve_cache_ttl(-1) + assert _resolve_cache_ttl(86400) == 86400