From 3c0ef4c2fb1e5b6732ac39a81ff1896ac2696505 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Nov 2025 23:22:06 +0000 Subject: [PATCH] Add thread-safety to lazy initialization with double-checked locking - Add threading.Lock for thread-safe lazy initialization of models - Implement double-checked locking pattern in all initialization methods: - _initialize_embedding_model - _initialize_chunker - _initialize_code_chunker - _initialize_reranker - Remove unused _models_initialized variable - Consolidate RERANKERS_ENABLED check in _initialize_reranker - Remove redundant check from reranker_instance property Addresses PR #15 review feedback for thread-safety concerns --- surfsense_backend/app/config/__init__.py | 81 ++++++++++++++---------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index b240636e6..922f453e5 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -1,5 +1,6 @@ import os import shutil +import threading from pathlib import Path from typing import Any @@ -183,46 +184,55 @@ class Config: _chunker_instance = None _code_chunker_instance = None _reranker_instance = None - _models_initialized = False + _init_lock = threading.Lock() # Thread-safety lock for lazy initialization @classmethod def _initialize_embedding_model(cls): - """Initialize embedding model on first access (lazy loading).""" + """Initialize embedding model on first access (lazy loading with thread-safety).""" if cls._embedding_model_instance is None: - cls._embedding_model_instance = AutoEmbeddings.get_embeddings( - cls.EMBEDDING_MODEL, - **cls.embedding_kwargs, - ) - # Validate embedding dimension - if ( - hasattr(cls._embedding_model_instance, "dimension") - and cls._embedding_model_instance.dimension > 2000 - ): - raise ValueError( - f"Embedding dimension for Model: {cls.EMBEDDING_MODEL} " - f"has {cls._embedding_model_instance.dimension} dimensions, which " - f"exceeds the maximum of 2000 allowed by PGVector." - ) + with cls._init_lock: + # Double-check after acquiring lock + if cls._embedding_model_instance is None: + cls._embedding_model_instance = AutoEmbeddings.get_embeddings( + cls.EMBEDDING_MODEL, + **cls.embedding_kwargs, + ) + # Validate embedding dimension + if ( + hasattr(cls._embedding_model_instance, "dimension") + and cls._embedding_model_instance.dimension > 2000 + ): + raise ValueError( + f"Embedding dimension for Model: {cls.EMBEDDING_MODEL} " + f"has {cls._embedding_model_instance.dimension} dimensions, which " + f"exceeds the maximum of 2000 allowed by PGVector." + ) return cls._embedding_model_instance @classmethod def _initialize_chunker(cls): - """Initialize chunker on first access (lazy loading).""" + """Initialize chunker on first access (lazy loading with thread-safety).""" if cls._chunker_instance is None: - embedding_model = cls._initialize_embedding_model() - cls._chunker_instance = RecursiveChunker( - chunk_size=getattr(embedding_model, "max_seq_length", 512) - ) + with cls._init_lock: + # Double-check after acquiring lock + if cls._chunker_instance is None: + embedding_model = cls._initialize_embedding_model() + cls._chunker_instance = RecursiveChunker( + chunk_size=getattr(embedding_model, "max_seq_length", 512) + ) return cls._chunker_instance @classmethod def _initialize_code_chunker(cls): - """Initialize code chunker on first access (lazy loading).""" + """Initialize code chunker on first access (lazy loading with thread-safety).""" if cls._code_chunker_instance is None: - embedding_model = cls._initialize_embedding_model() - cls._code_chunker_instance = CodeChunker( - chunk_size=getattr(embedding_model, "max_seq_length", 512) - ) + with cls._init_lock: + # Double-check after acquiring lock + if cls._code_chunker_instance is None: + embedding_model = cls._initialize_embedding_model() + cls._code_chunker_instance = CodeChunker( + chunk_size=getattr(embedding_model, "max_seq_length", 512) + ) return cls._code_chunker_instance # Properties for lazy access to model instances @@ -245,18 +255,21 @@ class Config: @classmethod def _initialize_reranker(cls): - """Initialize reranker on first access (lazy loading).""" - if cls.RERANKERS_ENABLED and cls._reranker_instance is None: - cls._reranker_instance = Reranker( - model_name=cls.RERANKERS_MODEL_NAME, - model_type=cls.RERANKERS_MODEL_TYPE, - ) + """Initialize reranker on first access (lazy loading with thread-safety).""" + if not cls.RERANKERS_ENABLED: + return None + if cls._reranker_instance is None: + with cls._init_lock: + # Double-check after acquiring lock + if cls._reranker_instance is None: + cls._reranker_instance = Reranker( + model_name=cls.RERANKERS_MODEL_NAME, + model_type=cls.RERANKERS_MODEL_TYPE, + ) return cls._reranker_instance @property def reranker_instance(self): - if not Config.RERANKERS_ENABLED: - return None return Config._initialize_reranker() # OAuth JWT