Added proper initialization control:

Setup lock in init.py to prevent concurrent cog loads
Check for existing cog instance to prevent duplicates
Setup_in_progress flag to track initialization state
Improved timeout handling and cleanup
Enhanced Queue Manager singleton pattern:
Class-level lock to prevent multiple instance creation
Consistent event naming with _init_event
Sequential component initialization
Proper lock ordering (global -> queue -> processing)
Improved state management:
Single source of truth for initialization state
Proper cleanup of resources on failure
Better error handling and logging
Timeout handling for all async operations
This commit is contained in:
pacnpal
2024-11-15 23:09:55 +00:00
parent 53e7769811
commit 39061cbf3e
2 changed files with 80 additions and 33 deletions

View File

@@ -7,29 +7,51 @@ from .exceptions import ProcessingError
logger = logging.getLogger("VideoArchiver") logger = logging.getLogger("VideoArchiver")
# Global lock to prevent multiple concurrent setup attempts
_setup_lock = asyncio.Lock()
_setup_in_progress = False
async def setup(bot: Red) -> None: async def setup(bot: Red) -> None:
"""Load VideoArchiver.""" """Load VideoArchiver."""
try: global _setup_in_progress
# Load main cog
cog = VideoArchiver(bot)
await bot.add_cog(cog)
# Wait for initialization to complete with timeout # Use lock to prevent multiple concurrent setup attempts
async with _setup_lock:
try: try:
await asyncio.wait_for(cog.ready.wait(), timeout=30) # Check if setup is already in progress
except asyncio.TimeoutError: if _setup_in_progress:
logger.error("VideoArchiver initialization timed out") logger.warning("VideoArchiver setup already in progress, skipping")
await bot.remove_cog(cog.__class__.__name__) return
raise ProcessingError("Initialization timed out")
if not cog.ready.is_set(): # Check if cog is already loaded
logger.error("VideoArchiver failed to initialize") if "VideoArchiver" in bot.cogs:
await bot.remove_cog(cog.__class__.__name__) logger.warning("VideoArchiver already loaded, skipping")
raise ProcessingError("Initialization failed") return
except Exception as e: _setup_in_progress = True
logger.error(f"Failed to load VideoArchiver: {str(e)}")
raise # Load main cog
cog = VideoArchiver(bot)
await bot.add_cog(cog)
# Wait for initialization to complete with timeout
try:
await asyncio.wait_for(cog.ready.wait(), timeout=30)
except asyncio.TimeoutError:
logger.error("VideoArchiver initialization timed out")
await bot.remove_cog(cog.__class__.__name__)
raise ProcessingError("Initialization timed out")
if not cog.ready.is_set():
logger.error("VideoArchiver failed to initialize")
await bot.remove_cog(cog.__class__.__name__)
raise ProcessingError("Initialization failed")
except Exception as e:
logger.error(f"Failed to load VideoArchiver: {str(e)}")
raise
finally:
_setup_in_progress = False
async def teardown(bot: Red) -> None: async def teardown(bot: Red) -> None:
"""Clean up when unloading.""" """Clean up when unloading."""

View File

@@ -20,6 +20,17 @@ logger = logging.getLogger("QueueManager")
class EnhancedVideoQueueManager: class EnhancedVideoQueueManager:
"""Enhanced queue manager with improved memory management and performance""" """Enhanced queue manager with improved memory management and performance"""
# Class-level initialization lock to prevent multiple instances
_instance_lock = asyncio.Lock()
_instance = None
_initialized = False
def __new__(cls, *args, **kwargs):
"""Ensure singleton instance"""
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__( def __init__(
self, self,
max_retries: int = 3, max_retries: int = 3,
@@ -32,6 +43,10 @@ class EnhancedVideoQueueManager:
deadlock_threshold: int = 300, # 5 minutes deadlock_threshold: int = 300, # 5 minutes
check_interval: int = 60, # 1 minute check_interval: int = 60, # 1 minute
): ):
"""Initialize only once"""
if self._initialized:
return
# Configuration # Configuration
self.max_retries = max_retries self.max_retries = max_retries
self.retry_delay = retry_delay self.retry_delay = retry_delay
@@ -48,14 +63,14 @@ class EnhancedVideoQueueManager:
self._channel_queues: Dict[int, Set[str]] = {} self._channel_queues: Dict[int, Set[str]] = {}
self._active_tasks: Set[asyncio.Task] = set() self._active_tasks: Set[asyncio.Task] = set()
# Locks - Establish consistent ordering # Locks
self._global_lock = asyncio.Lock() # Primary lock for coordinating all operations self._global_lock = asyncio.Lock()
self._queue_lock = asyncio.Lock() # Secondary lock for queue operations self._queue_lock = asyncio.Lock()
self._processing_lock = asyncio.Lock() # Tertiary lock for processing operations self._processing_lock = asyncio.Lock()
# State # State
self._shutdown = False self._shutdown = False
self._initialized = asyncio.Event() self._init_event = asyncio.Event() # Single event for initialization state
self.metrics = QueueMetrics() self.metrics = QueueMetrics()
# Components # Components
@@ -70,10 +85,19 @@ class EnhancedVideoQueueManager:
max_history_age=max_history_age max_history_age=max_history_age
) )
# Mark instance as initialized
self._initialized = True
async def initialize(self) -> None: async def initialize(self) -> None:
"""Initialize the queue manager components sequentially""" """Initialize the queue manager components sequentially"""
try: # Use class-level lock to prevent multiple initializations
async with self._global_lock: async with self._instance_lock:
# Check if already initialized
if self._init_event.is_set():
logger.info("Queue manager already initialized")
return
try:
logger.info("Starting queue manager initialization...") logger.info("Starting queue manager initialization...")
# Load persisted state first if available # Load persisted state first if available
@@ -112,13 +136,13 @@ class EnhancedVideoQueueManager:
logger.info("Queue cleanup started") logger.info("Queue cleanup started")
# Signal initialization complete # Signal initialization complete
self._initialized.set() self._init_event.set()
logger.info("Queue manager initialization completed") logger.info("Queue manager initialization completed")
except Exception as e: except Exception as e:
logger.error(f"Failed to initialize queue manager: {e}") logger.error(f"Failed to initialize queue manager: {e}")
self._shutdown = True self._shutdown = True
raise raise
async def _load_persisted_state(self) -> None: async def _load_persisted_state(self) -> None:
"""Load persisted queue state""" """Load persisted queue state"""
@@ -153,7 +177,7 @@ class EnhancedVideoQueueManager:
) -> None: ) -> None:
"""Process items in the queue""" """Process items in the queue"""
# Wait for initialization to complete # Wait for initialization to complete
await self._initialized.wait() await self._init_event.wait()
logger.info("Queue processor started") logger.info("Queue processor started")
last_persist_time = time.time() last_persist_time = time.time()
@@ -283,7 +307,8 @@ class EnhancedVideoQueueManager:
if self._shutdown: if self._shutdown:
raise QueueError("Queue manager is shutting down") raise QueueError("Queue manager is shutting down")
await self._initialized.wait() # Wait for initialization using the correct event
await self._init_event.wait()
try: try:
async with self._global_lock: async with self._global_lock: