refactor: Enhance database connection handling by prioritizing API server's pool and adding error handling
This commit is contained in:
parent
172f5907b3
commit
77973d5573
@ -1704,12 +1704,13 @@ async def dashboard_get_user_guilds(current_user: dict = Depends(dependencies.ge
|
||||
# Use the API server's own pool from app.state instead of the bot's pool
|
||||
while retry_count < max_db_retries and bot_guild_ids is None:
|
||||
try:
|
||||
# Check if we have a pool in app.state
|
||||
# Always use the API server's own pool with the new function
|
||||
if hasattr(app.state, 'pg_pool') and app.state.pg_pool:
|
||||
# Use the API server's own pool with the new function
|
||||
log.info("Dashboard: Using API server's pool to fetch guild IDs")
|
||||
bot_guild_ids = await settings_manager.get_bot_guild_ids_with_pool(app.state.pg_pool)
|
||||
else:
|
||||
# Fall back to the original function if app.state.pg_pool is not available
|
||||
# The improved get_bot_guild_ids will try app.state.pg_pool first
|
||||
log.info("Dashboard: Using enhanced get_bot_guild_ids that prioritizes API server's pool")
|
||||
bot_guild_ids = await settings_manager.get_bot_guild_ids()
|
||||
|
||||
if bot_guild_ids is None:
|
||||
@ -1717,6 +1718,31 @@ async def dashboard_get_user_guilds(current_user: dict = Depends(dependencies.ge
|
||||
retry_count += 1
|
||||
if retry_count < max_db_retries:
|
||||
await asyncio.sleep(1) # Wait before retrying
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Dashboard: Event loop error fetching guild IDs: {e}")
|
||||
log.warning("This is likely because we're trying to use a pool from a different thread.")
|
||||
# Try to create a new pool just for this request if needed
|
||||
if not hasattr(app.state, 'pg_pool') or not app.state.pg_pool:
|
||||
try:
|
||||
log.info("Dashboard: Attempting to create a temporary pool for this request")
|
||||
temp_pool = await asyncpg.create_pool(
|
||||
user=settings.POSTGRES_USER,
|
||||
password=settings.POSTGRES_PASSWORD,
|
||||
host=settings.POSTGRES_HOST,
|
||||
database=settings.POSTGRES_SETTINGS_DB,
|
||||
min_size=1,
|
||||
max_size=2,
|
||||
)
|
||||
bot_guild_ids = await settings_manager.get_bot_guild_ids_with_pool(temp_pool)
|
||||
await temp_pool.close()
|
||||
except Exception as pool_err:
|
||||
log.error(f"Dashboard: Failed to create temporary pool: {pool_err}")
|
||||
else:
|
||||
log.warning(f"Dashboard: Runtime error fetching bot guild IDs, retry {retry_count+1}/{max_db_retries}: {e}")
|
||||
retry_count += 1
|
||||
if retry_count < max_db_retries:
|
||||
await asyncio.sleep(1) # Wait before retrying
|
||||
except Exception as e:
|
||||
log.warning(f"Dashboard: Error fetching bot guild IDs, retry {retry_count+1}/{max_db_retries}: {e}")
|
||||
retry_count += 1
|
||||
@ -2155,35 +2181,116 @@ async def dashboard_get_guild_settings(
|
||||
|
||||
known_cogs_in_db = {}
|
||||
try:
|
||||
# Need to acquire connection from pool
|
||||
bot = get_bot_instance()
|
||||
if bot and bot.pg_pool:
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
# First try to use the API server's pool
|
||||
if hasattr(app.state, 'pg_pool') and app.state.pg_pool:
|
||||
log.info(f"Dashboard: Using API server's pool to fetch cog statuses for guild {guild_id}")
|
||||
async with app.state.pg_pool.acquire() as conn:
|
||||
records = await conn.fetch("SELECT cog_name, enabled FROM enabled_cogs WHERE guild_id = $1", guild_id)
|
||||
for record in records:
|
||||
known_cogs_in_db[record['cog_name']] = record['enabled']
|
||||
else:
|
||||
log.error("Dashboard: Bot instance or pg_pool not initialized.")
|
||||
# Decide how to handle - return empty or error?
|
||||
# Fall back to bot's pool if API server pool not available
|
||||
bot = get_bot_instance()
|
||||
if bot and bot.pg_pool:
|
||||
log.info(f"Dashboard: Using bot's pool to fetch cog statuses for guild {guild_id}")
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
records = await conn.fetch("SELECT cog_name, enabled FROM enabled_cogs WHERE guild_id = $1", guild_id)
|
||||
for record in records:
|
||||
known_cogs_in_db[record['cog_name']] = record['enabled']
|
||||
else:
|
||||
log.error("Dashboard: Neither API server pool nor bot pool is available")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Dashboard: Event loop error fetching cog statuses: {e}")
|
||||
log.warning("This is likely because we're trying to use a pool from a different thread.")
|
||||
# Try to create a temporary pool just for this request
|
||||
try:
|
||||
log.info("Dashboard: Attempting to create a temporary pool for cog statuses")
|
||||
temp_pool = await asyncpg.create_pool(
|
||||
user=settings.POSTGRES_USER,
|
||||
password=settings.POSTGRES_PASSWORD,
|
||||
host=settings.POSTGRES_HOST,
|
||||
database=settings.POSTGRES_SETTINGS_DB,
|
||||
min_size=1,
|
||||
max_size=2,
|
||||
)
|
||||
async with temp_pool.acquire() as conn:
|
||||
records = await conn.fetch("SELECT cog_name, enabled FROM enabled_cogs WHERE guild_id = $1", guild_id)
|
||||
for record in records:
|
||||
known_cogs_in_db[record['cog_name']] = record['enabled']
|
||||
await temp_pool.close()
|
||||
except Exception as pool_err:
|
||||
log.error(f"Dashboard: Failed to create temporary pool for cog statuses: {pool_err}")
|
||||
else:
|
||||
log.exception(f"Dashboard: Runtime error fetching cog statuses from DB for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Dashboard: Failed to fetch cog statuses from DB for guild {guild_id}: {e}")
|
||||
|
||||
# Fetch command permissions
|
||||
permissions_map: Dict[str, List[str]] = {}
|
||||
try:
|
||||
bot = get_bot_instance()
|
||||
if bot and bot.pg_pool:
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
# First try to use the API server's pool
|
||||
if hasattr(app.state, 'pg_pool') and app.state.pg_pool:
|
||||
log.info(f"Dashboard: Using API server's pool to fetch command permissions for guild {guild_id}")
|
||||
async with app.state.pg_pool.acquire() as conn:
|
||||
records = await conn.fetch(
|
||||
"SELECT command_name, allowed_role_id FROM command_permissions WHERE guild_id = $1 ORDER BY command_name, allowed_role_id",
|
||||
guild_id
|
||||
)
|
||||
for record in records:
|
||||
cmd = record['command_name']
|
||||
role_id_str = str(record['allowed_role_id'])
|
||||
if cmd not in permissions_map:
|
||||
permissions_map[cmd] = []
|
||||
permissions_map[cmd].append(role_id_str)
|
||||
for record in records:
|
||||
cmd = record['command_name']
|
||||
role_id_str = str(record['allowed_role_id'])
|
||||
if cmd not in permissions_map:
|
||||
permissions_map[cmd] = []
|
||||
permissions_map[cmd].append(role_id_str)
|
||||
else:
|
||||
# Fall back to bot's pool if API server pool not available
|
||||
bot = get_bot_instance()
|
||||
if bot and bot.pg_pool:
|
||||
log.info(f"Dashboard: Using bot's pool to fetch command permissions for guild {guild_id}")
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
records = await conn.fetch(
|
||||
"SELECT command_name, allowed_role_id FROM command_permissions WHERE guild_id = $1 ORDER BY command_name, allowed_role_id",
|
||||
guild_id
|
||||
)
|
||||
for record in records:
|
||||
cmd = record['command_name']
|
||||
role_id_str = str(record['allowed_role_id'])
|
||||
if cmd not in permissions_map:
|
||||
permissions_map[cmd] = []
|
||||
permissions_map[cmd].append(role_id_str)
|
||||
else:
|
||||
log.error("Dashboard: Neither API server pool nor bot pool is available")
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Dashboard: Event loop error fetching command permissions: {e}")
|
||||
# Try to create a temporary pool just for this request
|
||||
try:
|
||||
log.info("Dashboard: Attempting to create a temporary pool for command permissions")
|
||||
temp_pool = await asyncpg.create_pool(
|
||||
user=settings.POSTGRES_USER,
|
||||
password=settings.POSTGRES_PASSWORD,
|
||||
host=settings.POSTGRES_HOST,
|
||||
database=settings.POSTGRES_SETTINGS_DB,
|
||||
min_size=1,
|
||||
max_size=2,
|
||||
)
|
||||
async with temp_pool.acquire() as conn:
|
||||
records = await conn.fetch(
|
||||
"SELECT command_name, allowed_role_id FROM command_permissions WHERE guild_id = $1 ORDER BY command_name, allowed_role_id",
|
||||
guild_id
|
||||
)
|
||||
for record in records:
|
||||
cmd = record['command_name']
|
||||
role_id_str = str(record['allowed_role_id'])
|
||||
if cmd not in permissions_map:
|
||||
permissions_map[cmd] = []
|
||||
permissions_map[cmd].append(role_id_str)
|
||||
await temp_pool.close()
|
||||
except Exception as pool_err:
|
||||
log.error(f"Dashboard: Failed to create temporary pool for command permissions: {pool_err}")
|
||||
else:
|
||||
log.exception(f"Dashboard: Runtime error fetching command permissions from DB for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.exception(f"Dashboard: Failed to fetch command permissions from DB for guild {guild_id}: {e}")
|
||||
|
||||
|
@ -621,14 +621,31 @@ async def get_guild_settings(
|
||||
"""Get settings for a guild."""
|
||||
try:
|
||||
# Check if settings_manager is available
|
||||
from global_bot_accessor import get_bot_instance
|
||||
bot = get_bot_instance()
|
||||
if not settings_manager or not bot or not bot.pg_pool:
|
||||
if not settings_manager:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings manager or database connection not available"
|
||||
detail="Settings manager not available"
|
||||
)
|
||||
|
||||
# Try to get the API server's pool from FastAPI app
|
||||
try:
|
||||
from api_service.api_server import app
|
||||
has_api_pool = hasattr(app, 'state') and hasattr(app.state, 'pg_pool') and app.state.pg_pool
|
||||
log.info(f"API server pool available: {has_api_pool}")
|
||||
except (ImportError, AttributeError):
|
||||
has_api_pool = False
|
||||
log.warning("Could not access API server pool")
|
||||
|
||||
# Check bot pool as fallback
|
||||
from global_bot_accessor import get_bot_instance
|
||||
bot = get_bot_instance()
|
||||
has_bot_pool = bot and hasattr(bot, 'pg_pool') and bot.pg_pool
|
||||
log.info(f"Bot pool available: {has_bot_pool}")
|
||||
|
||||
if not has_api_pool and not has_bot_pool:
|
||||
log.warning("Neither API server pool nor bot pool is available")
|
||||
# Continue anyway - the settings_manager functions will handle the missing pools
|
||||
|
||||
# Initialize settings with defaults
|
||||
settings = {
|
||||
"prefix": DEFAULT_PREFIX,
|
||||
@ -643,6 +660,13 @@ async def get_guild_settings(
|
||||
# Get prefix with error handling
|
||||
try:
|
||||
settings["prefix"] = await settings_manager.get_guild_prefix(guild_id, DEFAULT_PREFIX)
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting prefix for guild {guild_id}: {e}")
|
||||
# Keep default prefix
|
||||
else:
|
||||
log.warning(f"Runtime error getting prefix for guild {guild_id}: {e}")
|
||||
# Keep default prefix
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting prefix for guild {guild_id}, using default: {e}")
|
||||
# Keep default prefix
|
||||
@ -650,27 +674,54 @@ async def get_guild_settings(
|
||||
# Get welcome/goodbye settings with error handling
|
||||
try:
|
||||
settings["welcome_channel_id"] = await settings_manager.get_setting(guild_id, 'welcome_channel_id')
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting welcome_channel_id for guild {guild_id}: {e}")
|
||||
else:
|
||||
log.warning(f"Runtime error getting welcome_channel_id for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting welcome_channel_id for guild {guild_id}: {e}")
|
||||
|
||||
try:
|
||||
settings["welcome_message"] = await settings_manager.get_setting(guild_id, 'welcome_message')
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting welcome_message for guild {guild_id}: {e}")
|
||||
else:
|
||||
log.warning(f"Runtime error getting welcome_message for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting welcome_message for guild {guild_id}: {e}")
|
||||
|
||||
try:
|
||||
settings["goodbye_channel_id"] = await settings_manager.get_setting(guild_id, 'goodbye_channel_id')
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting goodbye_channel_id for guild {guild_id}: {e}")
|
||||
else:
|
||||
log.warning(f"Runtime error getting goodbye_channel_id for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting goodbye_channel_id for guild {guild_id}: {e}")
|
||||
|
||||
try:
|
||||
settings["goodbye_message"] = await settings_manager.get_setting(guild_id, 'goodbye_message')
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting goodbye_message for guild {guild_id}: {e}")
|
||||
else:
|
||||
log.warning(f"Runtime error getting goodbye_message for guild {guild_id}: {e}")
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting goodbye_message for guild {guild_id}: {e}")
|
||||
|
||||
# Get cog enabled statuses with error handling
|
||||
try:
|
||||
settings["cogs"] = await settings_manager.get_all_enabled_cogs(guild_id)
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting cog enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for cogs
|
||||
else:
|
||||
log.warning(f"Runtime error getting cog enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for cogs
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting cog enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for cogs
|
||||
@ -678,6 +729,13 @@ async def get_guild_settings(
|
||||
# Get command enabled statuses with error handling
|
||||
try:
|
||||
settings["commands"] = await settings_manager.get_all_enabled_commands(guild_id)
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.warning(f"Event loop error getting command enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for commands
|
||||
else:
|
||||
log.warning(f"Runtime error getting command enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for commands
|
||||
except Exception as e:
|
||||
log.warning(f"Error getting command enabled statuses for guild {guild_id}: {e}")
|
||||
# Keep empty dict for commands
|
||||
|
@ -1680,7 +1680,23 @@ async def get_bot_guild_ids() -> set[int] | None:
|
||||
"""
|
||||
Gets the set of all guild IDs known to the bot from the guilds table.
|
||||
Returns None on error or if pool not initialized.
|
||||
|
||||
This function will first try to use the API server's pool if available,
|
||||
and fall back to the bot's pool if not.
|
||||
"""
|
||||
# First, try to get the API server's pool from FastAPI app.state
|
||||
try:
|
||||
# Import here to avoid circular imports
|
||||
from api_service.api_server import app
|
||||
if hasattr(app, 'state') and hasattr(app.state, 'pg_pool') and app.state.pg_pool:
|
||||
log.debug("Using API server's PostgreSQL pool for get_bot_guild_ids")
|
||||
return await get_bot_guild_ids_with_pool(app.state.pg_pool)
|
||||
except (ImportError, AttributeError) as e:
|
||||
log.debug(f"API server pool not available, will try bot pool: {e}")
|
||||
except Exception as e:
|
||||
log.warning(f"Error accessing API server pool: {e}")
|
||||
|
||||
# Fall back to the bot's pool
|
||||
bot = get_bot_instance()
|
||||
if not bot or not bot.pg_pool:
|
||||
log.error("Bot instance or PostgreSQL pool not available in settings_manager. Cannot get bot guild IDs.")
|
||||
@ -1691,11 +1707,20 @@ async def get_bot_guild_ids() -> set[int] | None:
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
records = await conn.fetch("SELECT guild_id FROM guilds")
|
||||
guild_ids = {record['guild_id'] for record in records}
|
||||
log.debug(f"Fetched {len(guild_ids)} guild IDs from database using pool.")
|
||||
log.debug(f"Fetched {len(guild_ids)} guild IDs from database using bot pool.")
|
||||
return guild_ids
|
||||
except asyncpg.exceptions.PostgresError as e:
|
||||
log.exception(f"PostgreSQL error fetching bot guild IDs using pool: {e}")
|
||||
log.exception(f"PostgreSQL error fetching bot guild IDs using bot pool: {e}")
|
||||
return None
|
||||
except RuntimeError as e:
|
||||
if "got Future" in str(e) and "attached to a different loop" in str(e):
|
||||
log.error(f"Event loop error in get_bot_guild_ids: {e}")
|
||||
log.warning("This is likely because the function is being called from the API server thread.")
|
||||
log.warning("Try using get_bot_guild_ids_with_pool with app.state.pg_pool instead.")
|
||||
return None
|
||||
else:
|
||||
log.exception(f"Runtime error fetching bot guild IDs: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
log.exception(f"Unexpected error fetching bot guild IDs: {e}")
|
||||
return None
|
||||
|
Loading…
x
Reference in New Issue
Block a user