fix(metrics): prevent thread leak by ensuring singleton initializatio… · googleapis/python-spanner@e792136
@@ -23,10 +23,12 @@
2323* a :class:`~google.cloud.spanner_v1.instance.Instance` owns a
2424 :class:`~google.cloud.spanner_v1.database.Database`
2525"""
26+2627import grpc
2728import os
2829import logging
2930import warnings
31+import threading
30323133from google.api_core.gapic_v1 import client_info
3234from google.auth.credentials import AnonymousCredentials
@@ -99,11 +101,50 @@ def _get_spanner_optimizer_statistics_package():
99101100102log = logging.getLogger(__name__)
101103104+_metrics_monitor_initialized = False
105+_metrics_monitor_lock = threading.Lock()
106+102107103108def _get_spanner_enable_builtin_metrics_env():
104109return os.getenv(SPANNER_DISABLE_BUILTIN_METRICS_ENV_VAR) != "true"
105110106111112+def _initialize_metrics(project, credentials):
113+"""
114+ Initializes the Spanner built-in metrics.
115+116+ This function sets up the OpenTelemetry MeterProvider and the SpannerMetricsTracerFactory.
117+ It uses a lock to ensure that initialization happens only once.
118+ """
119+global _metrics_monitor_initialized
120+if not _metrics_monitor_initialized:
121+with _metrics_monitor_lock:
122+if not _metrics_monitor_initialized:
123+meter_provider = metrics.NoOpMeterProvider()
124+try:
125+if not _get_spanner_emulator_host():
126+meter_provider = MeterProvider(
127+metric_readers=[
128+PeriodicExportingMetricReader(
129+CloudMonitoringMetricsExporter(
130+project_id=project,
131+credentials=credentials,
132+ ),
133+export_interval_millis=METRIC_EXPORT_INTERVAL_MS,
134+ ),
135+ ]
136+ )
137+metrics.set_meter_provider(meter_provider)
138+SpannerMetricsTracerFactory()
139+_metrics_monitor_initialized = True
140+except Exception as e:
141+# log is already defined at module level
142+log.warning(
143+"Failed to initialize Spanner built-in metrics. Error: %s",
144+e,
145+ )
146+147+107148class Client(ClientWithProject):
108149"""Client for interacting with Cloud Spanner API.
109150@@ -251,31 +292,12 @@ def __init__(
251292"http://" in self._emulator_host or "https://" in self._emulator_host
252293 ):
253294warnings.warn(_EMULATOR_HOST_HTTP_SCHEME)
254-# Check flag to enable Spanner builtin metrics
255295if (
256296_get_spanner_enable_builtin_metrics_env()
257297and not disable_builtin_metrics
258298and HAS_GOOGLE_CLOUD_MONITORING_INSTALLED
259299 ):
260-meter_provider = metrics.NoOpMeterProvider()
261-try:
262-if not _get_spanner_emulator_host():
263-meter_provider = MeterProvider(
264-metric_readers=[
265-PeriodicExportingMetricReader(
266-CloudMonitoringMetricsExporter(
267-project_id=project, credentials=credentials
268- ),
269-export_interval_millis=METRIC_EXPORT_INTERVAL_MS,
270- ),
271- ]
272- )
273-metrics.set_meter_provider(meter_provider)
274-SpannerMetricsTracerFactory()
275-except Exception as e:
276-log.warning(
277-"Failed to initialize Spanner built-in metrics. Error: %s", e
278- )
300+_initialize_metrics(project, credentials)
279301else:
280302SpannerMetricsTracerFactory(enabled=False)
281303