fix(shutdown): implement graceful async shutdown handler
Replace the minimal sync-only shutdown (which only saved autonomous state) with a comprehensive async graceful_shutdown() coroutine that: 1. Ends active voice sessions (disconnect, release GPU locks, cleanup audio) 2. Saves autonomous engine state 3. Stops the APScheduler 4. Cancels all tracked background tasks (from task_tracker) 5. Closes the Discord gateway connection Signal handlers (SIGTERM/SIGINT) now schedule the async shutdown on the running event loop. The atexit handler is kept as a last-resort sync fallback. Resolves #5, also addresses #4 (voice cleanup at shutdown)
This commit is contained in:
78
bot/bot.py
78
bot/bot.py
@@ -874,10 +874,82 @@ def save_autonomous_state():
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save autonomous context on shutdown: {e}")
|
||||
|
||||
# Register shutdown handlers
|
||||
async def graceful_shutdown():
|
||||
"""
|
||||
Perform a full async cleanup before the bot exits.
|
||||
|
||||
Shutdown sequence:
|
||||
1. End active voice sessions (disconnect, release GPU locks)
|
||||
2. Save autonomous engine state
|
||||
3. Stop the APScheduler
|
||||
4. Cancel all tracked background tasks
|
||||
5. Close the Discord gateway connection
|
||||
"""
|
||||
logger.warning("🛑 Graceful shutdown initiated...")
|
||||
|
||||
# 1. End active voice session (cleans up audio, STT, GPU locks, etc.)
|
||||
try:
|
||||
from utils.voice_manager import VoiceSessionManager
|
||||
session_mgr = VoiceSessionManager()
|
||||
if session_mgr.active_session:
|
||||
logger.info("🎙️ Ending active voice session...")
|
||||
await session_mgr.end_session()
|
||||
logger.info("✓ Voice session ended")
|
||||
except Exception as e:
|
||||
logger.error(f"Error ending voice session during shutdown: {e}")
|
||||
|
||||
# 2. Persist autonomous engine state
|
||||
save_autonomous_state()
|
||||
|
||||
# 3. Shut down the APScheduler
|
||||
try:
|
||||
if globals.scheduler.running:
|
||||
globals.scheduler.shutdown(wait=False)
|
||||
logger.info("✓ Scheduler stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping scheduler: {e}")
|
||||
|
||||
# 4. Cancel all tracked background tasks
|
||||
try:
|
||||
from utils.task_tracker import _active_tasks
|
||||
pending = [t for t in _active_tasks if not t.done()]
|
||||
if pending:
|
||||
logger.info(f"Cancelling {len(pending)} background tasks...")
|
||||
for t in pending:
|
||||
t.cancel()
|
||||
await asyncio.gather(*pending, return_exceptions=True)
|
||||
logger.info("✓ Background tasks cancelled")
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling background tasks: {e}")
|
||||
|
||||
# 5. Close the Discord gateway connection
|
||||
try:
|
||||
if not globals.client.is_closed():
|
||||
await globals.client.close()
|
||||
logger.info("✓ Discord client closed")
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing Discord client: {e}")
|
||||
|
||||
logger.warning("🛑 Graceful shutdown complete")
|
||||
|
||||
def _handle_shutdown_signal(sig, _frame):
|
||||
"""Schedule the async shutdown from a sync signal handler."""
|
||||
sig_name = signal.Signals(sig).name
|
||||
logger.warning(f"Received {sig_name}, scheduling graceful shutdown...")
|
||||
# Schedule the coroutine on the running event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
loop.create_task(graceful_shutdown())
|
||||
else:
|
||||
# Fallback: just save state synchronously
|
||||
save_autonomous_state()
|
||||
|
||||
# Register signal handlers (async-aware)
|
||||
signal.signal(signal.SIGTERM, _handle_shutdown_signal)
|
||||
signal.signal(signal.SIGINT, _handle_shutdown_signal)
|
||||
|
||||
# Keep atexit as a last-resort sync fallback
|
||||
atexit.register(save_autonomous_state)
|
||||
signal.signal(signal.SIGTERM, lambda s, f: save_autonomous_state())
|
||||
signal.signal(signal.SIGINT, lambda s, f: save_autonomous_state())
|
||||
|
||||
threading.Thread(target=start_api, daemon=True).start()
|
||||
globals.client.run(globals.DISCORD_BOT_TOKEN)
|
||||
|
||||
Reference in New Issue
Block a user