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+2627

import grpc

2728

import os

2829

import logging

2930

import warnings

31+

import threading

30323133

from google.api_core.gapic_v1 import client_info

3234

from google.auth.credentials import AnonymousCredentials

@@ -99,11 +101,50 @@ def _get_spanner_optimizer_statistics_package():

99101100102

log = logging.getLogger(__name__)

101103104+

_metrics_monitor_initialized = False

105+

_metrics_monitor_lock = threading.Lock()

106+102107103108

def _get_spanner_enable_builtin_metrics_env():

104109

return 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+107148

class 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

):

253294

warnings.warn(_EMULATOR_HOST_HTTP_SCHEME)

254-

# Check flag to enable Spanner builtin metrics

255295

if (

256296

_get_spanner_enable_builtin_metrics_env()

257297

and not disable_builtin_metrics

258298

and 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)

279301

else:

280302

SpannerMetricsTracerFactory(enabled=False)

281303