mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 10:51:05 -05:00
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:
@@ -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."""
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user