Merge pull request #17 from okapteinis/claude/thread-safety-fixes-01WNkTGGrYURxe9JATA3gX6y

Add thread-safety to lazy initialization with double-checked locking
This commit is contained in:
Ojārs Kapteinis 2025-11-19 01:23:05 +02:00 committed by GitHub
commit 0372fecdfa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,6 @@
import os import os
import shutil import shutil
import threading
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -183,46 +184,55 @@ class Config:
_chunker_instance = None _chunker_instance = None
_code_chunker_instance = None _code_chunker_instance = None
_reranker_instance = None _reranker_instance = None
_models_initialized = False _init_lock = threading.Lock() # Thread-safety lock for lazy initialization
@classmethod @classmethod
def _initialize_embedding_model(cls): 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: if cls._embedding_model_instance is None:
cls._embedding_model_instance = AutoEmbeddings.get_embeddings( with cls._init_lock:
cls.EMBEDDING_MODEL, # Double-check after acquiring lock
**cls.embedding_kwargs, if cls._embedding_model_instance is None:
) cls._embedding_model_instance = AutoEmbeddings.get_embeddings(
# Validate embedding dimension cls.EMBEDDING_MODEL,
if ( **cls.embedding_kwargs,
hasattr(cls._embedding_model_instance, "dimension") )
and cls._embedding_model_instance.dimension > 2000 # Validate embedding dimension
): if (
raise ValueError( hasattr(cls._embedding_model_instance, "dimension")
f"Embedding dimension for Model: {cls.EMBEDDING_MODEL} " and cls._embedding_model_instance.dimension > 2000
f"has {cls._embedding_model_instance.dimension} dimensions, which " ):
f"exceeds the maximum of 2000 allowed by PGVector." 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 return cls._embedding_model_instance
@classmethod @classmethod
def _initialize_chunker(cls): 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: if cls._chunker_instance is None:
embedding_model = cls._initialize_embedding_model() with cls._init_lock:
cls._chunker_instance = RecursiveChunker( # Double-check after acquiring lock
chunk_size=getattr(embedding_model, "max_seq_length", 512) 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 return cls._chunker_instance
@classmethod @classmethod
def _initialize_code_chunker(cls): 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: if cls._code_chunker_instance is None:
embedding_model = cls._initialize_embedding_model() with cls._init_lock:
cls._code_chunker_instance = CodeChunker( # Double-check after acquiring lock
chunk_size=getattr(embedding_model, "max_seq_length", 512) 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 return cls._code_chunker_instance
# Properties for lazy access to model instances # Properties for lazy access to model instances
@ -245,18 +255,21 @@ class Config:
@classmethod @classmethod
def _initialize_reranker(cls): def _initialize_reranker(cls):
"""Initialize reranker on first access (lazy loading).""" """Initialize reranker on first access (lazy loading with thread-safety)."""
if cls.RERANKERS_ENABLED and cls._reranker_instance is None: if not cls.RERANKERS_ENABLED:
cls._reranker_instance = Reranker( return None
model_name=cls.RERANKERS_MODEL_NAME, if cls._reranker_instance is None:
model_type=cls.RERANKERS_MODEL_TYPE, 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 return cls._reranker_instance
@property @property
def reranker_instance(self): def reranker_instance(self):
if not Config.RERANKERS_ENABLED:
return None
return Config._initialize_reranker() return Config._initialize_reranker()
# OAuth JWT # OAuth JWT