@@ -31,6 +31,10 @@ def initialize
3131 @client_handlers_mutex = Mutex . new
3232 end
3333
34+ def add_hooks ( *new_hooks )
35+ @hooks . concat ( new_hooks . flatten )
36+ end
37+
3438 def provider ( domain : nil )
3539 @providers [ domain ] || @providers [ nil ]
3640 end
@@ -85,9 +89,12 @@ def provider_tracked?(provider)
8589 end
8690
8791 def shutdown
88- providers_to_shutdown = @provider_mutex . synchronize { @providers . values . uniq }
92+ providers_to_shutdown = @provider_mutex . synchronize { @providers . values . uniq ( & :object_id ) }
8993
9094 providers_to_shutdown . each do |prov |
95+ # Spec 1.7.9: Set provider state to NOT_READY before shutdown
96+ @provider_state_registry . update_state_from_event ( prov , ProviderEvent ::PROVIDER_READY )
97+ @provider_state_registry . set_initial_state ( prov , ProviderState ::NOT_READY )
9198 prov . shutdown if prov . respond_to? ( :shutdown )
9299 rescue => e
93100 @logger &.warn ( "Error shutting down provider #{ prov &.class &.name || "unknown" } : #{ e . message } " )
@@ -113,28 +120,37 @@ def set_provider_internal(provider, domain:, wait_for_init:)
113120 # Capture evaluation context before acquiring mutex to prevent race conditions
114121 context_for_init = @evaluation_context
115122
116- old_provider , provider_to_init = nil
123+ old_provider = nil
124+ needs_init = false
125+ needs_shutdown = false
117126
118127 @provider_mutex . synchronize do
119128 old_provider = @providers [ domain ]
120129
121- # Remove old provider state to prevent memory leaks
122- @provider_state_registry . remove_provider ( old_provider )
123-
124130 new_providers = @providers . dup
125131 new_providers [ domain ] = provider
126132 @providers = new_providers
127133
128- @provider_state_registry . set_initial_state ( provider )
134+ # Spec 1.1.2.2: Only initialize if the provider is not already active
135+ # (i.e., not already bound to another domain)
136+ already_active = @providers . any? { |d , p | d != domain && p . equal? ( provider ) && @provider_state_registry . tracked? ( p ) }
137+ needs_init = !already_active
129138
130- provider . send ( :attach , self ) if provider . is_a? ( Provider ::EventEmitter )
139+ if needs_init
140+ @provider_state_registry . set_initial_state ( provider )
141+ provider . send ( :attach , self ) if provider . is_a? ( Provider ::EventEmitter )
142+ end
131143
132- provider_to_init = provider
144+ # Spec 1.1.2.3: Only shutdown old provider if it's no longer bound to any domain
145+ if old_provider && !old_provider . equal? ( provider )
146+ still_bound = @providers . any? { |_ , p | p . equal? ( old_provider ) }
147+ needs_shutdown = !still_bound
148+ @provider_state_registry . remove_provider ( old_provider ) unless still_bound
149+ end
133150 end
134151
135152 # Shutdown old provider outside mutex to avoid blocking other operations
136- # Only shutdown if it's a different provider to prevent race condition
137- if old_provider && old_provider != provider
153+ if needs_shutdown
138154 begin
139155 old_provider . shutdown if old_provider . respond_to? ( :shutdown )
140156 rescue => e
@@ -143,12 +159,17 @@ def set_provider_internal(provider, domain:, wait_for_init:)
143159 end
144160
145161 # Initialize provider outside the mutex to avoid blocking other operations
146- if wait_for_init
147- init_provider ( provider_to_init , context_for_init , raise_on_error : true )
148- else
149- Thread . new do
150- init_provider ( provider_to_init , context_for_init , raise_on_error : false )
162+ if needs_init
163+ if wait_for_init
164+ init_provider ( provider , context_for_init , raise_on_error : true )
165+ else
166+ Thread . new do
167+ init_provider ( provider , context_for_init , raise_on_error : false )
168+ end
151169 end
170+ elsif wait_for_init
171+ # Provider already active; no init needed but still dispatch READY
172+ dispatch_provider_event ( provider , ProviderEvent ::PROVIDER_READY )
152173 end
153174 end
154175
@@ -164,16 +185,22 @@ def init_provider(provider, context, raise_on_error: false)
164185
165186 dispatch_provider_event ( provider , ProviderEvent ::PROVIDER_READY )
166187 rescue => e
188+ # Spec 1.7.8: Propagate error code from provider if available
189+ error_code = if e . respond_to? ( :error_code ) && e . error_code
190+ e . error_code
191+ else
192+ Provider ::ErrorCode ::GENERAL
193+ end
194+
167195 dispatch_provider_event ( provider , ProviderEvent ::PROVIDER_ERROR ,
168- error_code : Provider :: ErrorCode :: GENERAL ,
196+ error_code : error_code ,
169197 message : e . message )
170198
171199 if raise_on_error
172- # Re-raise as ProviderInitializationError for synchronous callers
173200 raise ProviderInitializationError . new (
174201 "Provider #{ provider . class . name } initialization failed: #{ e . message } " ,
175202 provider :,
176- error_code : Provider :: ErrorCode :: GENERAL ,
203+ error_code : error_code ,
177204 original_error : e
178205 )
179206 end
0 commit comments