diff --git a/VERSION b/VERSION index 7ec1d6d..3e3c2f1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0 +2.1.1 diff --git a/core/database.py b/core/database.py index 80503de..e8b5ec3 100644 --- a/core/database.py +++ b/core/database.py @@ -271,4 +271,13 @@ class NFOGuardDatabase: "movies_with_video": movies_with_video, "processing_history_count": history_count, "database_size_mb": round(self.db_path.stat().st_size / 1024 / 1024, 2) - } \ No newline at end of file + } + + def close(self): + """Close all database connections""" + if hasattr(self._local, 'connection'): + try: + self._local.connection.close() + delattr(self._local, 'connection') + except Exception: + pass # Connection may already be closed \ No newline at end of file diff --git a/main.py b/main.py index 4005224..a6d91c1 100644 --- a/main.py +++ b/main.py @@ -114,6 +114,28 @@ def initialize_components(): def signal_handler(signum, frame): """Handle shutdown signals gracefully""" _log("INFO", f"Received signal {signum}, shutting down gracefully...") + + # Get the global dependencies if they exist + if hasattr(signal_handler, 'dependencies') and signal_handler.dependencies: + deps = signal_handler.dependencies + + # Shutdown webhook batcher cleanly + if 'batcher' in deps: + try: + _log("INFO", "Shutting down webhook batcher...") + deps['batcher'].shutdown() + except Exception as e: + _log("WARNING", f"Error during batcher shutdown: {e}") + + # Close database connection + if 'db' in deps: + try: + _log("INFO", "Closing database connection...") + deps['db'].close() + except Exception as e: + _log("WARNING", f"Error closing database: {e}") + + _log("INFO", "Graceful shutdown complete") sys.exit(0) @@ -139,6 +161,9 @@ def main(): # Initialize components dependencies = initialize_components() + # Store dependencies globally for signal handler access + signal_handler.dependencies = dependencies + # Register routes register_routes(app, dependencies) @@ -147,13 +172,32 @@ def main(): app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")), - reload=False + reload=False, + access_log=False, # Reduce logging overhead + server_header=False, # Reduce response overhead + timeout_graceful_shutdown=15 # Give more time for graceful shutdown ) except KeyboardInterrupt: _log("INFO", "NFOGuard stopped by user") except Exception as e: _log("ERROR", f"NFOGuard crashed: {e}") sys.exit(1) + finally: + # Ensure cleanup happens even if uvicorn doesn't trigger signal handler + if hasattr(signal_handler, 'dependencies') and signal_handler.dependencies: + deps = signal_handler.dependencies + + if 'batcher' in deps: + try: + deps['batcher'].shutdown() + except Exception: + pass + + if 'db' in deps: + try: + deps['db'].close() + except Exception: + pass if __name__ == "__main__": diff --git a/webhooks/webhook_batcher.py b/webhooks/webhook_batcher.py index 0810791..6d9ea1f 100644 --- a/webhooks/webhook_batcher.py +++ b/webhooks/webhook_batcher.py @@ -172,4 +172,33 @@ class WebhookBatcher: "processing_items": list(self.processing), "pending_count": len(self.pending), "processing_count": len(self.processing) - } \ No newline at end of file + } + + def shutdown(self): + """Shutdown the webhook batcher gracefully""" + _log("INFO", "Shutting down webhook batcher...") + + with self.lock: + # Cancel all pending timers + for timer in self.timers.values(): + try: + timer.cancel() + except Exception as e: + _log("WARNING", f"Error canceling timer: {e}") + + self.timers.clear() + + # Log any remaining items + if self.pending: + _log("WARNING", f"Shutting down with {len(self.pending)} pending items") + if self.processing: + _log("INFO", f"Waiting for {len(self.processing)} items to finish processing...") + + # Shutdown the thread pool executor + try: + self.executor.shutdown(wait=True, timeout=10) # Wait up to 10 seconds + _log("INFO", "Thread pool executor shut down successfully") + except Exception as e: + _log("WARNING", f"Error shutting down thread pool: {e}") + + _log("INFO", "Webhook batcher shutdown complete") \ No newline at end of file