@@ -55,8 +55,12 @@ class Project
5555 DEFAULT_CMAB_CACHE_SIZE = 1000
5656
5757 # Class-level instance cache to prevent memory leaks from repeated initialization
58- @@instance_cache = { }
59- @@cache_mutex = Mutex . new
58+ @instance_cache = { }
59+ @cache_mutex = Mutex . new
60+
61+ class << self
62+ attr_accessor :instance_cache , :cache_mutex
63+ end
6064
6165 attr_reader :notification_center
6266 # @api no-doc
@@ -74,53 +78,51 @@ def self.get_or_create_instance(datafile: nil, **options)
7478 return new ( datafile : datafile , **options ) if should_skip_cache? ( datafile , options )
7579
7680 cache_key = generate_cache_key ( datafile , options )
77-
78- @@ cache_mutex. synchronize do
81+
82+ cache_mutex . synchronize do
7983 # Return existing instance if available and not stopped
80- if @@instance_cache [ cache_key ] && !@@instance_cache [ cache_key ] . stopped
81- return @@instance_cache [ cache_key ]
82- end
84+ return instance_cache [ cache_key ] if instance_cache [ cache_key ] && !instance_cache [ cache_key ] . stopped
8385
8486 # Create new instance and cache it
8587 instance = new ( datafile : datafile , **options )
86- @@ instance_cache[ cache_key ] = instance
88+ instance_cache [ cache_key ] = instance
8789 instance
8890 end
8991 end
9092
9193 # Clear all cached instances and properly close them
9294 def self . clear_instance_cache!
93- @@ cache_mutex. synchronize do
95+ cache_mutex . synchronize do
9496 # First stop all instances without removing them from cache to avoid deadlock
95- @@ instance_cache. each_value do |instance |
97+ instance_cache . each_value do |instance |
9698 next if instance . stopped
97-
99+
98100 instance . instance_variable_set ( :@stopped , true )
99101 instance . config_manager . stop! if instance . config_manager . respond_to? ( :stop! )
100102 instance . event_processor . stop! if instance . event_processor . respond_to? ( :stop! )
101103 instance . odp_manager . stop!
102104 end
103105 # Then clear the cache
104- @@ instance_cache. clear
106+ instance_cache . clear
105107 end
106108 end
107109
108110 # Get count of cached instances (for testing/monitoring)
109111 def self . cached_instance_count
110- @@ cache_mutex. synchronize { @@ instance_cache. size }
112+ cache_mutex . synchronize { instance_cache . size }
111113 end
112114
113115 private_class_method def self . should_skip_cache? ( datafile , options )
114116 # Don't cache if using dynamic features that would make sharing unsafe
115- return true if options [ :sdk_key ] ||
116- options [ :config_manager ] ||
117- options [ :event_processor ] ||
118- options [ :user_profile_service ] ||
119- datafile . nil? || datafile . empty?
120-
117+ return true if options [ :sdk_key ] ||
118+ options [ :config_manager ] ||
119+ options [ :event_processor ] ||
120+ options [ :user_profile_service ] ||
121+ datafile . nil? || datafile . empty?
122+
121123 # Also don't cache if custom loggers or error handlers that might have state
122124 return true if options [ :logger ] || options [ :error_handler ] || options [ :event_dispatcher ]
123-
125+
124126 false
125127 end
126128
@@ -240,7 +242,7 @@ def initialize(
240242 flush_interval : event_processor_options [ :flush_interval ] || BatchEventProcessor ::DEFAULT_BATCH_INTERVAL
241243 )
242244 end
243-
245+
244246 # Set up finalizer to ensure cleanup if close() is not called explicitly
245247 ObjectSpace . define_finalizer ( self , self . class . create_finalizer ( @config_manager , @event_processor , @odp_manager ) )
246248 end
@@ -249,13 +251,11 @@ def initialize(
249251 # This ensures cleanup even if close() is not explicitly called
250252 def self . create_finalizer ( config_manager , event_processor , odp_manager )
251253 proc do
252- begin
253- config_manager . stop! if config_manager . respond_to? ( :stop! )
254- event_processor . stop! if event_processor . respond_to? ( :stop! )
255- odp_manager . stop! if odp_manager . respond_to? ( :stop! )
256- rescue
257- # Suppress errors during finalization to avoid issues during GC
258- end
254+ config_manager . stop! if config_manager . respond_to? ( :stop! )
255+ event_processor . stop! if event_processor . respond_to? ( :stop! )
256+ odp_manager . stop! if odp_manager . respond_to? ( :stop! )
257+ rescue
258+ # Suppress errors during finalization to avoid issues during GC
259259 end
260260 end
261261
@@ -1030,14 +1030,14 @@ def close
10301030 @config_manager . stop! if @config_manager . respond_to? ( :stop! )
10311031 @event_processor . stop! if @event_processor . respond_to? ( :stop! )
10321032 @odp_manager . stop!
1033-
1033+
10341034 # Remove this instance from the cache if it exists
10351035 # Note: we don't synchronize here to avoid deadlock when called from clear_instance_cache!
10361036 self . class . send ( :remove_from_cache_unsafe , self )
10371037 end
10381038
10391039 private_class_method def self . remove_from_cache_unsafe ( instance )
1040- @@ instance_cache. delete_if { |_key , cached_instance | cached_instance == instance }
1040+ instance_cache . delete_if { |_key , cached_instance | cached_instance == instance }
10411041 end
10421042
10431043 def get_optimizely_config
0 commit comments