rsmp 0.37.0 → 0.39.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/workflows/rubocop.yaml +17 -0
  4. data/.gitignore +5 -6
  5. data/.rubocop.yml +69 -0
  6. data/.tool-versions +1 -1
  7. data/Gemfile +14 -1
  8. data/Gemfile.lock +64 -29
  9. data/Rakefile +3 -3
  10. data/lib/rsmp/cli.rb +148 -124
  11. data/lib/rsmp/collect/ack_collector.rb +8 -7
  12. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  13. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  14. data/lib/rsmp/collect/alarm_matcher.rb +6 -6
  15. data/lib/rsmp/collect/collector/logging.rb +18 -0
  16. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  17. data/lib/rsmp/collect/collector/status.rb +34 -0
  18. data/lib/rsmp/collect/collector.rb +69 -150
  19. data/lib/rsmp/collect/command_matcher.rb +19 -6
  20. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  21. data/lib/rsmp/collect/distributor.rb +14 -11
  22. data/lib/rsmp/collect/filter.rb +31 -15
  23. data/lib/rsmp/collect/matcher.rb +9 -13
  24. data/lib/rsmp/collect/queue.rb +7 -7
  25. data/lib/rsmp/collect/receiver.rb +11 -15
  26. data/lib/rsmp/collect/state_collector.rb +116 -77
  27. data/lib/rsmp/collect/status_collector.rb +6 -6
  28. data/lib/rsmp/collect/status_matcher.rb +15 -4
  29. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -38
  30. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  31. data/lib/rsmp/component/component_base.rb +88 -0
  32. data/lib/rsmp/component/component_proxy.rb +75 -0
  33. data/lib/rsmp/component/components.rb +62 -0
  34. data/lib/rsmp/convert/export/json_schema.rb +118 -110
  35. data/lib/rsmp/convert/import/yaml.rb +22 -18
  36. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +8 -11
  37. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +3 -1
  38. data/lib/rsmp/helpers/error.rb +72 -0
  39. data/lib/rsmp/helpers/inspect.rb +41 -0
  40. data/lib/rsmp/log/archive.rb +97 -0
  41. data/lib/rsmp/log/colorization.rb +41 -0
  42. data/lib/rsmp/log/filtering.rb +54 -0
  43. data/lib/rsmp/log/logger.rb +207 -0
  44. data/lib/rsmp/{logging.rb → log/logging.rb} +6 -7
  45. data/lib/rsmp/message.rb +185 -148
  46. data/lib/rsmp/{node.rb → node/node.rb} +20 -19
  47. data/lib/rsmp/{protocol.rb → node/protocol.rb} +6 -3
  48. data/lib/rsmp/node/site/site.rb +192 -0
  49. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  50. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  51. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  52. data/lib/rsmp/node/supervisor/supervisor.rb +69 -0
  53. data/lib/rsmp/{task.rb → node/task.rb} +13 -14
  54. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  55. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  56. data/lib/rsmp/proxy/modules/send.rb +76 -0
  57. data/lib/rsmp/proxy/modules/state.rb +25 -0
  58. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  59. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  60. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  61. data/lib/rsmp/proxy/proxy.rb +197 -0
  62. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  63. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  64. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  65. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  66. data/lib/rsmp/proxy/site/site_proxy.rb +204 -0
  67. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  68. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  69. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  70. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  71. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +177 -0
  72. data/lib/rsmp/tlc/detector_logic.rb +19 -34
  73. data/lib/rsmp/tlc/input_states.rb +126 -0
  74. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  75. data/lib/rsmp/tlc/modules/display.rb +78 -0
  76. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  77. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  78. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  79. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  80. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  81. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  82. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  83. data/lib/rsmp/tlc/modules/system.rb +140 -0
  84. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  85. data/lib/rsmp/tlc/signal_group.rb +38 -41
  86. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  87. data/lib/rsmp/tlc/signal_priority.rb +40 -35
  88. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  89. data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
  90. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  91. data/lib/rsmp/version.rb +1 -1
  92. data/lib/rsmp.rb +82 -48
  93. data/rsmp.gemspec +24 -31
  94. metadata +79 -139
  95. data/lib/rsmp/archive.rb +0 -76
  96. data/lib/rsmp/collect/message_matchers.rb +0 -0
  97. data/lib/rsmp/component_base.rb +0 -87
  98. data/lib/rsmp/component_proxy.rb +0 -57
  99. data/lib/rsmp/components.rb +0 -65
  100. data/lib/rsmp/error.rb +0 -71
  101. data/lib/rsmp/inspect.rb +0 -46
  102. data/lib/rsmp/logger.rb +0 -216
  103. data/lib/rsmp/proxy.rb +0 -693
  104. data/lib/rsmp/site.rb +0 -188
  105. data/lib/rsmp/site_proxy.rb +0 -389
  106. data/lib/rsmp/supervisor.rb +0 -302
  107. data/lib/rsmp/supervisor_proxy.rb +0 -510
  108. data/lib/rsmp/tlc/inputs.rb +0 -134
@@ -1,4 +1,5 @@
1
1
  module RSMP
2
+ # Simple protocol wrapper for reading/writing RSMP framed messages.
2
3
  class Protocol
3
4
  def initialize(stream)
4
5
  @stream = stream
@@ -16,7 +17,7 @@ module RSMP
16
17
  end
17
18
 
18
19
  def peek_line
19
- @peeked = read unless @peeked
20
+ @peeked ||= read
20
21
  @peeked
21
22
  end
22
23
 
@@ -24,12 +25,14 @@ module RSMP
24
25
  @stream.write(data + RSMP::Proxy::WRAPPING_DELIMITER)
25
26
  @stream.flush unless @stream.closed?
26
27
  end
27
-
28
+
28
29
  protected
30
+
29
31
  def read
30
32
  line = @stream.gets(RSMP::Proxy::WRAPPING_DELIMITER)
31
33
  return nil unless line
34
+
32
35
  line.chomp(RSMP::Proxy::WRAPPING_DELIMITER)
33
36
  end
34
37
  end
35
- end
38
+ end
@@ -0,0 +1,192 @@
1
+ module RSMP
2
+ # RSMP site implementation that manages proxies and components.
3
+ class Site < Node
4
+ include Components
5
+
6
+ attr_reader :core_version, :site_settings, :logger, :proxies
7
+
8
+ def initialize(options = {})
9
+ super
10
+ initialize_components
11
+ handle_site_settings options
12
+ @proxies = []
13
+ @sleep_condition = Async::Notification.new
14
+ @proxies_condition = Async::Notification.new
15
+ build_proxies
16
+ end
17
+
18
+ def sxl_version
19
+ @site_settings['sxl_version']
20
+ end
21
+
22
+ def site_id
23
+ @site_settings['site_id']
24
+ end
25
+
26
+ def default_site_settings
27
+ {
28
+ 'site_id' => 'RN+SI0001',
29
+ 'supervisors' => [
30
+ { 'ip' => '127.0.0.1', 'port' => 12_111 }
31
+ ],
32
+ 'sxl' => 'tlc',
33
+ 'sxl_version' => RSMP::Schema.latest_version(:tlc),
34
+ 'intervals' => {
35
+ 'timer' => 0.1,
36
+ 'watchdog' => 1,
37
+ 'reconnect' => 0.1
38
+ },
39
+ 'timeouts' => {
40
+ 'watchdog' => 2,
41
+ 'acknowledgement' => 2
42
+ },
43
+ 'send_after_connect' => true,
44
+ 'components' => {
45
+ 'main' => {
46
+ 'C1' => {}
47
+ }
48
+ }
49
+ }
50
+ end
51
+
52
+ def handle_site_settings(options = {})
53
+ defaults = default_site_settings
54
+ defaults['components']['main'] = options[:site_settings]['components']['main'] if options.dig(
55
+ :site_settings, 'components', 'main'
56
+ )
57
+
58
+ @site_settings = defaults.deep_merge options[:site_settings]
59
+
60
+ check_sxl_version
61
+ check_core_versions
62
+ setup_components @site_settings['components']
63
+ end
64
+
65
+ def check_sxl_version
66
+ sxl = @site_settings['sxl']
67
+ version = @site_settings['sxl_version'].to_s
68
+ RSMP::Schema.find_schema! sxl, version, lenient: true
69
+ end
70
+
71
+ def check_core_versions
72
+ version = @site_settings['core_version']
73
+ return unless version
74
+
75
+ return if RSMP::Schema.core_versions.include? version
76
+
77
+ error_str = "Unknown core version: #{version}"
78
+ raise RSMP::ConfigurationError, error_str
79
+ end
80
+
81
+ def site_type_name
82
+ 'site'
83
+ end
84
+
85
+ def log_site_starting
86
+ log "Starting #{site_type_name} #{@site_settings['site_id']}", level: :info, timestamp: @clock.now
87
+ sxl = "Using #{@site_settings['sxl']} sxl #{@site_settings['sxl_version']}"
88
+ version = @site_settings['core_version']
89
+ core = if version
90
+ "accepting only core version #{version}"
91
+ else
92
+ "accepting all core versions [#{RSMP::Schema.core_versions.join(', ')}]"
93
+ end
94
+ log "#{sxl}, #{core}", level: :info, timestamp: @clock.now
95
+ end
96
+
97
+ def run
98
+ log_site_starting
99
+ @proxies.each do |proxy|
100
+ proxy.start
101
+ proxy.wait
102
+ end
103
+ end
104
+
105
+ def build_proxies
106
+ @site_settings['supervisors'].each do |supervisor_settings|
107
+ @proxies << SupervisorProxy.new({
108
+ site: self,
109
+ task: @task,
110
+ settings: @site_settings,
111
+ ip: supervisor_settings['ip'],
112
+ port: supervisor_settings['port'],
113
+ logger: @logger,
114
+ archive: @archive,
115
+ collect: @collect
116
+ })
117
+ end
118
+ end
119
+
120
+ def aggregated_status_changed(component, options = {})
121
+ @proxies.each do |proxy|
122
+ proxy.send_aggregated_status component, options if proxy.ready?
123
+ end
124
+ end
125
+
126
+ def alarm_acknowledged(alarm_state)
127
+ send_alarm AlarmAcknowledged.new(alarm_state.to_hash)
128
+ end
129
+
130
+ def alarm_suspended_or_resumed(alarm_state)
131
+ send_alarm AlarmSuspended.new(alarm_state.to_hash)
132
+ end
133
+
134
+ def alarm_activated_or_deactivated(alarm_state)
135
+ send_alarm AlarmIssue.new(alarm_state.to_hash)
136
+ end
137
+
138
+ def send_alarm(alarm)
139
+ @proxies.each do |proxy|
140
+ proxy.send_message alarm if proxy.ready?
141
+ end
142
+ end
143
+
144
+ def connect_to_supervisor(_task, supervisor_settings)
145
+ proxy = build_proxy({
146
+ site: self,
147
+ task: @task,
148
+ settings: @site_settings,
149
+ ip: supervisor_settings['ip'],
150
+ port: supervisor_settings['port'],
151
+ logger: @logger,
152
+ archive: @archive,
153
+ collect: @collect
154
+ })
155
+ @proxies << proxy
156
+ proxy.start
157
+ @proxies_condition.signal
158
+ end
159
+
160
+ # stop
161
+ def stop
162
+ log "Stopping site #{@site_settings['site_id']}", level: :info
163
+ super
164
+ end
165
+
166
+ def wait_for_supervisor(ip, timeout)
167
+ supervisor = find_supervisor ip
168
+ return supervisor if supervisor
169
+
170
+ wait_for_condition(@proxy_condition, timeout: timeout) { find_supervisor ip }
171
+ rescue Async::TimeoutError
172
+ raise RSMP::TimeoutError, "Supervisor '#{ip}' did not connect within #{timeout}s"
173
+ end
174
+
175
+ def find_supervisor(ip)
176
+ @proxies.each do |supervisor|
177
+ return supervisor if ip == :any || supervisor.ip == ip
178
+ end
179
+ nil
180
+ end
181
+
182
+ def build_component(id:, type:, settings:)
183
+ settings ||= {}
184
+ if type == 'main'
185
+ Component.new id: id, node: self, grouped: true,
186
+ ntsoid: settings['ntsOId'], xnid: settings['xNId']
187
+ else
188
+ Component.new id: id, node: self, grouped: false
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,59 @@
1
+ module RSMP
2
+ class Supervisor < Node
3
+ module Modules
4
+ # Handles supervisor configuration and site settings
5
+ module Configuration
6
+ def handle_supervisor_settings(supervisor_settings)
7
+ defaults = {
8
+ 'port' => 12_111,
9
+ 'ips' => 'all',
10
+ 'guest' => {
11
+ 'sxl' => 'tlc',
12
+ 'intervals' => {
13
+ 'timer' => 1,
14
+ 'watchdog' => 1
15
+ },
16
+ 'timeouts' => {
17
+ 'watchdog' => 2,
18
+ 'acknowledgement' => 2
19
+ }
20
+ }
21
+ }
22
+
23
+ # merge options into defaults
24
+ @supervisor_settings = defaults.deep_merge(supervisor_settings)
25
+ @core_version = @supervisor_settings['guest']['core_version']
26
+ check_site_sxl_types
27
+ end
28
+
29
+ def check_site_sxl_types
30
+ sites = @supervisor_settings['sites'].clone || {}
31
+ sites['guest'] = @supervisor_settings['guest']
32
+ sites.each do |site_id, settings|
33
+ raise RSMP::ConfigurationError, "Configuration for site '#{site_id}' is empty" unless settings
34
+
35
+ sxl = settings['sxl']
36
+ raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': No SXL specified" unless sxl
37
+
38
+ RSMP::Schema.find_schemas! sxl if sxl
39
+ rescue RSMP::Schema::UnknownSchemaError => e
40
+ raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': #{e}"
41
+ end
42
+ end
43
+
44
+ def site_id_to_site_setting(site_id)
45
+ return {} unless @supervisor_settings['sites']
46
+
47
+ @supervisor_settings['sites'].each_pair do |id, settings|
48
+ return settings if id == 'guest' || id == site_id
49
+ end
50
+ raise HandshakeError, "site id #{site_id} unknown"
51
+ end
52
+
53
+ def ip_to_site_settings(ip)
54
+ @supervisor_settings['sites'][ip] || @supervisor_settings['sites']['guest']
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,140 @@
1
+ module RSMP
2
+ class Supervisor < Node
3
+ module Modules
4
+ # Handles incoming connections from sites
5
+ module Connection
6
+ def handle_connection(socket)
7
+ remote_port = socket.remote_address.ip_port
8
+ remote_hostname = socket.remote_address.ip_address
9
+ remote_ip = socket.remote_address.ip_address
10
+
11
+ info = { ip: remote_ip, port: remote_port, hostname: remote_hostname, now: Clock.now }
12
+ if accept? socket, info
13
+ accept_connection socket, info
14
+ else
15
+ reject_connection socket, info
16
+ end
17
+ rescue ConnectionError, HandshakeError => e
18
+ log "Rejected connection from #{remote_ip}:#{remote_port}, #{e}", level: :warning
19
+ distribute_error e
20
+ rescue StandardError => e
21
+ log "Connection: #{e}", exception: e, level: :error
22
+ distribute_error e, level: :internal
23
+ ensure
24
+ close socket, info
25
+ end
26
+
27
+ def accept?(_socket, _info)
28
+ true
29
+ end
30
+
31
+ def format_ip_and_port(info)
32
+ if @logger.settings['hide_ip_and_port']
33
+ '********'
34
+ else
35
+ "#{info[:ip]}:#{info[:port]}"
36
+ end
37
+ end
38
+
39
+ def authorize_ip(ip)
40
+ return if @supervisor_settings['ips'] == 'all'
41
+ return if @supervisor_settings['ips'].include? ip
42
+
43
+ raise ConnectionError, 'guest ip not allowed'
44
+ end
45
+
46
+ def check_max_sites
47
+ max = @supervisor_settings['max_sites']
48
+ return unless max
49
+ return unless @proxies.size >= max
50
+
51
+ raise ConnectionError, "maximum of #{max} sites already connected"
52
+ end
53
+
54
+ def peek_version_message(protocol)
55
+ json = protocol.peek_line
56
+ attributes = Message.parse_attributes json
57
+ Message.build attributes, json
58
+ end
59
+
60
+ def build_proxy_settings(socket, info)
61
+ stream = IO::Stream::Buffered.new(socket)
62
+ protocol = RSMP::Protocol.new stream
63
+
64
+ {
65
+ supervisor: self,
66
+ ip: info[:ip],
67
+ port: info[:port],
68
+ task: @task,
69
+ collect: @collect,
70
+ socket: socket,
71
+ stream: stream,
72
+ protocol: protocol,
73
+ info: info,
74
+ logger: @logger,
75
+ archive: @archive
76
+ }
77
+ end
78
+
79
+ def retrieve_site_id(protocol)
80
+ version_message = peek_version_message protocol
81
+ version_message.attribute('siteId').first['sId']
82
+ end
83
+
84
+ def setup_proxy(proxy, settings, id)
85
+ if proxy
86
+ raise ConnectionError, "Site #{id} alredy connected from port #{proxy.port}" if proxy.connected?
87
+
88
+ proxy.revive settings
89
+ else
90
+ check_max_sites
91
+ proxy = build_proxy settings.merge(site_id: id)
92
+ @proxies.push proxy
93
+ end
94
+ proxy
95
+ end
96
+
97
+ def validate_and_start_proxy(proxy, protocol)
98
+ proxy.setup_site_settings
99
+ proxy.check_core_version peek_version_message(protocol)
100
+ log "Validating using core version #{proxy.core_version}", level: :debug
101
+ proxy.start
102
+ proxy.wait
103
+ end
104
+
105
+ def accept_connection(socket, info)
106
+ log "Site connected from #{format_ip_and_port(info)}",
107
+ ip: info[:ip],
108
+ port: info[:port],
109
+ level: :info,
110
+ timestamp: Clock.now
111
+
112
+ authorize_ip info[:ip]
113
+
114
+ settings = build_proxy_settings(socket, info)
115
+ id = retrieve_site_id(settings[:protocol])
116
+ proxy = setup_proxy(find_site(id), settings, id)
117
+
118
+ validate_and_start_proxy(proxy, settings[:protocol])
119
+ ensure
120
+ site_ids_changed
121
+ stop if @supervisor_settings['one_shot']
122
+ end
123
+
124
+ def reject_connection(_socket, info)
125
+ log 'Site rejected', ip: info[:ip], level: :info
126
+ end
127
+
128
+ def close(socket, info)
129
+ if info
130
+ log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: Clock.now
131
+ else
132
+ log 'Connection closed', level: :info, timestamp: Clock.now
133
+ end
134
+
135
+ socket.close
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,64 @@
1
+ module RSMP
2
+ class Supervisor < Node
3
+ module Modules
4
+ # Manages connected sites and site discovery
5
+ module Sites
6
+ def site_connected?(site_id)
7
+ !find_site(site_id).nil?
8
+ end
9
+
10
+ def find_site_from_ip_port(ip, port)
11
+ @proxies.each do |site|
12
+ return site if site.ip == ip && site.port == port
13
+ end
14
+ nil
15
+ end
16
+
17
+ def find_site(site_id)
18
+ @proxies.each do |site|
19
+ return site if site_id == :any || site.site_id == site_id
20
+ end
21
+ nil
22
+ end
23
+
24
+ def wait_for_site(site_id, timeout:)
25
+ site = find_site site_id
26
+ return site if site
27
+
28
+ wait_for_condition(@site_id_condition, timeout: timeout) do
29
+ find_site site_id
30
+ end
31
+ rescue Async::TimeoutError
32
+ str = if site_id == :any
33
+ 'No site connected'
34
+ else
35
+ "Site '#{site_id}' did not connect"
36
+ end
37
+ raise RSMP::TimeoutError, "#{str} within #{timeout}s"
38
+ end
39
+
40
+ def wait_for_site_disconnect(site_id, timeout:)
41
+ wait_for_condition(@site_id_condition, timeout: timeout) { true unless find_site site_id }
42
+ rescue Async::TimeoutError
43
+ raise RSMP::TimeoutError, "Site '#{site_id}' did not disconnect within #{timeout}s"
44
+ end
45
+
46
+ def check_site_id(site_id)
47
+ # check_site_already_connected site_id
48
+ site_id_to_site_setting site_id
49
+ end
50
+
51
+ def check_site_already_connected(site_id)
52
+ site = find_site(site_id)
53
+ raise HandshakeError, "Site '#{site_id}' already connected" if !site.nil? && site != self
54
+ end
55
+
56
+ def site_ids_changed
57
+ @site_id_condition.signal
58
+ end
59
+
60
+ def aggregated_status_changed(site_proxy, component); end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ module RSMP
2
+ # RSMP supervisor (server) that accepts site connections.
3
+ class Supervisor < Node
4
+ include Modules::Configuration
5
+ include Modules::Connection
6
+ include Modules::Sites
7
+
8
+ attr_reader :core_version, :supervisor_settings, :proxies, :logger, :ready_condition
9
+
10
+ attr_accessor :site_id_condition
11
+
12
+ def initialize(options = {})
13
+ handle_supervisor_settings(options[:supervisor_settings] || {})
14
+ super
15
+ @proxies = []
16
+ @ready_condition = Async::Notification.new
17
+ @site_id_condition = Async::Notification.new
18
+ end
19
+
20
+ def site_id
21
+ @supervisor_settings['site_id']
22
+ end
23
+
24
+ # listen for connections
25
+ def run
26
+ log "Starting supervisor on port #{@supervisor_settings['port']}",
27
+ level: :info,
28
+ timestamp: @clock.now
29
+
30
+ @endpoint = IO::Endpoint.tcp('0.0.0.0', @supervisor_settings['port'])
31
+ @accept_task = Async::Task.current.async do |task|
32
+ task.annotate 'supervisor accept loop'
33
+ @endpoint.accept do |socket| # creates fibers
34
+ handle_connection(socket)
35
+ rescue StandardError => e
36
+ distribute_error e, level: :internal
37
+ end
38
+ rescue Async::Stop
39
+ # Expected during shutdown - no action needed
40
+ rescue StandardError => e
41
+ distribute_error e, level: :internal
42
+ end
43
+
44
+ @ready_condition.signal
45
+ @accept_task.wait
46
+ rescue StandardError => e
47
+ distribute_error e, level: :internal
48
+ end
49
+
50
+ # stop
51
+ def stop
52
+ log "Stopping supervisor #{@supervisor_settings['site_id']}", level: :info
53
+
54
+ @accept_task&.stop
55
+ @accept_task = nil
56
+
57
+ @endpoint = nil
58
+ super
59
+ end
60
+
61
+ def build_proxy(settings)
62
+ SiteProxy.new settings
63
+ end
64
+
65
+ def self.build_id_from_ip_port(ip, port)
66
+ Digest::MD5.hexdigest("#{ip}:#{port}")[0..8]
67
+ end
68
+ end
69
+ end
@@ -2,6 +2,7 @@ module RSMP
2
2
  class Restart < StandardError
3
3
  end
4
4
 
5
+ # Task helpers for starting and managing an Async task lifecycle.
5
6
  module Task
6
7
  attr_reader :task
7
8
 
@@ -13,7 +14,7 @@ module RSMP
13
14
  # run() will be called inside the task to perform actual long-running work
14
15
  def start
15
16
  return if @task
16
-
17
+
17
18
  # Use current task context if available, otherwise create new reactor
18
19
  if Async::Task.current?
19
20
  Async::Task.current.async do |task|
@@ -37,12 +38,12 @@ module RSMP
37
38
 
38
39
  # initiate restart by raising a Restart exception
39
40
  def restart
40
- raise Restart.new "restart initiated by #{self.class.name}:#{object_id}"
41
+ raise Restart, "restart initiated by #{self.class.name}:#{object_id}"
41
42
  end
42
43
 
43
44
  # get the status of our task, or nil of no task
44
45
  def task_status
45
- @task.status if @task
46
+ @task&.status
46
47
  end
47
48
 
48
49
  # perform any long-running work
@@ -55,7 +56,7 @@ module RSMP
55
56
 
56
57
  # wait for our task to complete
57
58
  def wait
58
- @task.wait if @task
59
+ @task&.wait
59
60
  end
60
61
 
61
62
  # stop our task
@@ -64,8 +65,7 @@ module RSMP
64
65
  stop_task if @task
65
66
  end
66
67
 
67
- def stop_subtasks
68
- end
68
+ def stop_subtasks; end
69
69
 
70
70
  # stop our task and any subtask
71
71
  def stop_task
@@ -75,22 +75,21 @@ module RSMP
75
75
 
76
76
  # wait for an async condition to signal, then yield to block
77
77
  # if block returns true we're done. otherwise, wait again
78
- def wait_for_condition condition, timeout:, task:Async::Task.current, &block
79
- unless task
80
- raise RuntimeError.new("Can't wait without a task")
81
- end
78
+ def wait_for_condition(condition, timeout:, task: Async::Task.current, &block)
79
+ raise "Can't wait without a task" unless task
80
+
82
81
  task.with_timeout(timeout) do
83
82
  while task.running?
84
83
  value = condition.wait
85
84
  return value unless block
85
+
86
86
  result = yield value
87
87
  return result if result
88
88
  end
89
- raise RuntimeError.new("Can't wait for condition because task #{task.object_id} #{task.annotation} is not running")
89
+ raise "Can't wait for condition because task #{task.object_id} #{task.annotation} is not running"
90
90
  end
91
91
  rescue Async::TimeoutError
92
- raise RSMP::TimeoutError.new
92
+ raise RSMP::TimeoutError
93
93
  end
94
-
95
94
  end
96
- end
95
+ end