@@ -55,8 +55,12 @@ class Project
55
55
DEFAULT_CMAB_CACHE_SIZE = 1000
56
56
57
57
# 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
60
64
61
65
attr_reader :notification_center
62
66
# @api no-doc
@@ -74,53 +78,51 @@ def self.get_or_create_instance(datafile: nil, **options)
74
78
return new ( datafile : datafile , **options ) if should_skip_cache? ( datafile , options )
75
79
76
80
cache_key = generate_cache_key ( datafile , options )
77
-
78
- @@ cache_mutex. synchronize do
81
+
82
+ cache_mutex . synchronize do
79
83
# 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
83
85
84
86
# Create new instance and cache it
85
87
instance = new ( datafile : datafile , **options )
86
- @@ instance_cache[ cache_key ] = instance
88
+ instance_cache [ cache_key ] = instance
87
89
instance
88
90
end
89
91
end
90
92
91
93
# Clear all cached instances and properly close them
92
94
def self . clear_instance_cache!
93
- @@ cache_mutex. synchronize do
95
+ cache_mutex . synchronize do
94
96
# 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 |
96
98
next if instance . stopped
97
-
99
+
98
100
instance . instance_variable_set ( :@stopped , true )
99
101
instance . config_manager . stop! if instance . config_manager . respond_to? ( :stop! )
100
102
instance . event_processor . stop! if instance . event_processor . respond_to? ( :stop! )
101
103
instance . odp_manager . stop!
102
104
end
103
105
# Then clear the cache
104
- @@ instance_cache. clear
106
+ instance_cache . clear
105
107
end
106
108
end
107
109
108
110
# Get count of cached instances (for testing/monitoring)
109
111
def self . cached_instance_count
110
- @@ cache_mutex. synchronize { @@ instance_cache. size }
112
+ cache_mutex . synchronize { instance_cache . size }
111
113
end
112
114
113
115
private_class_method def self . should_skip_cache? ( datafile , options )
114
116
# 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
+
121
123
# Also don't cache if custom loggers or error handlers that might have state
122
124
return true if options [ :logger ] || options [ :error_handler ] || options [ :event_dispatcher ]
123
-
125
+
124
126
false
125
127
end
126
128
@@ -240,7 +242,7 @@ def initialize(
240
242
flush_interval : event_processor_options [ :flush_interval ] || BatchEventProcessor ::DEFAULT_BATCH_INTERVAL
241
243
)
242
244
end
243
-
245
+
244
246
# Set up finalizer to ensure cleanup if close() is not called explicitly
245
247
ObjectSpace . define_finalizer ( self , self . class . create_finalizer ( @config_manager , @event_processor , @odp_manager ) )
246
248
end
@@ -249,13 +251,11 @@ def initialize(
249
251
# This ensures cleanup even if close() is not explicitly called
250
252
def self . create_finalizer ( config_manager , event_processor , odp_manager )
251
253
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
259
259
end
260
260
end
261
261
@@ -1030,14 +1030,14 @@ def close
1030
1030
@config_manager . stop! if @config_manager . respond_to? ( :stop! )
1031
1031
@event_processor . stop! if @event_processor . respond_to? ( :stop! )
1032
1032
@odp_manager . stop!
1033
-
1033
+
1034
1034
# Remove this instance from the cache if it exists
1035
1035
# Note: we don't synchronize here to avoid deadlock when called from clear_instance_cache!
1036
1036
self . class . send ( :remove_from_cache_unsafe , self )
1037
1037
end
1038
1038
1039
1039
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 }
1041
1041
end
1042
1042
1043
1043
def get_optimizely_config
0 commit comments