Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from api.middleware.firebase_auth import FirebaseAuthMiddleware
from api.routers import auth, ingestion, languages, query, rag, repository, support, webhook
from common.firebase_service import firebase_service # noqa: F401 — init on import
from db.client import close_neo4j_client # Add this import

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -43,7 +44,10 @@ async def lifespan(app: FastAPI):
"""
# Startup
yield
# Shutdown - cleanup will be handled by new system
# Shutdown - cleanup resources
logger.info("Shutting down application...")
close_neo4j_client()
logger.info("Neo4j client closed")


# Create FastAPI application
Expand Down Expand Up @@ -114,3 +118,4 @@ def run_server():

if __name__ == "__main__":
run_server()

66 changes: 52 additions & 14 deletions src/db/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
import time
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple

from neo4j import GraphDatabase
Expand Down Expand Up @@ -123,22 +124,59 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.close()


# Global singleton instance with thread-safety
_client_instance = None
_client_lock = Lock()


def get_neo4j_client() -> "Neo4jClient":
"""Build a Neo4jClient from environment variables.
"""Build a Neo4jClient from environment variables (singleton pattern).

Reads NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD, and NEO4J_DATABASE.
Raises ValueError if required variables are missing.

Returns a single shared instance across all requests (thread-safe).
Uses double-check locking pattern for efficient concurrent access.
"""
global _client_instance

# Fast path - no lock needed
if _client_instance is not None:
return _client_instance

# Lock only when needed
with _client_lock:
# Double-check pattern
if _client_instance is not None:
return _client_instance

uri = os.getenv("NEO4J_URI", "")
user = os.getenv("NEO4J_USERNAME", "neo4j")
password = os.getenv("NEO4J_PASSWORD", "")
database = os.getenv("NEO4J_DATABASE", "neo4j")

if not uri:
raise ValueError("NEO4J_URI environment variable is required")
if not user:
raise ValueError("NEO4J_USERNAME environment variable is required")
if not password:
raise ValueError("NEO4J_PASSWORD environment variable is required")

_client_instance = Neo4jClient(uri=uri, user=user, password=password, database=database)
logger.info("Neo4j client initialized (singleton)")
return _client_instance


def close_neo4j_client() -> None:
"""Close the Neo4j client and cleanup resources.

Call this during application shutdown to properly close database connections.
"""
uri = os.getenv("NEO4J_URI", "")
user = os.getenv("NEO4J_USERNAME", "neo4j")
password = os.getenv("NEO4J_PASSWORD", "")
database = os.getenv("NEO4J_DATABASE", "neo4j")

if not uri:
raise ValueError("NEO4J_URI environment variable is required")
if not user:
raise ValueError("NEO4J_USERNAME environment variable is required")
if not password:
raise ValueError("NEO4J_PASSWORD environment variable is required")

return Neo4jClient(uri=uri, user=user, password=password, database=database)
global _client_instance
with _client_lock:
if _client_instance is not None:
_client_instance.close()
_client_instance = None
logger.info("Neo4j client closed")