fix(persistence): separate retryable rejection errors (#6423)

This commit is contained in:
InfinityPacer
2026-08-23 20:10:59 +08:00
committed by GitHub
parent 46d5c8538e
commit c6ffc309d4
8 changed files with 79 additions and 48 deletions
+2 -2
View File
@@ -57,7 +57,7 @@ from app.api.dependencies.plugin import (
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.external.market import PluginHelper
from app.adapters.system.plugin.package import PluginPackageManager
from app.schemas.exception import DatabaseWorkerOverloadedError
from app.schemas.exception import PersistenceUnavailableError
from app.runtime.log import logger
from app.schemas.types import SystemConfigKey
from app.api.context import get_background_task_registry, resolve_background_task_registry
@@ -893,7 +893,7 @@ async def save_plugin_folders(
folders,
)
return _SchemaResponse(success=True)
except DatabaseWorkerOverloadedError:
except PersistenceUnavailableError:
raise
except Exception as e:
logger.error(f"[文件夹API] 保存文件夹配置失败: {str(e)}")
+5 -6
View File
@@ -12,10 +12,7 @@ from app.application.database import (
AsyncDatabaseExecutor,
)
from app.schemas.agent import AgentChatSessionDetail, AgentChatSessionSummary
from app.schemas.exception import (
DatabaseWorkerClosedError,
DatabaseWorkerOverloadedError,
)
from app.schemas.exception import AgentChatPersistenceUnavailableError
from app.runtime.observability import record_metric
@@ -364,14 +361,16 @@ class AgentChatPersistenceService:
"""在线程 worker 内完成同步写入并丢弃仓储对象返回值。"""
# 同时限制全局和单会话等待量,避免一个热点会话占满总 admission 后饿死其他会话。
if self._closing:
raise DatabaseWorkerClosedError("AgentChat 持久化服务当前不可接收任务")
raise AgentChatPersistenceUnavailableError(
"AgentChat 持久化服务当前不可接收任务"
)
session_pending = self._pending_by_session.get(session_id, 0)
if (
self._pending_writes >= self._capacity
or session_pending >= self._session_capacity
):
record_metric("agent.chat.persistence.rejected")
raise DatabaseWorkerOverloadedError(
raise AgentChatPersistenceUnavailableError(
f"AgentChat 写入容量已用尽(全局上限 {self._capacity}"
f"单会话上限 {self._session_capacity}"
)
+6 -6
View File
@@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any, Optional
from app.schemas.exception import DatabaseWorkerOverloadedError
from app.schemas.exception import PersistenceUnavailableError
from app.application.plugin.lifecycle import plugin_lifecycle
from app.runtime.log import logger
@@ -196,7 +196,7 @@ class PluginInstallCommand:
message=str(err),
package_installed=False,
)
if isinstance(err, DatabaseWorkerOverloadedError):
if isinstance(err, PersistenceUnavailableError):
raise
return result
if not package_installed:
@@ -228,7 +228,7 @@ class PluginInstallCommand:
package_installed=True,
installed_list_persisted=state.installed_list_touched,
)
if isinstance(err, DatabaseWorkerOverloadedError):
if isinstance(err, PersistenceUnavailableError):
raise
return result
@@ -247,7 +247,7 @@ class PluginInstallCommand:
installed_list_persisted=installed_list_persisted,
runtime_touched=True,
)
if isinstance(err, DatabaseWorkerOverloadedError):
if isinstance(err, PersistenceUnavailableError):
raise
return result
@@ -267,7 +267,7 @@ class PluginInstallCommand:
runtime_touched=True,
registrations_touched=True,
)
if isinstance(err, DatabaseWorkerOverloadedError):
if isinstance(err, PersistenceUnavailableError):
raise
return result
@@ -412,7 +412,7 @@ class PluginInstallCommand:
failure_stage=failure_stage,
rollback=rollback,
)
if isinstance(err, DatabaseWorkerOverloadedError):
if isinstance(err, PersistenceUnavailableError):
raise
return result
+6 -11
View File
@@ -15,8 +15,7 @@ from app.adapters.web.plugin.routes import FastAPIDynamicRouteRegistry
from app.adapters.web.health import install_health_routes
from app.application.plugin.routes import configure_plugin_routes
from app.schemas.exception import (
DatabaseWorkerClosedError,
DatabaseWorkerOverloadedError,
PersistenceUnavailableError,
)
from app.adapters.web.security.access import (
configure_token_codec,
@@ -235,11 +234,11 @@ async def localized_http_exception_handler(
)
async def database_worker_overloaded_handler(
async def persistence_unavailable_handler(
request: Request,
_exc: DatabaseWorkerClosedError | DatabaseWorkerOverloadedError,
_exc: PersistenceUnavailableError,
) -> JSONResponse:
"""数据库 worker 暂不可用映射为可重试的 503 响应。"""
"""持久化能力暂不可用映射为可重试的 503 响应。"""
return await localized_http_exception_handler(
request,
HTTPException(
@@ -335,12 +334,8 @@ def create_app() -> FastAPI:
_app.add_exception_handler(HTTPException, localized_http_exception_handler)
_app.add_exception_handler(
DatabaseWorkerOverloadedError,
database_worker_overloaded_handler,
)
_app.add_exception_handler(
DatabaseWorkerClosedError,
database_worker_overloaded_handler,
PersistenceUnavailableError,
persistence_unavailable_handler,
)
_app.add_exception_handler(
RequestValidationError,
+10 -2
View File
@@ -58,14 +58,22 @@ class StorageQueryError(Exception):
pass
class DatabaseWorkerClosedError(RuntimeError):
class PersistenceUnavailableError(RuntimeError):
"""持久化执行能力暂时拒绝新操作,调用方可在稍后重试。"""
class DatabaseWorkerClosedError(PersistenceUnavailableError):
"""数据库执行器尚未启动或已经停止。"""
class DatabaseWorkerOverloadedError(RuntimeError):
class DatabaseWorkerOverloadedError(PersistenceUnavailableError):
"""数据库执行器的运行与排队容量已经用尽。"""
class AgentChatPersistenceUnavailableError(PersistenceUnavailableError):
"""AgentChat 持久化服务因关闭或自身容量限制拒绝新写入。"""
class TMDbException(Exception):
"""
用于表示TheMovieDB数据源请求失败的跨层异常契约。