fix(containers): 配额只统计 docker 句柄,修复 host 模式工作区列表 503
- ensure_container 的全局/每用户配额闸门仅在创建 docker 句柄时执行, host 句柄(会话标识,不占容器池)不再计入任何配额,修复 host 模式 下第 4 个工作区起 GET /api/conversations 返回 503 的问题 - _has_capacity / has_capacity 全局上限计数同步只统计 docker 句柄 - MAX_ACTIVE_CONTAINERS_PER_USER 默认值 3 -> 8 - admin 面板 available_slots 展示对齐为 docker 句柄计数
This commit is contained in:
parent
0522414a6c
commit
08f7adb0fd
@ -57,7 +57,8 @@ LINUX_SAFETY = os.environ.get("LINUX_SAFETY", "0") not in {"0", "false", "False"
|
|||||||
TOOLBOX_TERMINAL_IDLE_SECONDS = int(os.environ.get("TOOLBOX_TERMINAL_IDLE_SECONDS", "900"))
|
TOOLBOX_TERMINAL_IDLE_SECONDS = int(os.environ.get("TOOLBOX_TERMINAL_IDLE_SECONDS", "900"))
|
||||||
MAX_ACTIVE_USER_CONTAINERS = int(os.environ.get("MAX_ACTIVE_USER_CONTAINERS", "8"))
|
MAX_ACTIVE_USER_CONTAINERS = int(os.environ.get("MAX_ACTIVE_USER_CONTAINERS", "8"))
|
||||||
# 每用户同时活跃的容器上限(防单用户多工作区占满全局容器池,2026-09-02 审计新增)
|
# 每用户同时活跃的容器上限(防单用户多工作区占满全局容器池,2026-09-02 审计新增)
|
||||||
MAX_ACTIVE_CONTAINERS_PER_USER = int(os.environ.get("MAX_ACTIVE_CONTAINERS_PER_USER", "3"))
|
# 仅统计 docker 句柄(host 句柄不计入);默认值 8 与全局上限对齐(2026-09-02 调整)
|
||||||
|
MAX_ACTIVE_CONTAINERS_PER_USER = int(os.environ.get("MAX_ACTIVE_CONTAINERS_PER_USER", "8"))
|
||||||
HOST_EXECUTION_MODE_DEFAULT = os.environ.get("HOST_EXECUTION_MODE_DEFAULT", "sandbox").strip().lower()
|
HOST_EXECUTION_MODE_DEFAULT = os.environ.get("HOST_EXECUTION_MODE_DEFAULT", "sandbox").strip().lower()
|
||||||
# 沙箱可写路径的「部署通道」(逗号分隔)。路径授权只有两个来源:
|
# 沙箱可写路径的「部署通道」(逗号分隔)。路径授权只有两个来源:
|
||||||
# config/host_sandbox_policy.json(前端「路径授权」UI)+ 本变量(真·环境变量),
|
# config/host_sandbox_policy.json(前端「路径授权」UI)+ 本变量(真·环境变量),
|
||||||
|
|||||||
@ -158,6 +158,9 @@ class UserContainerManager:
|
|||||||
handle.touch()
|
handle.touch()
|
||||||
return handle
|
return handle
|
||||||
|
|
||||||
|
# 配额检查仅针对 docker 句柄:host 句柄只是会话标识,不占用容器池资源,
|
||||||
|
# 创建 host 句柄时直接跳过全局/每用户两道配额闸门
|
||||||
|
if mode == "docker":
|
||||||
if not self._has_capacity(key):
|
if not self._has_capacity(key):
|
||||||
raise RuntimeError(tr("container_mgr.quota_exhausted"))
|
raise RuntimeError(tr("container_mgr.quota_exhausted"))
|
||||||
|
|
||||||
@ -165,8 +168,9 @@ class UserContainerManager:
|
|||||||
per_user_limit = MAX_ACTIVE_CONTAINERS_PER_USER
|
per_user_limit = MAX_ACTIVE_CONTAINERS_PER_USER
|
||||||
if per_user_limit > 0:
|
if per_user_limit > 0:
|
||||||
owned = sum(
|
owned = sum(
|
||||||
1 for k in self._containers
|
1 for k, h in self._containers.items()
|
||||||
if k == username_norm or k.startswith(f"{username_norm}::")
|
if (k == username_norm or k.startswith(f"{username_norm}::"))
|
||||||
|
and h.mode == "docker"
|
||||||
)
|
)
|
||||||
if owned >= per_user_limit:
|
if owned >= per_user_limit:
|
||||||
raise RuntimeError(tr("container_mgr.per_user_quota_exhausted", limit=per_user_limit))
|
raise RuntimeError(tr("container_mgr.per_user_quota_exhausted", limit=per_user_limit))
|
||||||
@ -193,7 +197,8 @@ class UserContainerManager:
|
|||||||
return True
|
return True
|
||||||
if self.max_containers <= 0:
|
if self.max_containers <= 0:
|
||||||
return True
|
return True
|
||||||
return len(self._containers) < self.max_containers
|
docker_total = sum(1 for h in self._containers.values() if h.mode == "docker")
|
||||||
|
return docker_total < self.max_containers
|
||||||
|
|
||||||
def get_handle(self, container_key: str) -> Optional[ContainerHandle]:
|
def get_handle(self, container_key: str) -> Optional[ContainerHandle]:
|
||||||
key = self._normalize_username(container_key)
|
key = self._normalize_username(container_key)
|
||||||
@ -293,8 +298,11 @@ class UserContainerManager:
|
|||||||
def _has_capacity(self, username: str) -> bool:
|
def _has_capacity(self, username: str) -> bool:
|
||||||
if self.max_containers <= 0:
|
if self.max_containers <= 0:
|
||||||
return True
|
return True
|
||||||
existing = 1 if username in self._containers else 0
|
# 全局上限同样只统计 docker 句柄(host 句柄不占用容器池)
|
||||||
return (len(self._containers) - existing) < self.max_containers
|
docker_total = sum(1 for h in self._containers.values() if h.mode == "docker")
|
||||||
|
existing_handle = self._containers.get(username)
|
||||||
|
existing = 1 if (existing_handle and existing_handle.mode == "docker") else 0
|
||||||
|
return (docker_total - existing) < self.max_containers
|
||||||
|
|
||||||
def _create_handle(self, username: str, workspace: str, mode: str) -> ContainerHandle:
|
def _create_handle(self, username: str, workspace: str, mode: str) -> ContainerHandle:
|
||||||
if mode != "docker":
|
if mode != "docker":
|
||||||
|
|||||||
@ -419,7 +419,9 @@ def build_api_admin_dashboard_snapshot():
|
|||||||
max_containers = getattr(container_manager, "max_containers", None) or len(handle_map)
|
max_containers = getattr(container_manager, "max_containers", None) or len(handle_map)
|
||||||
available_slots = None
|
available_slots = None
|
||||||
if max_containers:
|
if max_containers:
|
||||||
available_slots = max(0, max_containers - len(handle_map))
|
# 可用余量只对 docker 句柄计数(host 句柄不占用容器池配额)
|
||||||
|
docker_total = sum(1 for val in handle_map.values() if val.get("mode") == "docker")
|
||||||
|
available_slots = max(0, max_containers - docker_total)
|
||||||
|
|
||||||
upload_events = collect_upload_events()
|
upload_events = collect_upload_events()
|
||||||
uploads_summary = summarize_upload_events(upload_events, quarantine_total_bytes)
|
uploads_summary = summarize_upload_events(upload_events, quarantine_total_bytes)
|
||||||
|
|||||||
@ -312,7 +312,8 @@ def collect_container_snapshots(handle_map: Dict[str, Dict[str, Any]]) -> Dict[s
|
|||||||
"host": active_total - docker_count,
|
"host": active_total - docker_count,
|
||||||
"issues": failure_count,
|
"issues": failure_count,
|
||||||
"max_containers": container_manager.max_containers,
|
"max_containers": container_manager.max_containers,
|
||||||
"available_slots": max(0, container_manager.max_containers - active_total) if container_manager.max_containers > 0 else None,
|
# 可用余量只对 docker 句柄计数(host 句柄不占用容器池配额)
|
||||||
|
"available_slots": max(0, container_manager.max_containers - docker_count) if container_manager.max_containers > 0 else None,
|
||||||
"avg_cpu_percent": round(sum(cpu_values) / len(cpu_values), 2) if cpu_values else None,
|
"avg_cpu_percent": round(sum(cpu_values) / len(cpu_values), 2) if cpu_values else None,
|
||||||
"avg_mem_percent": round(sum(mem_percent_values) / len(mem_percent_values), 2) if mem_percent_values else None,
|
"avg_mem_percent": round(sum(mem_percent_values) / len(mem_percent_values), 2) if mem_percent_values else None,
|
||||||
"total_mem_used_bytes": total_mem_used,
|
"total_mem_used_bytes": total_mem_used,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user