This commit is contained in:
Slipstream 2025-05-03 18:51:44 -06:00
parent 2e1071fb40
commit e8bd48da11
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
4 changed files with 191 additions and 19 deletions

View File

@ -156,13 +156,38 @@ async def lifespan(_: FastAPI): # Underscore indicates unused but required para
# than the main bot, so it needs its own connection pools
if settings_manager:
log.info("Initializing database and cache connection pools for API server...")
try:
# Initialize the pools in the settings_manager module
await settings_manager.initialize_pools()
log.info("Database and cache connection pools initialized for API server.")
except Exception as e:
log.exception(f"Failed to initialize connection pools for API server: {e}")
log.error("Dashboard endpoints requiring DB/cache will fail.")
# Add retry logic for database initialization
max_retries = 3
retry_count = 0
success = False
while retry_count < max_retries and not success:
try:
# Initialize the pools in the settings_manager module
# Close any existing pools first to ensure clean state
if settings_manager.pg_pool or settings_manager.redis_pool:
log.info("Closing existing database pools before reinitializing...")
await settings_manager.close_pools()
# Initialize new pools
await settings_manager.initialize_pools()
log.info("Database and cache connection pools initialized for API server.")
success = True
except Exception as e:
retry_count += 1
log.warning(f"Failed to initialize connection pools (attempt {retry_count}/{max_retries}): {e}")
if retry_count < max_retries:
# Wait before retrying with exponential backoff
wait_time = 2 ** retry_count # 2, 4, 8 seconds
log.info(f"Waiting {wait_time} seconds before retrying...")
await asyncio.sleep(wait_time)
else:
log.exception(f"Failed to initialize connection pools after {max_retries} attempts")
log.error("Dashboard endpoints requiring DB/cache will fail.")
if not success:
log.error("Failed to initialize database pools after all retries.")
else:
log.error("settings_manager not imported. Dashboard endpoints requiring DB/cache will fail.")
@ -883,9 +908,28 @@ async def dashboard_get_user_guilds(current_user: dict = Depends(get_dashboard_u
# 2. Fetch guilds the bot is in from our DB
try:
bot_guild_ids = await settings_manager.get_bot_guild_ids()
# Add retry logic for database operations
max_db_retries = 3
retry_count = 0
bot_guild_ids = None
while retry_count < max_db_retries and bot_guild_ids is None:
try:
bot_guild_ids = await settings_manager.get_bot_guild_ids()
if bot_guild_ids is None:
log.warning(f"Dashboard: Failed to fetch bot guild IDs, retry {retry_count+1}/{max_db_retries}")
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
if retry_count < max_db_retries:
await asyncio.sleep(1) # Wait before retrying
# After retries, if still no data, raise exception
if bot_guild_ids is None:
log.error("Dashboard: Failed to fetch bot guild IDs from settings_manager.")
log.error("Dashboard: Failed to fetch bot guild IDs from settings_manager after retries.")
raise HTTPException(status_code=500, detail="Could not retrieve bot's guild list.")
except Exception as e:
log.exception("Dashboard: Exception while fetching bot guild IDs from settings_manager.")

15
restart_api.bat Normal file
View File

@ -0,0 +1,15 @@
@echo off
echo Restarting API server...
REM Change to the discordbot directory
cd /d %~dp0
REM Check for running API server processes
echo Checking for running API server processes...
tasklist /fi "imagename eq python.exe" /v | findstr "api_server"
REM Start the API server
echo Starting API server...
start python run_unified_api.py
echo API server restart complete.

69
restart_api.py Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env python
"""
Script to restart the API server with proper environment setup.
This ensures the database connections are properly initialized.
"""
import os
import sys
import subprocess
import time
def main():
"""Main function to restart the API server"""
print("Restarting API server...")
# Get the current directory
current_dir = os.path.dirname(os.path.abspath(__file__))
# Check if we're in the discordbot directory
if os.path.basename(current_dir) != "discordbot":
print("Error: This script must be run from the discordbot directory.")
sys.exit(1)
# Find any running API server processes
try:
print("Checking for running API server processes...")
result = subprocess.run(
["ps", "-ef", "|", "grep", "api_server.py", "|", "grep", "-v", "grep"],
shell=True,
capture_output=True,
text=True
)
if result.stdout.strip():
print("Found running API server processes:")
print(result.stdout)
# Ask for confirmation before killing
confirm = input("Do you want to kill these processes? (y/n): ")
if confirm.lower() == 'y':
# Kill the processes
subprocess.run(
["pkill", "-f", "api_server.py"],
shell=True
)
print("Processes killed.")
time.sleep(2) # Give processes time to terminate
else:
print("Aborted. Please stop the running API server processes manually.")
sys.exit(1)
except Exception as e:
print(f"Error checking for running processes: {e}")
# Start the API server
print("Starting API server...")
try:
# Use the run_unified_api.py script
subprocess.Popen(
[sys.executable, "run_unified_api.py"],
cwd=current_dir
)
print("API server started successfully.")
except Exception as e:
print(f"Error starting API server: {e}")
sys.exit(1)
print("API server restart complete.")
if __name__ == "__main__":
main()

View File

@ -31,18 +31,50 @@ async def initialize_pools():
"""Initializes the PostgreSQL and Redis connection pools."""
global pg_pool, redis_pool
log.info("Initializing database and cache connection pools...")
# Close existing pools if they exist
if pg_pool:
log.info("Closing existing PostgreSQL pool before reinitializing...")
await pg_pool.close()
pg_pool = None
if redis_pool:
log.info("Closing existing Redis pool before reinitializing...")
await redis_pool.close()
redis_pool = None
# Initialize new pools
try:
pg_pool = await asyncpg.create_pool(DATABASE_URL, min_size=1, max_size=10)
# Create PostgreSQL pool with more conservative settings
# Increase max_inactive_connection_lifetime to avoid connections being closed too quickly
pg_pool = await asyncpg.create_pool(
DATABASE_URL,
min_size=1,
max_size=10,
max_inactive_connection_lifetime=60.0, # 60 seconds (default is 10 minutes)
command_timeout=30.0 # 30 seconds timeout for commands
)
log.info(f"PostgreSQL pool connected to {POSTGRES_HOST}/{POSTGRES_DB}")
# Create Redis pool
redis_pool = redis.from_url(REDIS_URL, decode_responses=True)
await redis_pool.ping() # Test connection
await redis_pool.ping() # Test connection
log.info(f"Redis pool connected to {REDIS_HOST}:{REDIS_PORT}")
await initialize_database() # Ensure tables exist
# Initialize database schema
await initialize_database() # Ensure tables exist
return True # Indicate successful initialization
except Exception as e:
log.exception(f"Failed to initialize connection pools: {e}")
# Depending on bot structure, might want to raise or exit here
# Clean up any partially initialized resources
if pg_pool:
await pg_pool.close()
pg_pool = None
if redis_pool:
await redis_pool.close()
redis_pool = None
# Raise the exception to be handled by the caller
raise
async def close_pools():
@ -587,17 +619,29 @@ async def get_command_permissions(guild_id: int, command_name: str) -> set[int]
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."""
global pg_pool
if not pg_pool:
log.error("Pools not initialized, cannot get bot guild IDs.")
return None
# Create a new connection for this specific operation to avoid event loop conflicts
try:
async with 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.")
return guild_ids
# Create a temporary connection just for this operation
# This ensures we're using the current event loop
temp_conn = await asyncpg.connect(DATABASE_URL)
try:
records = await temp_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.")
return guild_ids
finally:
# Always close the temporary connection
await temp_conn.close()
except asyncpg.exceptions.PostgresError as e:
log.exception(f"PostgreSQL error fetching bot guild IDs: {e}")
return None
except Exception as e:
log.exception("Database error fetching bot guild IDs.")
log.exception(f"Unexpected error fetching bot guild IDs: {e}")
return None