fix: Improve Docker container shutdown handling
Local Docker Build (Dev) / build-dev (push) Successful in 3s

- Add proper signal handlers for graceful shutdown of all components
- Implement WebhookBatcher.shutdown() to properly close ThreadPoolExecutor and cancel timers
- Add NFOGuardDatabase.close() method to close thread-local database connections
- Increase uvicorn graceful shutdown timeout to 15 seconds
- Add cleanup in finally block to ensure resources are freed even if signal handler fails
- Reduce logging and server header overhead for better performance

This should resolve Docker container shutdown issues where containers don't respond to SIGTERM properly.

Bump version to 2.1.1

🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
2025-10-12 13:02:07 -04:00
parent c8fde41dfb
commit 867bd008c5
4 changed files with 86 additions and 4 deletions
+1 -1
View File
@@ -1 +1 @@
2.1.0
2.1.1
+9
View File
@@ -272,3 +272,12 @@ class NFOGuardDatabase:
"processing_history_count": history_count,
"database_size_mb": round(self.db_path.stat().st_size / 1024 / 1024, 2)
}
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
+45 -1
View File
@@ -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__":
+29
View File
@@ -173,3 +173,32 @@ class WebhookBatcher:
"pending_count": len(self.pending),
"processing_count": len(self.processing)
}
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")