2025-06-07 02:54:46 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
會話清理管理器
|
|
|
|
|
==============
|
|
|
|
|
|
|
|
|
|
統一管理 Web 會話的清理策略、統計和性能監控。
|
|
|
|
|
與內存監控系統深度集成,提供智能清理決策。
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import threading
|
2025-06-11 03:25:08 +08:00
|
|
|
|
import time
|
|
|
|
|
from collections.abc import Callable
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from datetime import datetime
|
2025-06-07 02:54:46 +08:00
|
|
|
|
from enum import Enum
|
2025-06-11 03:25:08 +08:00
|
|
|
|
from typing import Any
|
2025-06-07 02:54:46 +08:00
|
|
|
|
|
|
|
|
|
from ...debug import web_debug_log as debug_log
|
|
|
|
|
from ...utils.error_handler import ErrorHandler, ErrorType
|
|
|
|
|
from ..models.feedback_session import CleanupReason, SessionStatus
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class CleanupPolicy:
|
|
|
|
|
"""清理策略配置"""
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
max_idle_time: int = 1800 # 最大空閒時間(秒)
|
|
|
|
|
max_session_age: int = 7200 # 最大會話年齡(秒)
|
|
|
|
|
max_sessions: int = 10 # 最大會話數量
|
|
|
|
|
cleanup_interval: int = 300 # 清理間隔(秒)
|
|
|
|
|
memory_pressure_threshold: float = 0.8 # 內存壓力閾值
|
|
|
|
|
enable_auto_cleanup: bool = True # 啟用自動清理
|
|
|
|
|
preserve_active_session: bool = True # 保護活躍會話
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class CleanupStats:
|
|
|
|
|
"""清理統計數據"""
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
total_cleanups: int = 0
|
|
|
|
|
expired_cleanups: int = 0
|
|
|
|
|
memory_pressure_cleanups: int = 0
|
|
|
|
|
manual_cleanups: int = 0
|
|
|
|
|
auto_cleanups: int = 0
|
|
|
|
|
total_sessions_cleaned: int = 0
|
|
|
|
|
total_cleanup_time: float = 0.0
|
|
|
|
|
average_cleanup_time: float = 0.0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
last_cleanup_time: datetime | None = None
|
2025-06-07 02:54:46 +08:00
|
|
|
|
cleanup_efficiency: float = 0.0 # 清理效率(清理的會話數/總會話數)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CleanupTrigger(Enum):
|
|
|
|
|
"""清理觸發器類型"""
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
AUTO = "auto" # 自動清理
|
|
|
|
|
MEMORY_PRESSURE = "memory_pressure" # 內存壓力
|
|
|
|
|
MANUAL = "manual" # 手動清理
|
|
|
|
|
EXPIRED = "expired" # 過期清理
|
|
|
|
|
CAPACITY = "capacity" # 容量限制
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SessionCleanupManager:
|
|
|
|
|
"""會話清理管理器"""
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def __init__(self, web_ui_manager, policy: CleanupPolicy = None):
|
|
|
|
|
"""
|
|
|
|
|
初始化會話清理管理器
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
Args:
|
|
|
|
|
web_ui_manager: WebUIManager 實例
|
|
|
|
|
policy: 清理策略配置
|
|
|
|
|
"""
|
|
|
|
|
self.web_ui_manager = web_ui_manager
|
|
|
|
|
self.policy = policy or CleanupPolicy()
|
|
|
|
|
self.stats = CleanupStats()
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 清理狀態
|
|
|
|
|
self.is_running = False
|
2025-06-11 03:25:08 +08:00
|
|
|
|
self.cleanup_thread: threading.Thread | None = None
|
2025-06-07 02:54:46 +08:00
|
|
|
|
self._stop_event = threading.Event()
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 回調函數
|
2025-06-11 03:25:08 +08:00
|
|
|
|
self.cleanup_callbacks: list[Callable] = []
|
|
|
|
|
self.stats_callbacks: list[Callable] = []
|
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 清理歷史記錄
|
2025-06-11 03:25:08 +08:00
|
|
|
|
self.cleanup_history: list[dict[str, Any]] = []
|
2025-06-07 02:54:46 +08:00
|
|
|
|
self.max_history = 100
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
debug_log("SessionCleanupManager 初始化完成")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def start_auto_cleanup(self) -> bool:
|
|
|
|
|
"""啟動自動清理"""
|
|
|
|
|
if not self.policy.enable_auto_cleanup:
|
|
|
|
|
debug_log("自動清理已禁用")
|
|
|
|
|
return False
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
if self.is_running:
|
|
|
|
|
debug_log("自動清理已在運行")
|
|
|
|
|
return True
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
try:
|
|
|
|
|
self.is_running = True
|
|
|
|
|
self._stop_event.clear()
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
self.cleanup_thread = threading.Thread(
|
|
|
|
|
target=self._auto_cleanup_loop,
|
|
|
|
|
name="SessionCleanupManager",
|
2025-06-11 03:25:08 +08:00
|
|
|
|
daemon=True,
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
|
|
|
|
self.cleanup_thread.start()
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
debug_log(f"自動清理已啟動,間隔 {self.policy.cleanup_interval} 秒")
|
|
|
|
|
return True
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
self.is_running = False
|
|
|
|
|
error_id = ErrorHandler.log_error_with_context(
|
2025-06-11 03:25:08 +08:00
|
|
|
|
e, context={"operation": "啟動自動清理"}, error_type=ErrorType.SYSTEM
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
|
|
|
|
debug_log(f"啟動自動清理失敗 [錯誤ID: {error_id}]: {e}")
|
|
|
|
|
return False
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def stop_auto_cleanup(self) -> bool:
|
|
|
|
|
"""停止自動清理"""
|
|
|
|
|
if not self.is_running:
|
|
|
|
|
debug_log("自動清理未在運行")
|
|
|
|
|
return True
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
try:
|
|
|
|
|
self.is_running = False
|
|
|
|
|
self._stop_event.set()
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
if self.cleanup_thread and self.cleanup_thread.is_alive():
|
|
|
|
|
self.cleanup_thread.join(timeout=5)
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
debug_log("自動清理已停止")
|
|
|
|
|
return True
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
error_id = ErrorHandler.log_error_with_context(
|
2025-06-11 03:25:08 +08:00
|
|
|
|
e, context={"operation": "停止自動清理"}, error_type=ErrorType.SYSTEM
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
|
|
|
|
debug_log(f"停止自動清理失敗 [錯誤ID: {error_id}]: {e}")
|
|
|
|
|
return False
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def _auto_cleanup_loop(self):
|
|
|
|
|
"""自動清理主循環"""
|
|
|
|
|
debug_log("自動清理循環開始")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
while not self._stop_event.is_set():
|
|
|
|
|
try:
|
|
|
|
|
# 執行清理檢查
|
|
|
|
|
self._perform_auto_cleanup()
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 等待下次清理
|
|
|
|
|
if self._stop_event.wait(self.policy.cleanup_interval):
|
|
|
|
|
break
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
error_id = ErrorHandler.log_error_with_context(
|
|
|
|
|
e,
|
|
|
|
|
context={"operation": "自動清理循環"},
|
2025-06-11 03:25:08 +08:00
|
|
|
|
error_type=ErrorType.SYSTEM,
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
|
|
|
|
debug_log(f"自動清理循環錯誤 [錯誤ID: {error_id}]: {e}")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 發生錯誤時等待較短時間後重試
|
|
|
|
|
if self._stop_event.wait(30):
|
|
|
|
|
break
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
debug_log("自動清理循環結束")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def _perform_auto_cleanup(self):
|
|
|
|
|
"""執行自動清理"""
|
|
|
|
|
cleanup_start_time = time.time()
|
|
|
|
|
cleaned_sessions = 0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
try:
|
|
|
|
|
# 1. 檢查會話數量限制
|
|
|
|
|
if len(self.web_ui_manager.sessions) > self.policy.max_sessions:
|
|
|
|
|
cleaned = self._cleanup_by_capacity()
|
|
|
|
|
cleaned_sessions += cleaned
|
|
|
|
|
debug_log(f"容量限制清理了 {cleaned} 個會話")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 2. 清理過期會話
|
|
|
|
|
cleaned = self._cleanup_expired_sessions()
|
|
|
|
|
cleaned_sessions += cleaned
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 3. 清理空閒會話
|
|
|
|
|
cleaned = self._cleanup_idle_sessions()
|
|
|
|
|
cleaned_sessions += cleaned
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 4. 更新統計
|
|
|
|
|
cleanup_duration = time.time() - cleanup_start_time
|
|
|
|
|
self._update_cleanup_stats(
|
2025-06-11 03:25:08 +08:00
|
|
|
|
CleanupTrigger.AUTO, cleaned_sessions, cleanup_duration
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
if cleaned_sessions > 0:
|
2025-06-11 03:25:08 +08:00
|
|
|
|
debug_log(
|
|
|
|
|
f"自動清理完成,清理了 {cleaned_sessions} 個會話,耗時: {cleanup_duration:.2f}秒"
|
|
|
|
|
)
|
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
error_id = ErrorHandler.log_error_with_context(
|
2025-06-11 03:25:08 +08:00
|
|
|
|
e, context={"operation": "執行自動清理"}, error_type=ErrorType.SYSTEM
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
|
|
|
|
debug_log(f"執行自動清理失敗 [錯誤ID: {error_id}]: {e}")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def trigger_cleanup(self, trigger: CleanupTrigger, force: bool = False) -> int:
|
|
|
|
|
"""觸發清理操作"""
|
|
|
|
|
cleanup_start_time = time.time()
|
|
|
|
|
cleaned_sessions = 0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
try:
|
|
|
|
|
debug_log(f"觸發清理操作,觸發器: {trigger.value},強制: {force}")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
if trigger == CleanupTrigger.MEMORY_PRESSURE:
|
2025-06-11 03:25:08 +08:00
|
|
|
|
cleaned_sessions = (
|
|
|
|
|
self.web_ui_manager.cleanup_sessions_by_memory_pressure(force)
|
|
|
|
|
)
|
2025-06-07 02:54:46 +08:00
|
|
|
|
elif trigger == CleanupTrigger.EXPIRED:
|
|
|
|
|
cleaned_sessions = self.web_ui_manager.cleanup_expired_sessions()
|
|
|
|
|
elif trigger == CleanupTrigger.CAPACITY:
|
|
|
|
|
cleaned_sessions = self._cleanup_by_capacity()
|
|
|
|
|
elif trigger == CleanupTrigger.MANUAL:
|
|
|
|
|
# 手動清理:組合多種策略
|
|
|
|
|
cleaned_sessions += self.web_ui_manager.cleanup_expired_sessions()
|
|
|
|
|
if force:
|
2025-06-11 03:25:08 +08:00
|
|
|
|
cleaned_sessions += (
|
|
|
|
|
self.web_ui_manager.cleanup_sessions_by_memory_pressure(force)
|
|
|
|
|
)
|
2025-06-07 02:54:46 +08:00
|
|
|
|
else:
|
|
|
|
|
# 自動清理
|
|
|
|
|
self._perform_auto_cleanup()
|
|
|
|
|
return 0 # 統計已在 _perform_auto_cleanup 中更新
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 更新統計
|
|
|
|
|
cleanup_duration = time.time() - cleanup_start_time
|
|
|
|
|
self._update_cleanup_stats(trigger, cleaned_sessions, cleanup_duration)
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
|
|
|
|
debug_log(
|
|
|
|
|
f"清理操作完成,清理了 {cleaned_sessions} 個會話,耗時: {cleanup_duration:.2f}秒"
|
|
|
|
|
)
|
2025-06-07 02:54:46 +08:00
|
|
|
|
return cleaned_sessions
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
error_id = ErrorHandler.log_error_with_context(
|
|
|
|
|
e,
|
2025-06-11 03:25:08 +08:00
|
|
|
|
context={
|
|
|
|
|
"operation": "觸發清理",
|
|
|
|
|
"trigger": trigger.value,
|
|
|
|
|
"force": force,
|
|
|
|
|
},
|
|
|
|
|
error_type=ErrorType.SYSTEM,
|
2025-06-07 02:54:46 +08:00
|
|
|
|
)
|
|
|
|
|
debug_log(f"觸發清理操作失敗 [錯誤ID: {error_id}]: {e}")
|
|
|
|
|
return 0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
def _cleanup_by_capacity(self) -> int:
|
|
|
|
|
"""根據容量限制清理會話"""
|
|
|
|
|
sessions = self.web_ui_manager.sessions
|
|
|
|
|
if len(sessions) <= self.policy.max_sessions:
|
|
|
|
|
return 0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 計算需要清理的會話數量
|
|
|
|
|
excess_count = len(sessions) - self.policy.max_sessions
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 按優先級排序會話(優先清理舊的、非活躍的會話)
|
|
|
|
|
session_priorities = []
|
|
|
|
|
for session_id, session in sessions.items():
|
|
|
|
|
# 跳過當前活躍會話(如果啟用保護)
|
2025-06-11 03:25:08 +08:00
|
|
|
|
if (
|
|
|
|
|
self.policy.preserve_active_session
|
|
|
|
|
and self.web_ui_manager.current_session
|
|
|
|
|
and session.session_id == self.web_ui_manager.current_session.session_id
|
|
|
|
|
):
|
2025-06-07 02:54:46 +08:00
|
|
|
|
continue
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 計算優先級分數(分數越高越優先清理)
|
|
|
|
|
priority_score = 0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 狀態優先級
|
2025-06-11 03:25:08 +08:00
|
|
|
|
if session.status in [
|
|
|
|
|
SessionStatus.COMPLETED,
|
|
|
|
|
SessionStatus.ERROR,
|
|
|
|
|
SessionStatus.TIMEOUT,
|
|
|
|
|
]:
|
2025-06-07 02:54:46 +08:00
|
|
|
|
priority_score += 100
|
|
|
|
|
elif session.status == SessionStatus.FEEDBACK_SUBMITTED:
|
|
|
|
|
priority_score += 50
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 年齡優先級
|
|
|
|
|
age = session.get_age()
|
|
|
|
|
priority_score += age / 60 # 每分鐘加1分
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 空閒時間優先級
|
|
|
|
|
idle_time = session.get_idle_time()
|
|
|
|
|
priority_score += idle_time / 30 # 每30秒加1分
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
session_priorities.append((session_id, session, priority_score))
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
# 按優先級排序並清理
|
|
|
|
|
session_priorities.sort(key=lambda x: x[2], reverse=True)
|
|
|
|
|
cleaned_count = 0
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
for i in range(min(excess_count, len(session_priorities))):
|
|
|
|
|
session_id, session, _ = session_priorities[i]
|
|
|
|
|
try:
|
|
|
|
|
session._cleanup_sync_enhanced(CleanupReason.MANUAL)
|
|
|
|
|
del self.web_ui_manager.sessions[session_id]
|
|
|
|
|
cleaned_count += 1
|
|
|
|
|
except Exception as e:
|
|
|
|
|
debug_log(f"容量清理會話 {session_id} 失敗: {e}")
|
2025-06-11 03:25:08 +08:00
|
|
|
|
|
2025-06-07 02:54:46 +08:00
|
|
|
|
return cleaned_count
|
|
|
|
|
|
|
|
|
|
def _cleanup_expired_sessions(self) -> int:
|
|
|
|
|
"""清理過期會話"""
|
|
|
|
|
expired_sessions = []
|
|
|
|
|
current_time = time.time()
|
|
|
|
|
|
|
|
|
|
for session_id, session in self.web_ui_manager.sessions.items():
|
|
|
|
|
# 檢查是否過期
|
2025-06-11 03:25:08 +08:00
|
|
|
|
if session.is_expired() or session.get_age() > self.policy.max_session_age:
|
2025-06-07 02:54:46 +08:00
|
|
|
|
expired_sessions.append(session_id)
|
|
|
|
|
|
|
|
|
|
# 清理過期會話
|
|
|
|
|
cleaned_count = 0
|
|
|
|
|
for session_id in expired_sessions:
|
|
|
|
|
try:
|
|
|
|
|
session = self.web_ui_manager.sessions.get(session_id)
|
|
|
|
|
if session:
|
|
|
|
|
session._cleanup_sync_enhanced(CleanupReason.EXPIRED)
|
|
|
|
|
del self.web_ui_manager.sessions[session_id]
|
|
|
|
|
cleaned_count += 1
|
|
|
|
|
|
|
|
|
|
# 如果清理的是當前活躍會話,清空當前會話
|
2025-06-11 03:25:08 +08:00
|
|
|
|
if (
|
|
|
|
|
self.web_ui_manager.current_session
|
|
|
|
|
and self.web_ui_manager.current_session.session_id == session_id
|
|
|
|
|
):
|
2025-06-07 02:54:46 +08:00
|
|
|
|
self.web_ui_manager.current_session = None
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
debug_log(f"清理過期會話 {session_id} 失敗: {e}")
|
|
|
|
|
|
|
|
|
|
return cleaned_count
|
|
|
|
|
|
|
|
|
|
def _cleanup_idle_sessions(self) -> int:
|
|
|
|
|
"""清理空閒會話"""
|
|
|
|
|
idle_sessions = []
|
|
|
|
|
|
|
|
|
|
for session_id, session in self.web_ui_manager.sessions.items():
|
|
|
|
|
# 跳過當前活躍會話(如果啟用保護)
|
2025-06-11 03:25:08 +08:00
|
|
|
|
if (
|
|
|
|
|
self.policy.preserve_active_session
|
|
|
|
|
and self.web_ui_manager.current_session
|
|
|
|
|
and session.session_id == self.web_ui_manager.current_session.session_id
|
|
|
|
|
):
|
2025-06-07 02:54:46 +08:00
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 檢查是否空閒時間過長
|
|
|
|
|
if session.get_idle_time() > self.policy.max_idle_time:
|
|
|
|
|
idle_sessions.append(session_id)
|
|
|
|
|
|
|
|
|
|
# 清理空閒會話
|
|
|
|
|
cleaned_count = 0
|
|
|
|
|
for session_id in idle_sessions:
|
|
|
|
|
try:
|
|
|
|
|
session = self.web_ui_manager.sessions.get(session_id)
|
|
|
|
|
if session:
|
|
|
|
|
session._cleanup_sync_enhanced(CleanupReason.EXPIRED)
|
|
|
|
|
del self.web_ui_manager.sessions[session_id]
|
|
|
|
|
cleaned_count += 1
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
debug_log(f"清理空閒會話 {session_id} 失敗: {e}")
|
|
|
|
|
|
|
|
|
|
return cleaned_count
|
|
|
|
|
|
2025-06-11 03:25:08 +08:00
|
|
|
|
def _update_cleanup_stats(
|
|
|
|
|
self, trigger: CleanupTrigger, cleaned_count: int, duration: float
|
|
|
|
|
):
|
2025-06-07 02:54:46 +08:00
|
|
|
|
"""更新清理統計"""
|
|
|
|
|
self.stats.total_cleanups += 1
|
|
|
|
|
self.stats.total_sessions_cleaned += cleaned_count
|
|
|
|
|
self.stats.total_cleanup_time += duration
|
|
|
|
|
self.stats.last_cleanup_time = datetime.now()
|
|
|
|
|
|
|
|
|
|
# 更新平均清理時間
|
|
|
|
|
if self.stats.total_cleanups > 0:
|
2025-06-11 03:25:08 +08:00
|
|
|
|
self.stats.average_cleanup_time = (
|
|
|
|
|
self.stats.total_cleanup_time / self.stats.total_cleanups
|
|
|
|
|
)
|
2025-06-07 02:54:46 +08:00
|
|
|
|
|
|
|
|
|
# 更新清理效率
|
|
|
|
|
total_sessions = len(self.web_ui_manager.sessions) + cleaned_count
|
|
|
|
|
if total_sessions > 0:
|
|
|
|
|
self.stats.cleanup_efficiency = cleaned_count / total_sessions
|
|
|
|
|
|
|
|
|
|
# 根據觸發器類型更新統計
|
|
|
|
|
if trigger == CleanupTrigger.AUTO:
|
|
|
|
|
self.stats.auto_cleanups += 1
|
|
|
|
|
elif trigger == CleanupTrigger.MEMORY_PRESSURE:
|
|
|
|
|
self.stats.memory_pressure_cleanups += 1
|
|
|
|
|
elif trigger == CleanupTrigger.EXPIRED:
|
|
|
|
|
self.stats.expired_cleanups += 1
|
|
|
|
|
elif trigger == CleanupTrigger.MANUAL:
|
|
|
|
|
self.stats.manual_cleanups += 1
|
|
|
|
|
|
|
|
|
|
# 記錄清理歷史
|
|
|
|
|
cleanup_record = {
|
|
|
|
|
"timestamp": datetime.now().isoformat(),
|
|
|
|
|
"trigger": trigger.value,
|
|
|
|
|
"cleaned_count": cleaned_count,
|
|
|
|
|
"duration": duration,
|
|
|
|
|
"total_sessions_before": total_sessions,
|
2025-06-11 03:25:08 +08:00
|
|
|
|
"total_sessions_after": len(self.web_ui_manager.sessions),
|
2025-06-07 02:54:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.cleanup_history.append(cleanup_record)
|
|
|
|
|
|
|
|
|
|
# 限制歷史記錄數量
|
|
|
|
|
if len(self.cleanup_history) > self.max_history:
|
2025-06-11 03:25:08 +08:00
|
|
|
|
self.cleanup_history = self.cleanup_history[-self.max_history :]
|
2025-06-07 02:54:46 +08:00
|
|
|
|
|
|
|
|
|
# 調用統計回調
|
|
|
|
|
for callback in self.stats_callbacks:
|
|
|
|
|
try:
|
|
|
|
|
callback(self.stats, cleanup_record)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
debug_log(f"統計回調執行失敗: {e}")
|
|
|
|
|
|
2025-06-11 03:25:08 +08:00
|
|
|
|
def get_cleanup_statistics(self) -> dict[str, Any]:
|
2025-06-07 02:54:46 +08:00
|
|
|
|
"""獲取清理統計數據"""
|
|
|
|
|
stats_dict = {
|
|
|
|
|
"total_cleanups": self.stats.total_cleanups,
|
|
|
|
|
"expired_cleanups": self.stats.expired_cleanups,
|
|
|
|
|
"memory_pressure_cleanups": self.stats.memory_pressure_cleanups,
|
|
|
|
|
"manual_cleanups": self.stats.manual_cleanups,
|
|
|
|
|
"auto_cleanups": self.stats.auto_cleanups,
|
|
|
|
|
"total_sessions_cleaned": self.stats.total_sessions_cleaned,
|
|
|
|
|
"total_cleanup_time": round(self.stats.total_cleanup_time, 2),
|
|
|
|
|
"average_cleanup_time": round(self.stats.average_cleanup_time, 2),
|
|
|
|
|
"cleanup_efficiency": round(self.stats.cleanup_efficiency, 3),
|
2025-06-11 03:25:08 +08:00
|
|
|
|
"last_cleanup_time": self.stats.last_cleanup_time.isoformat()
|
|
|
|
|
if self.stats.last_cleanup_time
|
|
|
|
|
else None,
|
2025-06-07 02:54:46 +08:00
|
|
|
|
"is_auto_cleanup_running": self.is_running,
|
|
|
|
|
"current_sessions": len(self.web_ui_manager.sessions),
|
|
|
|
|
"policy": {
|
|
|
|
|
"max_idle_time": self.policy.max_idle_time,
|
|
|
|
|
"max_session_age": self.policy.max_session_age,
|
|
|
|
|
"max_sessions": self.policy.max_sessions,
|
|
|
|
|
"cleanup_interval": self.policy.cleanup_interval,
|
|
|
|
|
"enable_auto_cleanup": self.policy.enable_auto_cleanup,
|
2025-06-11 03:25:08 +08:00
|
|
|
|
"preserve_active_session": self.policy.preserve_active_session,
|
|
|
|
|
},
|
2025-06-07 02:54:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stats_dict
|
|
|
|
|
|
2025-06-11 03:25:08 +08:00
|
|
|
|
def get_cleanup_history(self, limit: int = 20) -> list[dict[str, Any]]:
|
2025-06-07 02:54:46 +08:00
|
|
|
|
"""獲取清理歷史記錄"""
|
|
|
|
|
return self.cleanup_history[-limit:] if self.cleanup_history else []
|
|
|
|
|
|
|
|
|
|
def add_cleanup_callback(self, callback: Callable):
|
|
|
|
|
"""添加清理回調函數"""
|
|
|
|
|
if callback not in self.cleanup_callbacks:
|
|
|
|
|
self.cleanup_callbacks.append(callback)
|
|
|
|
|
debug_log("添加清理回調函數")
|
|
|
|
|
|
|
|
|
|
def add_stats_callback(self, callback: Callable):
|
|
|
|
|
"""添加統計回調函數"""
|
|
|
|
|
if callback not in self.stats_callbacks:
|
|
|
|
|
self.stats_callbacks.append(callback)
|
|
|
|
|
debug_log("添加統計回調函數")
|
|
|
|
|
|
|
|
|
|
def update_policy(self, **kwargs):
|
|
|
|
|
"""更新清理策略"""
|
|
|
|
|
for key, value in kwargs.items():
|
|
|
|
|
if hasattr(self.policy, key):
|
|
|
|
|
setattr(self.policy, key, value)
|
|
|
|
|
debug_log(f"更新清理策略 {key} = {value}")
|
|
|
|
|
else:
|
|
|
|
|
debug_log(f"未知的策略參數: {key}")
|
|
|
|
|
|
|
|
|
|
def reset_stats(self):
|
|
|
|
|
"""重置統計數據"""
|
|
|
|
|
self.stats = CleanupStats()
|
|
|
|
|
self.cleanup_history.clear()
|
|
|
|
|
debug_log("清理統計數據已重置")
|
|
|
|
|
|
|
|
|
|
def force_cleanup_all(self, exclude_current: bool = True) -> int:
|
|
|
|
|
"""強制清理所有會話"""
|
|
|
|
|
sessions_to_clean = []
|
|
|
|
|
|
|
|
|
|
for session_id, session in self.web_ui_manager.sessions.items():
|
|
|
|
|
# 是否排除當前活躍會話
|
2025-06-11 03:25:08 +08:00
|
|
|
|
if (
|
|
|
|
|
exclude_current
|
|
|
|
|
and self.web_ui_manager.current_session
|
|
|
|
|
and session.session_id == self.web_ui_manager.current_session.session_id
|
|
|
|
|
):
|
2025-06-07 02:54:46 +08:00
|
|
|
|
continue
|
|
|
|
|
sessions_to_clean.append(session_id)
|
|
|
|
|
|
|
|
|
|
# 清理會話
|
|
|
|
|
cleaned_count = 0
|
|
|
|
|
for session_id in sessions_to_clean:
|
|
|
|
|
try:
|
|
|
|
|
session = self.web_ui_manager.sessions.get(session_id)
|
|
|
|
|
if session:
|
|
|
|
|
session._cleanup_sync_enhanced(CleanupReason.MANUAL)
|
|
|
|
|
del self.web_ui_manager.sessions[session_id]
|
|
|
|
|
cleaned_count += 1
|
|
|
|
|
except Exception as e:
|
|
|
|
|
debug_log(f"強制清理會話 {session_id} 失敗: {e}")
|
|
|
|
|
|
|
|
|
|
# 更新統計
|
|
|
|
|
self._update_cleanup_stats(CleanupTrigger.MANUAL, cleaned_count, 0.0)
|
|
|
|
|
|
|
|
|
|
debug_log(f"強制清理完成,清理了 {cleaned_count} 個會話")
|
|
|
|
|
return cleaned_count
|