mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 02:41:06 -05:00
Component-based architecture with lifecycle management Enhanced error handling and recovery mechanisms Comprehensive state management and tracking Event-driven architecture with monitoring Queue Management: Multiple processing strategies for different scenarios Advanced state management with recovery Comprehensive metrics and health monitoring Sophisticated cleanup system with multiple strategies Processing Pipeline: Enhanced message handling with validation Improved URL extraction and processing Better queue management and monitoring Advanced cleanup mechanisms Overall Benefits: Better code organization and maintainability Improved error handling and recovery Enhanced monitoring and reporting More robust and reliable system
240 lines
7.8 KiB
Python
240 lines
7.8 KiB
Python
"""Module for managing VideoArchiver lifecycle"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Optional, Dict, Any, Set
|
|
from enum import Enum
|
|
from datetime import datetime
|
|
|
|
from .cleanup import cleanup_resources, force_cleanup_resources
|
|
from ..utils.exceptions import VideoArchiverError
|
|
from .initialization import initialize_cog, init_callback
|
|
|
|
logger = logging.getLogger("VideoArchiver")
|
|
|
|
class LifecycleState(Enum):
|
|
"""Possible states in the cog lifecycle"""
|
|
UNINITIALIZED = "uninitialized"
|
|
INITIALIZING = "initializing"
|
|
READY = "ready"
|
|
UNLOADING = "unloading"
|
|
ERROR = "error"
|
|
|
|
class TaskManager:
|
|
"""Manages asyncio tasks"""
|
|
|
|
def __init__(self):
|
|
self._tasks: Dict[str, asyncio.Task] = {}
|
|
self._task_history: Dict[str, Dict[str, Any]] = {}
|
|
|
|
async def create_task(
|
|
self,
|
|
name: str,
|
|
coro,
|
|
callback=None
|
|
) -> asyncio.Task:
|
|
"""Create and track a task"""
|
|
task = asyncio.create_task(coro)
|
|
self._tasks[name] = task
|
|
self._task_history[name] = {
|
|
"start_time": datetime.utcnow(),
|
|
"status": "running"
|
|
}
|
|
|
|
if callback:
|
|
task.add_done_callback(lambda t: self._handle_completion(name, t, callback))
|
|
else:
|
|
task.add_done_callback(lambda t: self._handle_completion(name, t))
|
|
|
|
return task
|
|
|
|
def _handle_completion(
|
|
self,
|
|
name: str,
|
|
task: asyncio.Task,
|
|
callback=None
|
|
) -> None:
|
|
"""Handle task completion"""
|
|
try:
|
|
task.result() # Raises exception if task failed
|
|
status = "completed"
|
|
except asyncio.CancelledError:
|
|
status = "cancelled"
|
|
except Exception as e:
|
|
status = "failed"
|
|
logger.error(f"Task {name} failed: {e}")
|
|
|
|
self._task_history[name].update({
|
|
"end_time": datetime.utcnow(),
|
|
"status": status
|
|
})
|
|
|
|
if callback:
|
|
try:
|
|
callback(task)
|
|
except Exception as e:
|
|
logger.error(f"Task callback error for {name}: {e}")
|
|
|
|
self._tasks.pop(name, None)
|
|
|
|
async def cancel_task(self, name: str) -> None:
|
|
"""Cancel a specific task"""
|
|
if task := self._tasks.get(name):
|
|
if not task.done():
|
|
task.cancel()
|
|
try:
|
|
await task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
except Exception as e:
|
|
logger.error(f"Error cancelling task {name}: {e}")
|
|
|
|
async def cancel_all_tasks(self) -> None:
|
|
"""Cancel all tracked tasks"""
|
|
for name in list(self._tasks.keys()):
|
|
await self.cancel_task(name)
|
|
|
|
def get_task_status(self) -> Dict[str, Any]:
|
|
"""Get status of all tasks"""
|
|
return {
|
|
"active_tasks": list(self._tasks.keys()),
|
|
"history": self._task_history.copy()
|
|
}
|
|
|
|
class StateTracker:
|
|
"""Tracks lifecycle state and transitions"""
|
|
|
|
def __init__(self):
|
|
self.state = LifecycleState.UNINITIALIZED
|
|
self.state_history: List[Dict[str, Any]] = []
|
|
self._record_state()
|
|
|
|
def set_state(self, state: LifecycleState) -> None:
|
|
"""Set current state"""
|
|
self.state = state
|
|
self._record_state()
|
|
|
|
def _record_state(self) -> None:
|
|
"""Record state transition"""
|
|
self.state_history.append({
|
|
"state": self.state.value,
|
|
"timestamp": datetime.utcnow()
|
|
})
|
|
|
|
def get_state_history(self) -> List[Dict[str, Any]]:
|
|
"""Get state transition history"""
|
|
return self.state_history.copy()
|
|
|
|
class LifecycleManager:
|
|
"""Manages the lifecycle of the VideoArchiver cog"""
|
|
|
|
def __init__(self, cog):
|
|
self.cog = cog
|
|
self.task_manager = TaskManager()
|
|
self.state_tracker = StateTracker()
|
|
self._cleanup_handlers: Set[callable] = set()
|
|
|
|
def register_cleanup_handler(self, handler: callable) -> None:
|
|
"""Register a cleanup handler"""
|
|
self._cleanup_handlers.add(handler)
|
|
|
|
async def handle_load(self) -> None:
|
|
"""Handle cog loading without blocking"""
|
|
try:
|
|
self.state_tracker.set_state(LifecycleState.INITIALIZING)
|
|
|
|
# Start initialization as background task
|
|
await self.task_manager.create_task(
|
|
"initialization",
|
|
initialize_cog(self.cog),
|
|
lambda t: init_callback(self.cog, t)
|
|
)
|
|
logger.info("Initialization started in background")
|
|
|
|
except Exception as e:
|
|
self.state_tracker.set_state(LifecycleState.ERROR)
|
|
# Ensure cleanup on any error
|
|
try:
|
|
await asyncio.wait_for(
|
|
force_cleanup_resources(self.cog),
|
|
timeout=15 # CLEANUP_TIMEOUT
|
|
)
|
|
except asyncio.TimeoutError:
|
|
logger.error("Force cleanup during load error timed out")
|
|
raise VideoArchiverError(f"Error during cog load: {str(e)}")
|
|
|
|
async def handle_unload(self) -> None:
|
|
"""Clean up when cog is unloaded"""
|
|
self.state_tracker.set_state(LifecycleState.UNLOADING)
|
|
|
|
try:
|
|
# Cancel all tasks
|
|
await self.task_manager.cancel_all_tasks()
|
|
|
|
# Run cleanup handlers
|
|
await self._run_cleanup_handlers()
|
|
|
|
# Try normal cleanup
|
|
try:
|
|
cleanup_task = await self.task_manager.create_task(
|
|
"cleanup",
|
|
cleanup_resources(self.cog)
|
|
)
|
|
await asyncio.wait_for(cleanup_task, timeout=30) # UNLOAD_TIMEOUT
|
|
logger.info("Normal cleanup completed")
|
|
|
|
except (asyncio.TimeoutError, Exception) as e:
|
|
if isinstance(e, asyncio.TimeoutError):
|
|
logger.warning("Normal cleanup timed out, forcing cleanup")
|
|
else:
|
|
logger.error(f"Error during normal cleanup: {str(e)}")
|
|
|
|
# Force cleanup
|
|
try:
|
|
await asyncio.wait_for(
|
|
force_cleanup_resources(self.cog),
|
|
timeout=15 # CLEANUP_TIMEOUT
|
|
)
|
|
logger.info("Force cleanup completed")
|
|
except asyncio.TimeoutError:
|
|
logger.error("Force cleanup timed out")
|
|
except Exception as e:
|
|
logger.error(f"Error during force cleanup: {str(e)}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during cog unload: {str(e)}")
|
|
self.state_tracker.set_state(LifecycleState.ERROR)
|
|
finally:
|
|
# Clear all references
|
|
await self._cleanup_references()
|
|
|
|
async def _run_cleanup_handlers(self) -> None:
|
|
"""Run all registered cleanup handlers"""
|
|
for handler in self._cleanup_handlers:
|
|
try:
|
|
if asyncio.iscoroutinefunction(handler):
|
|
await handler()
|
|
else:
|
|
handler()
|
|
except Exception as e:
|
|
logger.error(f"Error in cleanup handler: {e}")
|
|
|
|
async def _cleanup_references(self) -> None:
|
|
"""Clean up all references"""
|
|
self.cog.ready.clear()
|
|
self.cog.bot = None
|
|
self.cog.processor = None
|
|
self.cog.queue_manager = None
|
|
self.cog.update_checker = None
|
|
self.cog.ffmpeg_mgr = None
|
|
self.cog.components.clear()
|
|
self.cog.db = None
|
|
|
|
def get_status(self) -> Dict[str, Any]:
|
|
"""Get current lifecycle status"""
|
|
return {
|
|
"state": self.state_tracker.state.value,
|
|
"state_history": self.state_tracker.get_state_history(),
|
|
"tasks": self.task_manager.get_task_status()
|
|
}
|