finite_machine 0.11.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +564 -569
  4. data/Rakefile +5 -1
  5. data/benchmarks/memory_profile.rb +11 -0
  6. data/benchmarks/memory_usage.rb +16 -9
  7. data/finite_machine.gemspec +10 -3
  8. data/lib/finite_machine.rb +34 -46
  9. data/lib/finite_machine/async_call.rb +5 -21
  10. data/lib/finite_machine/callable.rb +4 -4
  11. data/lib/finite_machine/catchable.rb +4 -2
  12. data/lib/finite_machine/choice_merger.rb +19 -19
  13. data/lib/finite_machine/const.rb +16 -0
  14. data/lib/finite_machine/definition.rb +2 -2
  15. data/lib/finite_machine/dsl.rb +66 -149
  16. data/lib/finite_machine/env.rb +4 -2
  17. data/lib/finite_machine/event_definition.rb +7 -15
  18. data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
  19. data/lib/finite_machine/hook_event.rb +60 -61
  20. data/lib/finite_machine/hooks.rb +44 -36
  21. data/lib/finite_machine/listener.rb +2 -2
  22. data/lib/finite_machine/logger.rb +5 -4
  23. data/lib/finite_machine/message_queue.rb +39 -30
  24. data/lib/finite_machine/observer.rb +55 -37
  25. data/lib/finite_machine/safety.rb +12 -10
  26. data/lib/finite_machine/state_definition.rb +3 -5
  27. data/lib/finite_machine/state_machine.rb +83 -64
  28. data/lib/finite_machine/state_parser.rb +51 -79
  29. data/lib/finite_machine/subscribers.rb +1 -1
  30. data/lib/finite_machine/threadable.rb +3 -1
  31. data/lib/finite_machine/transition.rb +30 -31
  32. data/lib/finite_machine/transition_builder.rb +23 -32
  33. data/lib/finite_machine/transition_event.rb +12 -11
  34. data/lib/finite_machine/two_phase_lock.rb +3 -1
  35. data/lib/finite_machine/undefined_transition.rb +5 -6
  36. data/lib/finite_machine/version.rb +2 -2
  37. data/spec/integration/system_spec.rb +36 -38
  38. data/spec/performance/benchmark_spec.rb +13 -21
  39. data/spec/unit/alias_target_spec.rb +22 -41
  40. data/spec/unit/async_callbacks_spec.rb +8 -13
  41. data/spec/unit/auto_methods_spec.rb +44 -0
  42. data/spec/unit/callable/call_spec.rb +1 -3
  43. data/spec/unit/callbacks_spec.rb +372 -463
  44. data/spec/unit/can_spec.rb +13 -23
  45. data/spec/unit/cancel_callbacks_spec.rb +46 -0
  46. data/spec/unit/choice_spec.rb +105 -141
  47. data/spec/unit/define_spec.rb +31 -31
  48. data/spec/unit/definition_spec.rb +24 -41
  49. data/spec/unit/event_names_spec.rb +6 -10
  50. data/spec/unit/events_map/add_spec.rb +23 -0
  51. data/spec/unit/events_map/choice_transition_spec.rb +25 -0
  52. data/spec/unit/events_map/clear_spec.rb +13 -0
  53. data/spec/unit/events_map/events_spec.rb +16 -0
  54. data/spec/unit/events_map/inspect_spec.rb +22 -0
  55. data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
  56. data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
  57. data/spec/unit/events_map/states_for_spec.rb +17 -0
  58. data/spec/unit/events_spec.rb +91 -160
  59. data/spec/unit/handlers_spec.rb +34 -66
  60. data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
  61. data/spec/unit/hook_event/build_spec.rb +1 -3
  62. data/spec/unit/hook_event/eql_spec.rb +1 -3
  63. data/spec/unit/hook_event/initialize_spec.rb +2 -4
  64. data/spec/unit/hook_event/notify_spec.rb +2 -4
  65. data/spec/unit/hooks/clear_spec.rb +1 -1
  66. data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
  67. data/spec/unit/hooks/inspect_spec.rb +16 -8
  68. data/spec/unit/hooks/register_spec.rb +4 -9
  69. data/spec/unit/if_unless_spec.rb +76 -115
  70. data/spec/unit/initial_spec.rb +50 -82
  71. data/spec/unit/inspect_spec.rb +14 -9
  72. data/spec/unit/is_spec.rb +12 -18
  73. data/spec/unit/log_transitions_spec.rb +4 -10
  74. data/spec/unit/logger_spec.rb +1 -3
  75. data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
  76. data/spec/unit/new_spec.rb +50 -0
  77. data/spec/unit/respond_to_spec.rb +2 -6
  78. data/spec/unit/state_parser/parse_spec.rb +9 -12
  79. data/spec/unit/states_spec.rb +12 -18
  80. data/spec/unit/subscribers_spec.rb +1 -3
  81. data/spec/unit/target_spec.rb +60 -93
  82. data/spec/unit/terminated_spec.rb +15 -25
  83. data/spec/unit/transition/check_conditions_spec.rb +16 -15
  84. data/spec/unit/transition/inspect_spec.rb +6 -6
  85. data/spec/unit/transition/matches_spec.rb +5 -7
  86. data/spec/unit/transition/states_spec.rb +5 -7
  87. data/spec/unit/transition/to_state_spec.rb +5 -13
  88. data/spec/unit/trigger_spec.rb +5 -9
  89. data/spec/unit/undefined_transition/eql_spec.rb +1 -3
  90. metadata +86 -49
  91. data/.gitignore +0 -18
  92. data/.rspec +0 -5
  93. data/.travis.yml +0 -27
  94. data/Gemfile +0 -16
  95. data/assets/finite_machine_logo.png +0 -0
  96. data/lib/finite_machine/async_proxy.rb +0 -55
  97. data/spec/unit/async_events_spec.rb +0 -107
  98. data/spec/unit/events_chain/add_spec.rb +0 -25
  99. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  100. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  101. data/spec/unit/events_chain/clear_spec.rb +0 -15
  102. data/spec/unit/events_chain/events_spec.rb +0 -18
  103. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  104. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  105. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  106. data/spec/unit/state_parser/inspect_spec.rb +0 -25
@@ -1,92 +1,93 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ require_relative 'hook_event'
2
6
 
3
7
  module FiniteMachine
4
8
  # A class reponsible for registering callbacks
5
9
  class Hooks
6
- include Threadable
7
-
8
- attr_threadsafe :collection
10
+ attr_reader :hooks_map
9
11
 
10
- # Initialize a collection of hoooks
12
+ # Initialize a hooks_map of hooks
11
13
  #
12
14
  # @example
13
15
  # Hoosk.new(machine)
14
16
  #
15
17
  # @api public
16
18
  def initialize
17
- @collection = Hash.new do |events_hash, event_type|
18
- events_hash[event_type] = Hash.new do |state_hash, name|
19
+ @hooks_map = Concurrent::Map.new do |events_hash, hook_event|
20
+ events_hash[hook_event] = Concurrent::Map.new do |state_hash, name|
19
21
  state_hash[name] = []
20
22
  end
21
23
  end
22
24
  end
23
25
 
24
- # Register callback
26
+ # Finds all hooks for the event type
25
27
  #
26
- # @param [String] event_type
27
- # @param [String] name
28
- # @param [Proc] callback
28
+ # @param [Symbol] name
29
29
  #
30
30
  # @example
31
- # hooks.register HookEvent::Enter, :green do ... end
31
+ # hooks[HookEvent::Enter][:go] # => [-> { }]
32
32
  #
33
- # @example
34
- # hooks.register HookEvent::Before, :any do ... end
35
- #
36
- # @return [Hash]
33
+ # @return [Array[Transition]]
34
+ # the transitions matching event name
37
35
  #
38
36
  # @api public
39
- def register(event_type, name, callback)
40
- collection[event_type][name] << callback
37
+ def find(name)
38
+ @hooks_map[name]
41
39
  end
40
+ alias [] find
42
41
 
43
- # Unregister callback
42
+ # Register callback
44
43
  #
45
- # @param [String] event_type
44
+ # @param [String] hook_event
46
45
  # @param [String] name
47
46
  # @param [Proc] callback
48
47
  #
49
48
  # @example
50
- # hooks.unregister HookEvent::Enter, :green do ... end
49
+ # hooks.register HookEvent::Enter, :green do ... end
50
+ #
51
+ # @example
52
+ # hooks.register HookEvent::Before, any_state do ... end
51
53
  #
52
54
  # @return [Hash]
53
55
  #
54
56
  # @api public
55
- def unregister(event_type, name, callback)
56
- callbacks = collection[event_type][name]
57
- callbacks.delete(callback)
57
+ def register(hook_event, name, callback)
58
+ @hooks_map[hook_event][name] << callback
58
59
  end
59
60
 
60
- # Return all hooks matching event and state
61
+ # Unregister callback
61
62
  #
62
- # @param [String] event_type
63
- # @param [String] event_state
64
- # @param [Event] event
63
+ # @param [String] hook_event
64
+ # @param [String] name
65
+ # @param [Proc] callback
65
66
  #
66
67
  # @example
67
- # hooks.call(HookEvent::Enter, :green, Event.new)
68
+ # hooks.unregister HookEvent::Enter, :green do ... end
68
69
  #
69
70
  # @return [Hash]
70
71
  #
71
72
  # @api public
72
- def call(event_type, event_state, &block)
73
- collection[event_type][event_state].each(&block)
73
+ def unregister(hook_event, name, callback)
74
+ @hooks_map[hook_event][name].delete(callback)
74
75
  end
75
76
 
76
- # Check if collection has any elements
77
+ # Check if hooks_map has any elements
77
78
  #
78
79
  # @return [Boolean]
79
80
  #
80
81
  # @api public
81
82
  def empty?
82
- collection.empty?
83
+ @hooks_map.empty?
83
84
  end
84
85
 
85
86
  # Remove all callbacks
86
87
  #
87
88
  # @api public
88
89
  def clear
89
- collection.clear
90
+ @hooks_map.clear
90
91
  end
91
92
 
92
93
  # String representation
@@ -95,7 +96,14 @@ module FiniteMachine
95
96
  #
96
97
  # @api public
97
98
  def to_s
98
- self.inspect
99
+ hash = {}
100
+ @hooks_map.each_pair do |hook_event, nested_hash|
101
+ hash[hook_event] = {}
102
+ nested_hash.each_pair do |name, callbacks|
103
+ hash[hook_event][name] = callbacks
104
+ end
105
+ end
106
+ hash.to_s
99
107
  end
100
108
 
101
109
  # String representation
@@ -104,7 +112,7 @@ module FiniteMachine
104
112
  #
105
113
  # @api public
106
114
  def inspect
107
- "<##{self.class}:0x#{object_id.to_s(16)} @collection=#{collection.inspect}>"
115
+ "<##{self.class}:0x#{object_id.to_s(16)} @hooks_map=#{self}>"
108
116
  end
109
117
  end # Hooks
110
118
  end # FiniteMachine
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  # A generic listener interface
@@ -24,6 +24,6 @@ module FiniteMachine
24
24
  def call(*args)
25
25
  @on_delivery.call(*args) if @on_delivery
26
26
  end
27
- alias_method :handle_delivery, :call
27
+ alias handle_delivery call
28
28
  end # Listener
29
29
  end # FiniteMachine
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  module Logger
@@ -21,21 +21,22 @@ module FiniteMachine
21
21
  end
22
22
 
23
23
  def format_error(error)
24
- message = "#{error.class}: #{error.message}\n\t"
24
+ message = ["#{error.class}: #{error.message}\n\t"]
25
25
  if error.backtrace
26
26
  message << "occured at #{error.backtrace.join("\n\t")}"
27
27
  else
28
28
  message << "EMPTY BACKTRACE\n\t"
29
29
  end
30
+ message.join
30
31
  end
31
32
 
32
33
  def report_transition(name, from, to, *args)
33
- message = "Transition: @event=#{name} "
34
+ message = ["Transition: @event=#{name} "]
34
35
  unless args.empty?
35
36
  message << "@with=[#{args.join(',')}] "
36
37
  end
37
38
  message << "#{from} -> #{to}"
38
- info(message)
39
+ info(message.join)
39
40
  end
40
41
  end # Logger
41
42
  end # FiniteMachine
@@ -1,4 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'listener'
4
+ require 'thread'
2
5
 
3
6
  module FiniteMachine
4
7
  # Allows for storage of asynchronous messages such as events
@@ -8,15 +11,15 @@ module FiniteMachine
8
11
  #
9
12
  # @api private
10
13
  class MessageQueue
11
- include Threadable
12
-
13
- # Initialize an event queue
14
+ # Initialize an event queue in separate thread
14
15
  #
15
16
  # @example
16
17
  # MessageQueue.new
17
18
  #
18
19
  # @api public
19
20
  def initialize
21
+ @not_empty = ConditionVariable.new
22
+ @mutex = Mutex.new
20
23
  @queue = Queue.new
21
24
  @dead = false
22
25
  @listeners = []
@@ -28,7 +31,13 @@ module FiniteMachine
28
31
  # @api private
29
32
  def start
30
33
  return if running?
34
+ @mutex.synchronize { spawn_thread }
35
+ end
31
36
 
37
+ # Spawn new background thread
38
+ #
39
+ # @api private
40
+ def spawn_thread
32
41
  @thread = Thread.new do
33
42
  Thread.current.abort_on_exception = true
34
43
  process_events
@@ -39,16 +48,7 @@ module FiniteMachine
39
48
  !@thread.nil? && alive?
40
49
  end
41
50
 
42
- # Retrieve the next event
43
- #
44
- # @return [AsyncCall]
45
- #
46
- # @api private
47
- def next_event
48
- sync_shared { @queue.pop }
49
- end
50
-
51
- # Add asynchronous event to the event queue
51
+ # Add asynchronous event to the event queue to process
52
52
  #
53
53
  # @example
54
54
  # event_queue << AsyncCall.build(...)
@@ -59,21 +59,21 @@ module FiniteMachine
59
59
  #
60
60
  # @api public
61
61
  def <<(event)
62
- sync_exclusive do
62
+ @mutex.synchronize do
63
63
  if @dead
64
64
  discard_message(event)
65
65
  else
66
66
  @queue << event
67
+ @not_empty.signal
67
68
  end
68
69
  end
69
- self
70
70
  end
71
71
 
72
72
  # Add listener to the queue to receive messages
73
73
  #
74
74
  # @api public
75
75
  def subscribe(*args, &block)
76
- sync_exclusive do
76
+ @mutex.synchronize do
77
77
  listener = Listener.new(*args)
78
78
  listener.on_delivery(&block)
79
79
  @listeners << listener
@@ -87,7 +87,7 @@ module FiniteMachine
87
87
  #
88
88
  # @api public
89
89
  def empty?
90
- sync_shared { @queue.empty? }
90
+ @mutex.synchronize { @queue.empty? }
91
91
  end
92
92
 
93
93
  # Check if the event queue is alive
@@ -99,7 +99,7 @@ module FiniteMachine
99
99
  #
100
100
  # @api public
101
101
  def alive?
102
- sync_shared { !@dead }
102
+ @mutex.synchronize { !@dead }
103
103
  end
104
104
 
105
105
  # Join the event queue from current thread
@@ -129,10 +129,12 @@ module FiniteMachine
129
129
  fail EventQueueDeadError, 'event queue already dead' if @dead
130
130
 
131
131
  queue = []
132
- sync_exclusive do
132
+ @mutex.synchronize do
133
+ @dead = true
134
+ @not_empty.broadcast
135
+
133
136
  queue = @queue
134
137
  @queue.clear
135
- @dead = true
136
138
  end
137
139
  while !queue.empty?
138
140
  discard_message(queue.pop)
@@ -149,24 +151,24 @@ module FiniteMachine
149
151
  #
150
152
  # @api public
151
153
  def size
152
- sync_shared { @queue.size }
154
+ @mutex.synchronize { @queue.size }
153
155
  end
154
156
 
155
157
  def inspect
156
- "#<#{self.class}:#{object_id.to_s(16)} @size=#{size}, @dead=#{@dead}>"
158
+ @mutex.synchronize do
159
+ "#<#{self.class}:#{object_id.to_s(16)} @size=#{size}, @dead=#{@dead}>"
160
+ end
157
161
  end
158
162
 
159
163
  private
160
164
 
161
165
  # Notify consumers about process event
162
166
  #
163
- # @param [FiniteMachine::AsyncCall] event
167
+ # @param [AsyncCall] event
164
168
  #
165
169
  # @api private
166
170
  def notify_listeners(event)
167
- sync_shared do
168
- @listeners.each { |listener| listener.handle_delivery(event) }
169
- end
171
+ @listeners.each { |listener| listener.handle_delivery(event) }
170
172
  end
171
173
 
172
174
  # Process all the events
@@ -176,9 +178,16 @@ module FiniteMachine
176
178
  # @api private
177
179
  def process_events
178
180
  until @dead
179
- event = next_event
180
- notify_listeners(event)
181
- event.dispatch
181
+ @mutex.synchronize do
182
+ while @queue.empty?
183
+ break if @dead
184
+ @not_empty.wait(@mutex)
185
+ end
186
+ event = @queue.pop
187
+ break unless event
188
+ notify_listeners(event)
189
+ event.dispatch
190
+ end
182
191
  end
183
192
  rescue Exception => ex
184
193
  Logger.error "Error while running event: #{Logger.format_error(ex)}"
@@ -1,18 +1,37 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'finite_machine/hooks'
3
+ require_relative 'async_call'
4
+ require_relative 'callable'
5
+ require_relative 'hook_event'
6
+ require_relative 'hooks'
7
+ require_relative 'message_queue'
8
+ require_relative 'safety'
9
+ require_relative 'transition_event'
4
10
 
5
11
  module FiniteMachine
6
12
  # A class responsible for observing state changes
7
- class Observer
8
- include Threadable
13
+ class Observer < GenericDSL
9
14
  include Safety
10
15
 
16
+ # Clean up callback queue
17
+ #
18
+ # @api private
19
+ def self.cleanup_callback_queue
20
+ proc do
21
+ begin
22
+ if callback_queue.alive?
23
+ callback_queue.shutdown
24
+ end
25
+ rescue MessageQueueDeadError
26
+ end
27
+ end
28
+ end
29
+
11
30
  # The current state machine
12
- attr_threadsafe :machine
31
+ attr_reader :machine
13
32
 
14
33
  # The hooks to trigger around the transition lifecycle.
15
- attr_threadsafe :hooks
34
+ attr_reader :hooks
16
35
 
17
36
  # Initialize an Observer
18
37
  #
@@ -21,12 +40,15 @@ module FiniteMachine
21
40
  #
22
41
  # @api public
23
42
  def initialize(machine)
24
- @machine = machine
25
- @hooks = FiniteMachine::Hooks.new
26
- @callback_queue = MessageQueue.new
43
+ @machine = machine
44
+ @hooks = Hooks.new
27
45
 
28
46
  @machine.subscribe(self)
29
- ObjectSpace.define_finalizer(self, self.class.cleanup(@callback_queue))
47
+ ObjectSpace.define_finalizer(self, self.class.cleanup_callback_queue)
48
+ end
49
+
50
+ def callback_queue
51
+ @callback_queue ||= MessageQueue.new
30
52
  end
31
53
 
32
54
  # Evaluate in current context
@@ -49,7 +71,7 @@ module FiniteMachine
49
71
  def on(hook_type, state_or_event_name = nil, async = nil, &callback)
50
72
  sync_exclusive do
51
73
  if state_or_event_name.nil?
52
- state_or_event_name = HookEvent.infer_default_name(hook_type)
74
+ state_or_event_name = HookEvent.any_state_or_event(hook_type)
53
75
  end
54
76
  async = false if async.nil?
55
77
  ensure_valid_callback_name!(hook_type, state_or_event_name)
@@ -124,8 +146,9 @@ module FiniteMachine
124
146
  def emit(event, *data)
125
147
  sync_exclusive do
126
148
  [event.type].each do |hook_type|
127
- [event.name, ANY_STATE, ANY_EVENT].each do |event_name|
128
- hooks.call(hook_type, event_name) do |hook|
149
+ any_state_or_event = HookEvent.any_state_or_event(hook_type)
150
+ [any_state_or_event, event.name].each do |event_name|
151
+ hooks[hook_type][event_name].each do |hook|
129
152
  handle_callback(hook, event, *data)
130
153
  off(hook_type, event_name, &hook) if hook.is_a?(Once)
131
154
  end
@@ -134,6 +157,19 @@ module FiniteMachine
134
157
  end
135
158
  end
136
159
 
160
+ # Cancel the current event
161
+ #
162
+ # This should be called inside a on_before or on_exit callbacks
163
+ # to prevent event transition.
164
+ #
165
+ # @param [String] msg
166
+ # the message used for failure
167
+ #
168
+ # @api public
169
+ def cancel_event(msg = nil)
170
+ raise CallbackError.new(msg)
171
+ end
172
+
137
173
  private
138
174
 
139
175
  # Handle callback and decide if run synchronously or asynchronously
@@ -148,20 +184,14 @@ module FiniteMachine
148
184
  #
149
185
  # @api private
150
186
  def handle_callback(hook, event, *data)
151
- to = machine.events_chain.move_to(event.event_name, event.from, *data)
152
- trans_event = TransitionEvent.new(event, to)
187
+ to = machine.events_map.move_to(event.event_name, event.from, *data)
188
+ trans_event = TransitionEvent.new(event.event_name, event.from, to)
153
189
  callable = create_callable(hook)
154
190
 
155
191
  if hook.is_a?(Async)
156
192
  defer(callable, trans_event, *data)
157
- result = nil
158
193
  else
159
- result = callable.call(trans_event, *data)
160
- end
161
-
162
- if result == CANCELLED
163
- hooks.clear
164
- machine.events_chain.cancel_transitions(event.event_name)
194
+ callable.(trans_event, *data)
165
195
  end
166
196
  end
167
197
 
@@ -170,8 +200,8 @@ module FiniteMachine
170
200
  # @api private
171
201
  def defer(callable, trans_event, *data)
172
202
  async_call = AsyncCall.new(machine, callable, trans_event, *data)
173
- @callback_queue.start unless @callback_queue.running?
174
- @callback_queue << async_call
203
+ callback_queue.start unless callback_queue.running?
204
+ callback_queue << async_call
175
205
  end
176
206
 
177
207
  # Create callable instance
@@ -191,7 +221,7 @@ module FiniteMachine
191
221
  #
192
222
  # @api private
193
223
  def callback_names
194
- machine.states + machine.event_names + [ANY_EVENT, ANY_STATE]
224
+ machine.states + machine.events + [ANY_EVENT, ANY_STATE]
195
225
  end
196
226
 
197
227
  # Forward the message to observer
@@ -225,17 +255,5 @@ module FiniteMachine
225
255
  *_, callback_name = *method_name.to_s.match(/^(\w*?on_\w+?)_(\w+)$/)
226
256
  callback_name && callback_names.include?(:"#{callback_name}")
227
257
  end
228
-
229
- # Clean up callback queue
230
- #
231
- # @api private
232
- def self.cleanup(queue)
233
- proc do
234
- begin
235
- queue && queue.shutdown
236
- rescue MessageQueueDeadError
237
- end
238
- end
239
- end
240
258
  end # Observer
241
259
  end # FiniteMachine