11import threading
22import time
33import logging
4+ import json
45from concurrent .futures import ThreadPoolExecutor
5- from typing import Dict , Optional , TYPE_CHECKING
6+ from concurrent .futures import Future
7+ from datetime import datetime , timezone
8+ from typing import List , Dict , Any , Optional , TYPE_CHECKING
69from databricks .sql .telemetry .models .event import (
710 TelemetryEvent ,
811 DriverSystemConfiguration ,
3639import locale
3740from databricks .sql .telemetry .utils import BaseTelemetryClient
3841from databricks .sql .common .feature_flag import FeatureFlagsContextFactory
39-
40- from src .databricks .sql .common .unified_http_client import UnifiedHttpClient
42+ from databricks .sql .common .unified_http_client import UnifiedHttpClient
4143
4244if TYPE_CHECKING :
4345 from databricks .sql .client import Connection
@@ -151,6 +153,44 @@ def _flush(self):
151153 pass
152154
153155
156+ class TelemetryHttpClientSingleton :
157+ """
158+ Singleton HTTP client for telemetry operations.
159+
160+ This ensures that telemetry has its own dedicated HTTP client that
161+ is independent of individual connection lifecycles.
162+ """
163+
164+ _instance = None
165+ _lock = threading .RLock ()
166+
167+ def __new__ (cls ):
168+ if cls ._instance is None :
169+ with cls ._lock :
170+ if cls ._instance is None :
171+ cls ._instance = super ().__new__ (cls )
172+ cls ._instance ._http_client = None
173+ cls ._instance ._initialized = False
174+ return cls ._instance
175+
176+ def get_http_client (self , client_context ):
177+ """Get or create the singleton HTTP client."""
178+ if not self ._initialized and client_context :
179+ with self ._lock :
180+ if not self ._initialized :
181+ self ._http_client = UnifiedHttpClient (client_context )
182+ self ._initialized = True
183+ return self ._http_client
184+
185+ def close (self ):
186+ """Close the singleton HTTP client."""
187+ with self ._lock :
188+ if self ._http_client :
189+ self ._http_client .close ()
190+ self ._http_client = None
191+ self ._initialized = False
192+
193+
154194class TelemetryClient (BaseTelemetryClient ):
155195 """
156196 Telemetry client class that handles sending telemetry events in batches to the server.
@@ -169,7 +209,7 @@ def __init__(
169209 host_url ,
170210 executor ,
171211 batch_size ,
172- http_client ,
212+ client_context ,
173213 ):
174214 logger .debug ("Initializing TelemetryClient for connection: %s" , session_id_hex )
175215 self ._telemetry_enabled = telemetry_enabled
@@ -182,7 +222,10 @@ def __init__(
182222 self ._driver_connection_params = None
183223 self ._host_url = host_url
184224 self ._executor = executor
185- self ._http_client = http_client
225+
226+ # Use singleton HTTP client for telemetry instead of connection-specific client
227+ self ._http_client_singleton = TelemetryHttpClientSingleton ()
228+ self ._http_client = self ._http_client_singleton .get_http_client (client_context )
186229
187230 def _export_event (self , event ):
188231 """Add an event to the batch queue and flush if batch is full"""
@@ -246,17 +289,24 @@ def _send_telemetry(self, events):
246289 except Exception as e :
247290 logger .debug ("Failed to submit telemetry request: %s" , e )
248291
249- def _send_with_unified_client (self , url , data , headers ):
292+ def _send_with_unified_client (self , url , data , headers , timeout = 900 ):
250293 """Helper method to send telemetry using the unified HTTP client."""
251294 try :
252295 response = self ._http_client .request (
253- "POST" , url , body = data , headers = headers , timeout = 900
296+ "POST" , url , body = data , headers = headers , timeout = timeout
254297 )
255298 # Convert urllib3 response to requests-like response for compatibility
256299 response .status_code = response .status
300+ response .ok = 200 <= response .status < 300
257301 response .json = (
258302 lambda : json .loads (response .data .decode ()) if response .data else {}
259303 )
304+ # Add raise_for_status method
305+ def raise_for_status ():
306+ if not response .ok :
307+ raise Exception (f"HTTP { response .status_code } " )
308+
309+ response .raise_for_status = raise_for_status
260310 return response
261311 except Exception as e :
262312 logger .error ("Failed to send telemetry with unified client: %s" , e )
@@ -452,7 +502,7 @@ def initialize_telemetry_client(
452502 auth_provider ,
453503 host_url ,
454504 batch_size ,
455- http_client ,
505+ client_context ,
456506 ):
457507 """Initialize a telemetry client for a specific connection if telemetry is enabled"""
458508 try :
@@ -475,7 +525,7 @@ def initialize_telemetry_client(
475525 host_url = host_url ,
476526 executor = TelemetryClientFactory ._executor ,
477527 batch_size = batch_size ,
478- http_client = http_client ,
528+ client_context = client_context ,
479529 )
480530 else :
481531 TelemetryClientFactory ._clients [
@@ -528,7 +578,7 @@ def connection_failure_log(
528578 host_url : str ,
529579 http_path : str ,
530580 port : int ,
531- http_client : UnifiedHttpClient ,
581+ client_context ,
532582 user_agent : Optional [str ] = None ,
533583 ):
534584 """Send error telemetry when connection creation fails, without requiring a session"""
@@ -541,7 +591,7 @@ def connection_failure_log(
541591 auth_provider = None ,
542592 host_url = host_url ,
543593 batch_size = TelemetryClientFactory .DEFAULT_BATCH_SIZE ,
544- http_client = http_client ,
594+ client_context = client_context ,
545595 )
546596
547597 telemetry_client = TelemetryClientFactory .get_telemetry_client (
0 commit comments