77
88from azure .monitor .opentelemetry .exporter ._constants import (
99 _ONE_SETTINGS_DEFAULT_REFRESH_INTERVAL_SECONDS ,
10- _ONE_SETTINGS_PYTHON_KEY ,
1110 _ONE_SETTINGS_CHANGE_URL ,
1211 _ONE_SETTINGS_CONFIG_URL ,
1312)
1413from azure .monitor .opentelemetry .exporter ._configuration ._utils import make_onesettings_request
14+ from azure .monitor .opentelemetry .exporter ._utils import Singleton
1515
1616# Set up logger
1717logger = logging .getLogger (__name__ )
@@ -35,34 +35,42 @@ def with_updates(self, **kwargs) -> '_ConfigurationState': # pylint: disable=C4
3535 )
3636
3737
38- class _ConfigurationManager :
38+ class _ConfigurationManager ( metaclass = Singleton ) :
3939 """Singleton class to manage configuration settings."""
4040
41- _instance = None
42- _configuration_worker = None
43- _instance_lock = Lock ()
44- _state_lock = Lock () # Single lock for all state
45- _current_state = _ConfigurationState ()
41+ def __init__ (self ):
42+ """Initialize the ConfigurationManager instance."""
4643
47- def __new__ (cls ):
48- with cls ._instance_lock :
49- if cls ._instance is None :
50- cls ._instance = super (_ConfigurationManager , cls ).__new__ (cls )
51- # Initialize the instance here to avoid re-initialization
52- cls ._instance ._initialize_worker ()
53- return cls ._instance
44+ self ._configuration_worker = None
45+ self ._state_lock = Lock () # Single lock for all state
46+ self ._current_state = _ConfigurationState ()
47+ self ._callbacks = []
48+ self ._initialize_worker ()
5449
5550 def _initialize_worker (self ):
5651 """Initialize the ConfigurationManager and start the configuration worker."""
5752 # Lazy import to avoid circular import
5853 from azure .monitor .opentelemetry .exporter ._configuration ._worker import _ConfigurationWorker
5954
6055 # Get initial refresh interval from state
61- with _ConfigurationManager ._state_lock :
62- initial_refresh_interval = _ConfigurationManager ._current_state .refresh_interval
56+ with self ._state_lock :
57+ initial_refresh_interval = self ._current_state .refresh_interval
6358
64- self ._configuration_worker = _ConfigurationWorker (initial_refresh_interval )
59+ self ._configuration_worker = _ConfigurationWorker (self , initial_refresh_interval )
6560
61+ def register_callback (self , callback ):
62+ # Register a callback to be invoked when configuration changes.
63+ self ._callbacks .append (callback )
64+
65+ def _notify_callbacks (self , settings : Dict [str , str ]):
66+ # Notify all registered callbacks of configuration changes.
67+ for cb in self ._callbacks :
68+ try :
69+ cb (settings )
70+ except Exception as ex : # pylint: disable=broad-except
71+ logger .warning ("Callback failed: %s" , ex )
72+
73+ # pylint: disable=too-many-statements
6674 def get_configuration_and_refresh_interval (self , query_dict : Optional [Dict [str , str ]] = None ) -> int :
6775 """Fetch configuration from OneSettings and update local cache atomically.
6876
@@ -122,8 +130,8 @@ def get_configuration_and_refresh_interval(self, query_dict: Optional[Dict[str,
122130 headers = {}
123131
124132 # Read current state atomically
125- with _ConfigurationManager ._state_lock :
126- current_state = _ConfigurationManager ._current_state
133+ with self ._state_lock :
134+ current_state = self ._current_state
127135 if current_state .etag :
128136 headers ["If-None-Match" ] = current_state .etag
129137 if current_state .refresh_interval :
@@ -145,8 +153,8 @@ def get_configuration_and_refresh_interval(self, query_dict: Optional[Dict[str,
145153 # Handle version and settings updates
146154 elif response .settings and response .version is not None :
147155 needs_config_fetch = False
148- with _ConfigurationManager ._state_lock :
149- current_state = _ConfigurationManager ._current_state
156+ with self ._state_lock :
157+ current_state = self ._current_state
150158
151159 if response .version > current_state .version_cache :
152160 # Version increase: new config available
@@ -182,34 +190,41 @@ def get_configuration_and_refresh_interval(self, query_dict: Optional[Dict[str,
182190 # No settings or version provided
183191 logger .warning ("No settings or version provided in config response. Config not updated." )
184192
193+
194+ notify_callbacks = False
195+ current_refresh_interval = _ONE_SETTINGS_DEFAULT_REFRESH_INTERVAL_SECONDS
196+ state_for_callbacks = None
197+
185198 # Atomic state update
186- with _ConfigurationManager ._state_lock :
187- latest_state = _ConfigurationManager ._current_state # Always use latest state
188- _ConfigurationManager ._current_state = latest_state .with_updates (** new_state_updates )
189- return _ConfigurationManager ._current_state .refresh_interval
199+ with self ._state_lock :
200+ latest_state = self ._current_state # Always use latest state
201+ self ._current_state = latest_state .with_updates (** new_state_updates )
202+ current_refresh_interval = self ._current_state .refresh_interval
203+ if 'settings_cache' in new_state_updates :
204+ notify_callbacks = True
205+ state_for_callbacks = self ._current_state
206+
207+ # Handle configuration updates throughout the SDK
208+ if notify_callbacks and state_for_callbacks is not None and state_for_callbacks .settings_cache :
209+ self ._notify_callbacks (state_for_callbacks .settings_cache )
210+
211+ return current_refresh_interval
190212
191213 def get_settings (self ) -> Dict [str , str ]: # pylint: disable=C4741,C4742
192214 """Get current settings cache."""
193- with _ConfigurationManager ._state_lock :
194- return _ConfigurationManager ._current_state .settings_cache .copy ()
215+ with self ._state_lock :
216+ return self ._current_state .settings_cache .copy () # type: ignore
195217
196218 def get_current_version (self ) -> int : # pylint: disable=C4741,C4742
197219 """Get current version."""
198- with _ConfigurationManager ._state_lock :
199- return _ConfigurationManager ._current_state .version_cache
220+ with self ._state_lock :
221+ return self ._current_state .version_cache # type: ignore
200222
201223 def shutdown (self ) -> None :
202224 """Shutdown the configuration worker."""
203- with _ConfigurationManager ._instance_lock :
204- if self ._configuration_worker :
205- self ._configuration_worker .shutdown ()
206- self ._configuration_worker = None
207- if _ConfigurationManager ._instance :
208- _ConfigurationManager ._instance = None
209-
210-
211- def _update_configuration_and_get_refresh_interval () -> int :
212- targeting = {
213- "namespaces" : _ONE_SETTINGS_PYTHON_KEY ,
214- }
215- return _ConfigurationManager ().get_configuration_and_refresh_interval (targeting )
225+ if self ._configuration_worker :
226+ self ._configuration_worker .shutdown ()
227+ self ._configuration_worker = None
228+ # Clear the singleton instance from the metaclass
229+ if self .__class__ in Singleton ._instances : # pylint: disable=protected-access
230+ del Singleton ._instances [self .__class__ ] # pylint: disable=protected-access
0 commit comments