From cdb46f919dfd4350b660c43238acd13147f665a6 Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Fri, 21 Nov 2025 19:11:54 -0800 Subject: [PATCH] fix: cache SSL contexts to prevent excessive memory allocation (#16955) Previously, get_ssl_configuration() created a new SSL context on every call, even when the configuration was identical. This caused continuous memory allocation from ssl.create_default_context(), especially during: - Proxy server startup - Background health checks - HTTP client creation Solution: - Added _ssl_context_cache to cache SSL contexts by configuration parameters (cafile, ssl_security_level, ssl_ecdh_curve) - Refactored SSL context creation into _create_ssl_context() helper - Modified get_ssl_configuration() to reuse cached contexts when configuration matches This significantly reduces memory allocation while maintaining backward compatibility. SSL contexts are now reused instead of being recreated repeatedly, eliminating the memory leak observed in memray profiling. Fixes memory allocation issue where create_default_context was allocating 6.282MB+ continuously even without any requests. --- litellm/llms/custom_httpx/http_handler.py | 110 ++++++++++++++-------- 1 file changed, 71 insertions(+), 39 deletions(-) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 37b4af306a..dbeceab5e0 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -87,6 +87,60 @@ def _prepare_request_data_and_content( return request_data, request_content +# Cache for SSL contexts to avoid creating duplicate contexts with the same configuration +# Key: tuple of (cafile, ssl_security_level, ssl_ecdh_curve) +# Value: ssl.SSLContext +_ssl_context_cache: Dict[Tuple[Optional[str], Optional[str], Optional[str]], ssl.SSLContext] = {} + + +def _create_ssl_context( + cafile: Optional[str], + ssl_security_level: Optional[str], + ssl_ecdh_curve: Optional[str], +) -> ssl.SSLContext: + """ + Create an SSL context with the given configuration. + This is separated from get_ssl_configuration to enable caching. + """ + custom_ssl_context = ssl.create_default_context(cafile=cafile) + + # Optimize SSL handshake performance + # Set minimum TLS version to 1.2 for better performance + custom_ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 + + # Configure cipher suites for optimal performance + if ssl_security_level and isinstance(ssl_security_level, str): + # User provided custom cipher configuration (e.g., via SSL_SECURITY_LEVEL env var) + custom_ssl_context.set_ciphers(ssl_security_level) + else: + # Use optimized cipher list that strongly prefers fast ciphers + # but falls back to widely compatible ones + custom_ssl_context.set_ciphers(DEFAULT_SSL_CIPHERS) + + # Configure ECDH curve for key exchange (e.g., to disable PQC and improve performance) + # Set SSL_ECDH_CURVE env var or litellm.ssl_ecdh_curve to 'X25519' to disable PQC + # Common valid curves: X25519, prime256v1, secp384r1, secp521r1 + if ssl_ecdh_curve and isinstance(ssl_ecdh_curve, str): + try: + custom_ssl_context.set_ecdh_curve(ssl_ecdh_curve) + verbose_logger.debug(f"SSL ECDH curve set to: {ssl_ecdh_curve}") + except AttributeError: + verbose_logger.warning( + f"SSL ECDH curve configuration not supported. " + f"Python version: {sys.version.split()[0]}, OpenSSL version: {ssl.OPENSSL_VERSION}. " + f"Requested curve: {ssl_ecdh_curve}. Continuing with default curves." + ) + except ValueError as e: + # Invalid curve name + verbose_logger.warning( + f"Invalid SSL ECDH curve name: '{ssl_ecdh_curve}'. {e}. " + f"Common valid curves: X25519, prime256v1, secp384r1, secp521r1. " + f"Continuing with default curves (including PQC)." + ) + + return custom_ssl_context + + def get_ssl_configuration( ssl_verify: Optional[VerifyTypes] = None, ) -> Union[bool, str, ssl.SSLContext]: @@ -102,6 +156,9 @@ def get_ssl_configuration( If ssl_security_level is set, it will apply the security level to the SSL context. + SSL contexts are cached to avoid creating duplicate contexts with the same configuration, + which reduces memory allocation and improves performance. + Args: ssl_verify: SSL verification setting. Can be: - None: Use default from environment/litellm settings @@ -128,6 +185,7 @@ def get_ssl_configuration( ssl_verify = ssl_verify_bool ssl_security_level = os.getenv("SSL_SECURITY_LEVEL", litellm.ssl_security_level) + ssl_ecdh_curve = os.getenv("SSL_ECDH_CURVE", litellm.ssl_ecdh_curve) cafile = None if isinstance(ssl_verify, str) and os.path.exists(ssl_verify): @@ -140,45 +198,19 @@ def get_ssl_configuration( cafile = certifi.where() if ssl_verify is not False: - custom_ssl_context = ssl.create_default_context(cafile=cafile) - - # Optimize SSL handshake performance - # Set minimum TLS version to 1.2 for better performance - custom_ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 - - # Configure cipher suites for optimal performance - if ssl_security_level and isinstance(ssl_security_level, str): - # User provided custom cipher configuration (e.g., via SSL_SECURITY_LEVEL env var) - custom_ssl_context.set_ciphers(ssl_security_level) - else: - # Use optimized cipher list that strongly prefers fast ciphers - # but falls back to widely compatible ones - custom_ssl_context.set_ciphers(DEFAULT_SSL_CIPHERS) - - # Configure ECDH curve for key exchange (e.g., to disable PQC and improve performance) - # Set SSL_ECDH_CURVE env var or litellm.ssl_ecdh_curve to 'X25519' to disable PQC - # Common valid curves: X25519, prime256v1, secp384r1, secp521r1 - ssl_ecdh_curve = os.getenv("SSL_ECDH_CURVE", litellm.ssl_ecdh_curve) - if ssl_ecdh_curve and isinstance(ssl_ecdh_curve, str): - try: - custom_ssl_context.set_ecdh_curve(ssl_ecdh_curve) - verbose_logger.debug(f"SSL ECDH curve set to: {ssl_ecdh_curve}") - except AttributeError: - verbose_logger.warning( - f"SSL ECDH curve configuration not supported. " - f"Python version: {sys.version.split()[0]}, OpenSSL version: {ssl.OPENSSL_VERSION}. " - f"Requested curve: {ssl_ecdh_curve}. Continuing with default curves." - ) - except ValueError as e: - # Invalid curve name - verbose_logger.warning( - f"Invalid SSL ECDH curve name: '{ssl_ecdh_curve}'. {e}. " - f"Common valid curves: X25519, prime256v1, secp384r1, secp521r1. " - f"Continuing with default curves (including PQC)." - ) - - # Use our custom SSL context instead of the original ssl_verify value - return custom_ssl_context + # Create cache key from configuration parameters + cache_key = (cafile, ssl_security_level, ssl_ecdh_curve) + + # Check if we have a cached SSL context for this configuration + if cache_key not in _ssl_context_cache: + _ssl_context_cache[cache_key] = _create_ssl_context( + cafile=cafile, + ssl_security_level=ssl_security_level, + ssl_ecdh_curve=ssl_ecdh_curve, + ) + + # Return the cached SSL context + return _ssl_context_cache[cache_key] return ssl_verify