@@ -19,12 +19,13 @@ def initialize(
1919 broadcast_worker_count : SolidObjects . configuration . broadcast_worker_count ,
2020 reminder_scheduler_count : SolidObjects . configuration . reminder_scheduler_count
2121 )
22- @components = build_components (
22+ @builders = component_builders (
2323 worker_count :,
2424 effect_worker_count :,
2525 broadcast_worker_count :,
2626 reminder_scheduler_count :
2727 )
28+ @components = build_all ( @builders )
2829 @threads = [ ]
2930 @monitor = nil
3031 @started = false
@@ -77,7 +78,7 @@ def stop
7778
7879 private
7980
80- attr_reader :components , :threads
81+ attr_reader :components , :threads , :builders
8182
8283 # A role that raises leaves its thread dead. Without replacement the
8384 # process keeps running while quietly doing less work, so the supervisor
@@ -116,7 +117,11 @@ def replace_dead_roles
116117 replaced = @lifecycle . synchronize do
117118 next false unless @started
118119
119- replacement = component . class . new
120+ # A component built by this supervisor has a builder, which carries
121+ # whatever the constructor was given. A component put in place by
122+ # other means has none, so the class is the only thing left to go on.
123+ builder = builders [ index ] || -> { component . class . new }
124+ replacement = builder . call
120125 components [ index ] = replacement
121126 threads [ index ] = supervise ( replacement )
122127 replacement
@@ -250,17 +255,53 @@ def release_wake_up
250255 nil
251256 end
252257
253- # @rbs (worker_count: Integer, effect_worker_count: Integer, broadcast_worker_count: Integer, reminder_scheduler_count: Integer) -> Array[Worker | EffectExecutor | ReminderScheduler | BroadcastExecutor]
254- def build_components (
258+ # A constructor can take a resource, and a later builder can raise. Without
259+ # this, the components built first would be dropped while still holding
260+ # whatever they took, and nothing would ever give it back.
261+ # @rbs (Array[^() -> untyped]) -> Array[untyped]
262+ def build_all ( builders )
263+ built = [ ]
264+ builders . each do |builder |
265+ # The component joins the list before the contract check, so a
266+ # component that fails the check is stopped along with the rest.
267+ built << ( component = builder . call )
268+ SolidObjects . configuration . validate_component! ( component )
269+ end
270+ built
271+ rescue Exception # rubocop:disable Lint/RescueException
272+ built . each { |component | stop_after_failed_build ( component ) }
273+ raise
274+ end
275+
276+ # The failure that stopped the build is the one worth reporting, so a
277+ # failure inside the cleanup never replaces it.
278+ # @rbs (untyped) -> void
279+ def stop_after_failed_build ( component )
280+ component . stop if component . respond_to? ( :stop )
281+ rescue Exception => error # rubocop:disable Lint/RescueException
282+ SolidObjects . instrument (
283+ :"supervisor.component_cleanup_failed" ,
284+ role : component . class . name ,
285+ error_class : error . class . name
286+ )
287+ end
288+
289+ # Each component keeps the builder that made it, so a replacement after a
290+ # crash is built the same way as the original. Components registered
291+ # through the configuration run beside the built in ones, under the same
292+ # supervision, restart, and shutdown timeout.
293+ # @rbs (worker_count: Integer, effect_worker_count: Integer, broadcast_worker_count: Integer, reminder_scheduler_count: Integer) -> Array[^() -> untyped]
294+ def component_builders (
255295 worker_count :,
256296 effect_worker_count :,
257297 broadcast_worker_count :,
258298 reminder_scheduler_count :
259299 )
260- Array . new ( worker_count ) { Worker . new } +
261- Array . new ( effect_worker_count ) { EffectExecutor . new } +
262- Array . new ( broadcast_worker_count ) { BroadcastExecutor . new } +
263- Array . new ( reminder_scheduler_count ) { ReminderScheduler . new }
300+ Array . new ( worker_count ) { -> { Worker . new } } +
301+ Array . new ( effect_worker_count ) { -> { EffectExecutor . new } } +
302+ Array . new ( broadcast_worker_count ) { -> { BroadcastExecutor . new } } +
303+ Array . new ( reminder_scheduler_count ) { -> { ReminderScheduler . new } } +
304+ SolidObjects . configuration . additional_components
264305 end
265306
266307 # @rbs () -> void
0 commit comments