Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Session sync daemon - Periodically backs up sessions to HF Dataset. | |
| Runs as a background process managed by process-compose. | |
| Syncs session files every 5 minutes to ensure persistence. | |
| Implements graceful shutdown with signal handling: | |
| - SIGTERM: Docker/process-compose shutdown (triggers final backup) | |
| - SIGINT: Ctrl+C / manual interrupt (triggers final backup) | |
| """ | |
| import os | |
| import sys | |
| import time | |
| import signal | |
| import logging | |
| from pathlib import Path | |
| # Add hf_space directory to path | |
| sys.path.insert(0, str(Path(__file__).parent)) | |
| from session_sync import backup_sessions_to_dataset, SessionSyncError | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='[%(asctime)s] %(levelname)s: %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # Configuration | |
| SYNC_INTERVAL_SECONDS = 120 # 2 minutes (increased from 5 for faster persistence) | |
| # Global flag for graceful shutdown | |
| shutdown_requested = False | |
| def signal_handler(signum, frame): | |
| """ | |
| Handle shutdown signals (SIGTERM, SIGINT). | |
| Triggers immediate backup before exit to preserve session state. | |
| Signal sources: | |
| - SIGTERM: Docker stop, process-compose shutdown, HF Space restart | |
| - SIGINT: Ctrl+C, manual interrupt | |
| """ | |
| global shutdown_requested | |
| signal_name = signal.Signals(signum).name | |
| logger.info("=" * 60) | |
| logger.info(f"Received {signal_name} signal - initiating graceful shutdown") | |
| logger.info("=" * 60) | |
| shutdown_requested = True | |
| # Perform final backup if HF_TOKEN is available | |
| if os.environ.get("HF_TOKEN"): | |
| try: | |
| logger.info("Triggering final session backup...") | |
| backup_sessions_to_dataset() | |
| logger.info("β Final backup complete") | |
| except SessionSyncError as e: | |
| logger.error(f"π¨ SECURITY ERROR during shutdown: {e}") | |
| # Don't exit(1) here - we're already shutting down | |
| except Exception as e: | |
| logger.warning(f"Final backup failed: {e}") | |
| else: | |
| logger.info("HF_TOKEN not set - skipping final backup") | |
| logger.info("=" * 60) | |
| logger.info("Session Sync Daemon - Stopped") | |
| logger.info("=" * 60) | |
| sys.exit(0) | |
| def main(): | |
| """Run periodic session sync.""" | |
| # Register signal handlers BEFORE starting loop | |
| # SIGTERM: Docker/process-compose shutdown | |
| # SIGINT: Ctrl+C / manual interrupt | |
| signal.signal(signal.SIGTERM, signal_handler) | |
| signal.signal(signal.SIGINT, signal_handler) | |
| logger.info("=" * 60) | |
| logger.info("Session Sync Daemon - Started") | |
| logger.info("=" * 60) | |
| logger.info("Signal handlers registered (SIGTERM, SIGINT)") | |
| # Check if HF_TOKEN is set | |
| if not os.environ.get("HF_TOKEN"): | |
| logger.warning("HF_TOKEN not set - daemon will not sync sessions") | |
| logger.info("Set HF_TOKEN in Space settings to enable persistence") | |
| logger.info("Daemon will sleep indefinitely (required by process-compose)") | |
| # Keep daemon running but do nothing (still responsive to signals) | |
| while not shutdown_requested: | |
| time.sleep(3600) | |
| return | |
| logger.info(f"Syncing sessions every {SYNC_INTERVAL_SECONDS} seconds") | |
| while not shutdown_requested: | |
| try: | |
| # Sleep in small intervals to be responsive to shutdown signals | |
| # Split SYNC_INTERVAL into 1-second chunks for faster signal response | |
| for _ in range(SYNC_INTERVAL_SECONDS): | |
| if shutdown_requested: | |
| break | |
| time.sleep(1) | |
| if shutdown_requested: | |
| break | |
| logger.info("Starting session backup...") | |
| backup_sessions_to_dataset() | |
| logger.info("β Session backup complete") | |
| except SessionSyncError as e: | |
| logger.error(f"π¨ SECURITY ERROR: {e}") | |
| logger.error("Daemon stopping to prevent data leak") | |
| sys.exit(1) | |
| except Exception as e: | |
| logger.warning(f"Session sync failed: {e}") | |
| logger.info("Will retry on next interval...") | |
| # Graceful exit (signal handler already performed final backup) | |
| logger.info("Main loop exited gracefully") | |
| if __name__ == "__main__": | |
| main() | |