""" Background scheduler for License Server Handles periodic tasks like session cleanup """ import logging from datetime import datetime, timedelta from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger from sqlalchemy import text from sqlalchemy.orm import Session from app.db.database import SessionLocal logger = logging.getLogger(__name__) scheduler = BackgroundScheduler() def cleanup_expired_sessions(): """Clean up sessions that haven't sent heartbeat within timeout period""" db: Session = SessionLocal() try: # Get session timeout from config (default 60 seconds) result = db.execute(text(""" SELECT session_timeout FROM client_configs WHERE client_name = 'Account Forger' """)).first() timeout_seconds = result[0] if result else 60 # Find expired sessions expired_sessions = db.execute(text(f""" SELECT id, license_id, hardware_fingerprint, session_token FROM license_sessions WHERE ended_at IS NULL AND last_heartbeat < NOW() - INTERVAL '{timeout_seconds} seconds' """)).fetchall() if expired_sessions: logger.info(f"Found {len(expired_sessions)} expired sessions to clean up") # Mark sessions as ended for session in expired_sessions: db.execute(text(""" UPDATE license_sessions SET ended_at = NOW(), end_reason = 'timeout' WHERE id = :session_id """), {"session_id": session[0]}) logger.info(f"Ended session {session[0]} for license {session[1]} due to timeout") db.commit() logger.info(f"Successfully cleaned up {len(expired_sessions)} expired sessions") except Exception as e: logger.error(f"Error cleaning up sessions: {str(e)}") db.rollback() finally: db.close() def cleanup_old_sessions(): """Remove old ended sessions from database (older than 30 days)""" db: Session = SessionLocal() try: result = db.execute(text(""" DELETE FROM license_sessions WHERE ended_at IS NOT NULL AND ended_at < NOW() - INTERVAL '30 days' """)) if result.rowcount > 0: db.commit() logger.info(f"Cleaned up {result.rowcount} old sessions") except Exception as e: logger.error(f"Error cleaning up old sessions: {str(e)}") db.rollback() finally: db.close() def init_scheduler(): """Initialize and start the background scheduler""" # Add job to cleanup expired sessions every 30 seconds scheduler.add_job( func=cleanup_expired_sessions, trigger=IntervalTrigger(seconds=30), id='cleanup_expired_sessions', name='Cleanup expired sessions', replace_existing=True ) # Add job to cleanup old sessions daily at 3 AM scheduler.add_job( func=cleanup_old_sessions, trigger='cron', hour=3, minute=0, id='cleanup_old_sessions', name='Cleanup old sessions', replace_existing=True ) scheduler.start() logger.info("Background scheduler started") logger.info("- Session cleanup runs every 30 seconds") logger.info("- Old session cleanup runs daily at 3:00 AM") def shutdown_scheduler(): """Shutdown the scheduler gracefully""" if scheduler.running: scheduler.shutdown() logger.info("Background scheduler stopped")