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,9 +7,29 @@ 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."""
global _setup_in_progress
# Use lock to prevent multiple concurrent setup attempts
async with _setup_lock:
try: try:
# Check if setup is already in progress
if _setup_in_progress:
logger.warning("VideoArchiver setup already in progress, skipping")
return
# Check if cog is already loaded
if "VideoArchiver" in bot.cogs:
logger.warning("VideoArchiver already loaded, skipping")
return
_setup_in_progress = True
# Load main cog # Load main cog
cog = VideoArchiver(bot) cog = VideoArchiver(bot)
await bot.add_cog(cog) await bot.add_cog(cog)
@@ -30,6 +50,8 @@ async def setup(bot: Red) -> None:
except Exception as e: except Exception as e:
logger.error(f"Failed to load VideoArchiver: {str(e)}") logger.error(f"Failed to load VideoArchiver: {str(e)}")
raise 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"""
# Use class-level lock to prevent multiple initializations
async with self._instance_lock:
# Check if already initialized
if self._init_event.is_set():
logger.info("Queue manager already initialized")
return
try: try:
async with self._global_lock:
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,7 +136,7 @@ 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:
@@ -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: