rsmp 0.48.4 → 0.51.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +19 -20
  4. data/README.md +27 -3
  5. data/Rakefile +3 -1
  6. data/documentation/cli.md +79 -4
  7. data/documentation/collecting_message.md +90 -170
  8. data/documentation/message_distribution.md +17 -3
  9. data/documentation/tasks.md +65 -117
  10. data/lib/rsmp/cli.rb +93 -5
  11. data/lib/rsmp/collect/collector.rb +117 -134
  12. data/lib/rsmp/collect/distributor.rb +16 -3
  13. data/lib/rsmp/collect/queue.rb +14 -6
  14. data/lib/rsmp/collect/receiver.rb +1 -1
  15. data/lib/rsmp/collect/state_collector.rb +11 -2
  16. data/lib/rsmp/collection.rb +12 -0
  17. data/lib/rsmp/completion.rb +49 -0
  18. data/lib/rsmp/component/component_base.rb +3 -3
  19. data/lib/rsmp/convert/export/json_schema/index.rb +8 -3
  20. data/lib/rsmp/convert/import/yaml.rb +12 -8
  21. data/lib/rsmp/delivery.rb +18 -0
  22. data/lib/rsmp/event.rb +19 -0
  23. data/lib/rsmp/event_source.rb +36 -0
  24. data/lib/rsmp/exchange.rb +10 -0
  25. data/lib/rsmp/failure.rb +23 -0
  26. data/lib/rsmp/helpers/error.rb +14 -23
  27. data/lib/rsmp/message.rb +9 -7
  28. data/lib/rsmp/node/node.rb +3 -21
  29. data/lib/rsmp/node/site/connections.rb +4 -9
  30. data/lib/rsmp/node/site/site.rb +31 -16
  31. data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
  32. data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
  33. data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
  34. data/lib/rsmp/node/task.rb +59 -53
  35. data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
  36. data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
  37. data/lib/rsmp/proxy/modules/receive.rb +109 -66
  38. data/lib/rsmp/proxy/modules/send.rb +109 -51
  39. data/lib/rsmp/proxy/modules/state.rb +15 -5
  40. data/lib/rsmp/proxy/modules/tasks.rb +96 -70
  41. data/lib/rsmp/proxy/modules/versions.rb +8 -8
  42. data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
  43. data/lib/rsmp/proxy/proxy.rb +4 -66
  44. data/lib/rsmp/proxy/site/connection.rb +27 -22
  45. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
  46. data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
  47. data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
  48. data/lib/rsmp/proxy/site/modules/status.rb +44 -19
  49. data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
  50. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
  51. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
  52. data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
  53. data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
  54. data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
  55. data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
  56. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +34 -30
  57. data/lib/rsmp/result.rb +95 -0
  58. data/lib/rsmp/schema/validation.rb +5 -3
  59. data/lib/rsmp/sxl/interface.rb +8 -1
  60. data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
  61. data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
  62. data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
  63. data/lib/rsmp/sxl/processing/document.rb +158 -0
  64. data/lib/rsmp/sxl/processing/error.rb +8 -0
  65. data/lib/rsmp/sxl/processing/manifest.rb +144 -0
  66. data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
  67. data/lib/rsmp/sxl/processing/resolver.rb +94 -0
  68. data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
  69. data/lib/rsmp/sxl/processing.rb +18 -0
  70. data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
  71. data/lib/rsmp/termination.rb +10 -0
  72. data/lib/rsmp/tlc/modules/plans.rb +0 -2
  73. data/lib/rsmp/tlc/proxy/control.rb +50 -16
  74. data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
  75. data/lib/rsmp/tlc/proxy/io.rb +46 -31
  76. data/lib/rsmp/tlc/proxy/plans.rb +36 -19
  77. data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
  78. data/lib/rsmp/tlc/proxy/status.rb +47 -23
  79. data/lib/rsmp/tlc/proxy/system.rb +16 -4
  80. data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
  81. data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
  82. data/lib/rsmp/validation.rb +22 -0
  83. data/lib/rsmp/version.rb +1 -1
  84. data/lib/rsmp.rb +19 -4
  85. data/rsmp.gemspec +1 -1
  86. metadata +25 -4
  87. data/lib/rsmp/collect/collector/status.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a7aa9df937cb7b5576fb663f49805e407ad171eb15e8984def184f417591092
4
- data.tar.gz: 3e7331da7fa601171f970850fa10a2e923a6aaa4aabf860d1e18a5aad7d25ff0
3
+ metadata.gz: 363ee5c8eb68f060a0f464939cc6a4444265568821fab38186992b189de29dd0
4
+ data.tar.gz: f53b952f5877fd3c17b8d90ee82b7e1412432ad34a68cdcac3736320674cce8f
5
5
  SHA512:
6
- metadata.gz: 4b42f68a891ca93dfe38f7d643551c2a09a82b577f95b6dfe8b46bb7dd75f240b52b8373ab93f6d33e1c86719be3da9778b8e4e225da0e05e998e394c0c025ec
7
- data.tar.gz: 303b2c7eca70376a291f04a145e7aa54c4b9666a2db73223e4d42b9cd41d2f6673a8c7ef5ac2af52b1ef9d1701a8f262d88b5b718bdc816707e0db7e894519d9
6
+ metadata.gz: ed82fd73d09b5bcbd83c5c675a9e8ff9ed8d925e23f7cd059dd8efd9ca413e672f53ca0f3f76717114686d541c88b2a84f6dcd204a57338688b4c5db11dec1c9
7
+ data.tar.gz: 2598d67cfd2d5a66bf466b6c8901d2013689160b18cf4fead369111c41dd0cc2ee760874551fc0b86d70f284b21a38b68aa5810c534a0a3563b79ef028f972a9
data/CHANGELOG.md CHANGED
@@ -705,3 +705,9 @@ Initial release.
705
705
 
706
706
  ## 0.48.2
707
707
  - fix legacy TLC M0001 schemas to use scalar command arguments
708
+
709
+ ## 0.50.0
710
+ - return immutable results for finite sends, waits, validation, and collection
711
+ - publish typed events for expected peer and connection failures
712
+ - preserve unexpected exceptions and backtraces through structured Async task trees
713
+ - add explicit bang variants for callers that prefer exception-based handling
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.48.4)
5
- async (~> 2.39)
4
+ rsmp (0.51.0)
5
+ async (~> 2.45)
6
6
  colorize (~> 1.1)
7
7
  io-endpoint (~> 0.17)
8
8
  io-stream (~> 0.13)
@@ -15,15 +15,13 @@ GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
17
  ast (2.4.3)
18
- async (2.41.0)
18
+ async (2.45.1)
19
19
  console (~> 1.29)
20
20
  fiber-annotation
21
- io-event (~> 1.11)
22
- metrics (~> 0.12)
23
- traces (~> 0.18)
21
+ io-event (~> 1.21)
24
22
  bigdecimal (4.1.2)
25
23
  colorize (1.1.0)
26
- console (1.36.0)
24
+ console (1.37.0)
27
25
  fiber-annotation
28
26
  fiber-local (~> 1.1)
29
27
  json
@@ -32,22 +30,24 @@ GEM
32
30
  fiber-storage
33
31
  fiber-storage (1.0.1)
34
32
  hana (1.3.7)
35
- io-endpoint (0.17.2)
36
- io-event (1.19.1)
37
- io-stream (0.13.1)
38
- json (2.20.0)
33
+ io-endpoint (0.18.0)
34
+ openssl (>= 3.3.0)
35
+ io-event (1.21.1)
36
+ io-stream (0.14.0)
37
+ openssl (>= 3.3)
38
+ json (2.21.2)
39
39
  json_schemer (2.5.0)
40
40
  bigdecimal
41
41
  hana (~> 1.3)
42
42
  regexp_parser (~> 2.0)
43
43
  simpleidn (~> 0.2)
44
- language_server-protocol (3.17.0.5)
44
+ language_server-protocol (3.17.0.6)
45
45
  lint_roller (1.1.0)
46
46
  logger (1.7.0)
47
- metrics (0.15.0)
47
+ openssl (4.0.2)
48
48
  ostruct (0.6.3)
49
49
  parallel (2.1.0)
50
- parser (3.3.11.1)
50
+ parser (3.3.12.0)
51
51
  ast (~> 2.4.1)
52
52
  racc
53
53
  prism (1.9.0)
@@ -55,8 +55,8 @@ GEM
55
55
  rainbow (3.1.1)
56
56
  rake (13.4.2)
57
57
  regexp_parser (2.12.0)
58
- rubocop (1.87.0)
59
- json (~> 2.3)
58
+ rubocop (1.90.0)
59
+ json (>= 2.3)
60
60
  language_server-protocol (~> 3.17.0.2)
61
61
  lint_roller (~> 1.1.0)
62
62
  parallel (>= 1.10)
@@ -66,21 +66,20 @@ GEM
66
66
  rubocop-ast (>= 1.49.0, < 2.0)
67
67
  ruby-progressbar (~> 1.7)
68
68
  unicode-display_width (>= 2.4.0, < 4.0)
69
- rubocop-ast (1.49.1)
69
+ rubocop-ast (1.50.0)
70
70
  parser (>= 3.3.7.2)
71
71
  prism (~> 1.7)
72
72
  rubocop-rake (0.7.1)
73
73
  lint_roller (~> 1.1)
74
74
  rubocop (>= 1.72.1)
75
75
  ruby-progressbar (1.13.0)
76
- simpleidn (0.2.3)
77
- sus (0.37.0)
76
+ simpleidn (0.3.0)
77
+ sus (0.37.2)
78
78
  sus-fixtures-async (0.2.0)
79
79
  async
80
80
  sus (~> 0.10)
81
81
  thor (1.5.0)
82
82
  timecop (0.9.11)
83
- traces (0.18.2)
84
83
  unicode-display_width (3.2.0)
85
84
  unicode-emoji (~> 4.1)
86
85
  unicode-emoji (4.2.0)
data/README.md CHANGED
@@ -20,12 +20,20 @@ The RSMP::Site and RSMP::Supervisor classes can be used to run a RSMP site.
20
20
 
21
21
  ```ruby
22
22
  require 'rsmp'
23
- RSMP::Site.new.start # run site until Ctlr-C is pressed
23
+ Async do |task|
24
+ site = RSMP::Site.new
25
+ site.start(parent: task)
26
+ site.wait
27
+ end
24
28
  ```
25
29
 
26
30
  ```ruby
27
31
  require 'rsmp'
28
- RSMP::Supervisor.new.start # run supervisor until Ctlr-C is pressed
32
+ Async do |task|
33
+ supervisor = RSMP::Supervisor.new
34
+ supervisor.start(parent: task)
35
+ supervisor.wait
36
+ end
29
37
  ```
30
38
 
31
39
  By default, a site will try to connect to a single supervisor on localhost 127.0.0.1, port 12111. By default, a supervisor will listen for sites on port 12111 and accept any site.
@@ -131,12 +139,19 @@ the RSMP Core and SXL source repositories.
131
139
  Core and SXL schemas are selected with a flat map:
132
140
 
133
141
  ```ruby
134
- RSMP::Schema.validate(message, {
142
+ validation = RSMP::Schema.validate(message, {
135
143
  core: '3.3.0',
136
144
  tlc: '1.3.0'
137
145
  })
146
+ warn validation.message if validation.invalid?
138
147
  ```
139
148
 
149
+ Finite operations such as waits, sends, and response collection return
150
+ `RSMP::Result`. Expected peer, transport, and timeout failures are values;
151
+ unexpected implementation errors still propagate with their original
152
+ backtraces. See [finite operations and collection](documentation/collecting_message.md)
153
+ and [Async task ownership](documentation/tasks.md).
154
+
140
155
  Sites and supervisors configure one or more SXLs with `sxls`:
141
156
 
142
157
  ```yaml
@@ -149,6 +164,15 @@ See [configuration](documentation/configuration.md) for connection-role examples
149
164
  ## Command-line tool
150
165
  Tools for easily running RSMP supervisors and sites. The binary is called ```rsmp```.
151
166
 
167
+ It also provides SXL dependency and manifest tooling:
168
+
169
+ ```console
170
+ $ rsmp sxl resolve path/to/sxl.yaml --source path/to/sxl-catalog
171
+ $ rsmp sxl verify manifest.yaml --source path/to/sxl-catalog
172
+ ```
173
+
174
+ See [the CLI reference](documentation/cli.md#rsmp-sxl-resolve) for source layout, requirement syntax, and options.
175
+
152
176
  The ```supervisor``` command will start an RSMP supervisor, which sites can connect to:
153
177
 
154
178
  ```console
data/Rakefile CHANGED
@@ -23,7 +23,9 @@ end
23
23
  def require_minimum_core_version!(source_path)
24
24
  yaml = YAML.load_file(source_path)
25
25
  minimum_core_version = yaml.dig('meta', 'minimum_core_version')
26
- raise "Missing meta.minimum_core_version in #{source_path}" if minimum_core_version.nil? || minimum_core_version.to_s.empty?
26
+ return unless minimum_core_version.nil? || minimum_core_version.to_s.empty?
27
+
28
+ raise "Missing meta.minimum_core_version in #{source_path}"
27
29
  end
28
30
 
29
31
  # Update vendored schemas from source repos.
data/documentation/cli.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # RSMP CLI
2
2
 
3
- The `rsmp` command can run a site, run a supervisor, validate config files, generate JSON Schemas from an SXL, and print the gem version.
3
+ The `rsmp` command can run a site, run a supervisor, validate config files, resolve SXL dependencies,
4
+ verify SXL manifests, generate JSON Schemas from an SXL, and print the gem version.
4
5
 
5
6
  Use help at any level:
6
7
 
@@ -8,8 +9,10 @@ Use help at any level:
8
9
  $ rsmp help
9
10
  $ rsmp help site
10
11
  $ rsmp help supervisor
11
- $ rsmp help config check
12
- $ rsmp help schema generate
12
+ $ rsmp config help check
13
+ $ rsmp schema help generate
14
+ $ rsmp sxl help resolve
15
+ $ rsmp sxl help verify
13
16
  ```
14
17
 
15
18
  ## Quick Examples
@@ -66,6 +69,18 @@ Generate JSON Schema files from an SXL YAML file:
66
69
  $ rsmp schema generate --in schemas/tlc/1.3.0/sxl.yaml --out /tmp/tlc-schema
67
70
  ```
68
71
 
72
+ Resolve an SXL and its dependencies from local sources:
73
+
74
+ ```console
75
+ $ rsmp sxl resolve schema/sxl.yaml --source ../sxl-catalog
76
+ ```
77
+
78
+ Verify the resulting manifest against the same sources:
79
+
80
+ ```console
81
+ $ rsmp sxl verify manifest.yaml --source ../sxl-catalog
82
+ ```
83
+
69
84
  ## Configuration Files
70
85
 
71
86
  Most real runs should use YAML config files. The CLI accepts `--config`, `-c`, and `--options` as aliases:
@@ -206,10 +221,70 @@ Options:
206
221
  - `--in PATH`, `-i PATH`: path to the input `sxl.yaml`. Defaults to `sxl.yaml`.
207
222
  - `--out PATH`, `-o PATH`: output directory. Defaults to the current directory.
208
223
 
209
- The command writes the generated status, command, alarm, root schema, definitions, and `sxl_index.json` files to the output directory.
224
+ The input can use either the top-level `components` or `objects` mapping. Both forms are normalized to
225
+ `components` before processing. The command
226
+ writes the generated status, command, alarm, root schema, definitions, and `sxl_index.json` files to the output directory.
210
227
 
211
228
  If the input file is missing, the command prints an error and exits with status `1`.
212
229
 
230
+ ### `rsmp sxl resolve`
231
+
232
+ Resolves direct and transitive SXL dependencies, checks for conflicting component types and message codes,
233
+ and writes a manifest:
234
+
235
+ ```console
236
+ $ rsmp sxl resolve schema/sxl.yaml --source ../sxl-catalog
237
+ ```
238
+
239
+ Roots can be SXL YAML paths or `name:requirement` specifications. Several roots can be supplied
240
+ for a site SXL list:
241
+
242
+ ```console
243
+ $ rsmp sxl resolve traffic_light_controller:~1.3 public_priority:2.1.0 \
244
+ --source ../sxl-catalog --out site-manifest.yaml
245
+ ```
246
+
247
+ Quote a requirement when it contains spaces:
248
+
249
+ ```console
250
+ $ rsmp sxl resolve 'traffic_light_controller:>=1.3.0 and <2.0.0' --source ../sxl-catalog
251
+ ```
252
+
253
+ Source arguments can be individual YAML files or directories. Directories are searched recursively for files
254
+ named `sxl.yaml` or `sxl.yml`. Every source must use top-level `meta`, optional `prefix`, optional
255
+ `dependencies`, and either `components` or `objects`. Both input forms are normalized to `components`.
256
+ Sources are indexed by exact `meta.name` and `meta.version`.
257
+
258
+ Options:
259
+
260
+ - `--source PATH...`, `-s PATH...`: one or more local source files or directories.
261
+ - `--out PATH`, `-o PATH`: manifest output path. Defaults to `manifest.yaml`.
262
+ - `--format VERSION`: RSMP Core version written as `meta.format`. Defaults to the latest Core version bundled with the gem.
263
+ - `--force`, `-f`: replace an existing output file. Without this option, existing files are preserved and the command fails.
264
+
265
+ The resolver supports exact versions, `>`, `>=`, `<`, `<=`, `~`, and two comparisons joined by `and`. It tries
266
+ available versions newest first, rejects unsatisfied requirements and dependency cycles, applies each SXL prefix,
267
+ and rejects component types or message codes defined by more than one resolved SXL. For non-zero major versions,
268
+ `~1.3` means `>=1.3.0 and <2.0.0`. A major-zero compatibility requirement must include all three parts and is exact,
269
+ for example `~0.3.1` matches only `0.3.1`.
270
+
271
+ ### `rsmp sxl verify`
272
+
273
+ Verifies a manifest against locally supplied SXL sources:
274
+
275
+ ```console
276
+ $ rsmp sxl verify manifest.yaml --source ../sxl-catalog
277
+ ```
278
+
279
+ The manifest defaults to `manifest.yaml`. `--source PATH...` has the same meaning as for `sxl resolve`.
280
+ Verification checks the manifest shape and metadata, natural name order, availability of every exact SXL version,
281
+ the complete dependency closure and version requirements, dependency cycles, Core-format compatibility, and component
282
+ or message-code conflicts. A valid manifest prints `OK`; a failure prints an error and exits with status `1`.
283
+
284
+ Because the manifest format records only exact resolved versions and does not distinguish roots from dependencies,
285
+ verification checks validity but cannot determine whether the manifest still selects the newest versions in a changed
286
+ source catalogue. Run `sxl resolve` again to refresh version selection.
287
+
213
288
  ### `rsmp version`
214
289
 
215
290
  Prints the installed `rsmp` gem version:
@@ -1,226 +1,146 @@
1
- # Collection
2
- You often need to collect messages or responses. The collector classes are used to collect message asyncronously. Other tasks continue until the collection completes, time outs or is cancelled.
1
+ # Finite operations and message collection
3
2
 
4
- A collector can collect ingoing and/or outgoing messages.
5
-
6
- An object that includes the Distributor module (or implements the same functionality) must be provided when you construct a Collected. The collector will attach itself to this distributor when it starts collecting, to receive messages. The SiteProxy and SupervisorProxy classes both include the Distributor module, and can therefore be used as message sources.
7
-
8
- Messages that match the relevant criteria are stored by the collector.
9
-
10
- When the collection is done, the collector detaches from the distributor, and returns the status.
11
-
12
-
13
- ## Collector
14
- Class used for collecting messages filtered by message type, direction and/or component id. A block can be used for custom filtering.
15
-
16
- You can choose to collect a specific number of message and/or for a specific duration.
17
-
18
- A collector has a status, which is `:ready` initialialy. When you start collecting, it changes to `:collecting`. It will be `:ok` once collection completes successfully, or `:cancel` if it was cancelled to to some error or by a filter block.
19
-
20
- ### Initialization
21
- When you create a collector, you provide a Filter to specify the messages types you want to collect. You can also specify ingoing and/or outgoing direction and the RSMP component.
3
+ RSMP distinguishes expected operational failures from implementation defects.
4
+ Operations that can normally time out, be rejected, or lose their connection
5
+ return an `RSMP::Result`. They do not use exceptions for ordinary control flow.
22
6
 
23
7
  ```ruby
24
- collector = MessageCollector.new(distributor,
25
- num: 10,
26
- filter: Filter.new(ingoing: true, outgoing: true)
27
- ```
28
-
29
- num: The number of messages to collect. If not provided, a timeout must be set instead.
30
- filter: filter to identify the types of messages to look for.
8
+ result = proxy.wait_for_state(:ready, timeout: 5)
31
9
 
32
- ### Filter
33
- The Filter class is used to filter messages according to message type, direction and component.
34
-
35
- ```ruby
36
- filter = Filter.new(
37
- type: 'Alarm',
38
- ingoing: true,
39
- outgoing: false,
40
- component: 'DL1'
41
- )
10
+ if result.success?
11
+ puts "Reached #{result.value}"
12
+ else
13
+ warn "#{result.failure.code}: #{result.failure.message}"
14
+ end
42
15
  ```
43
16
 
44
- type: a string, or an array of string, specifiying one or more RSMP message types.
45
- ingoing: Whether to collect ingoing messages. Defaults to true
46
- outgoing: Whether to collect outgoing messages. Defaults to true
47
- component: An RSMP component id.
17
+ `RSMP::Result::Success` contains `value`. `RSMP::Result::Failure` contains an
18
+ immutable `RSMP::Failure` with:
48
19
 
49
- ### Collecting
50
- Use collect() to start collecting and wait for completion or timeout. The status will be returned.
20
+ - `code`: a stable symbol such as `:timeout`, `:not_ready`,
21
+ `:message_rejected`, or `:disconnected`.
22
+ - `message`: a human-readable explanation.
23
+ - `source`: where the failure originated, such as `:peer`, `:transport`,
24
+ `:connection`, `:timeout`, or `:local`.
25
+ - `context`: structured details such as the message and connection session.
26
+ - `cause`: the underlying exception, when an expected low-level exception was
27
+ translated at a boundary.
28
+
29
+ Use the corresponding bang method when exception-based handling is more
30
+ convenient. A failed result is then raised as `RSMP::OperationError`; its
31
+ `failure` attribute retains the structured failure.
51
32
 
52
33
  ```ruby
53
- result = collector.collect # => :ok, :timeout or :cancelled
54
- collector.messages # => collected messages
34
+ proxy.wait_for_state!(:ready, timeout: 5)
55
35
  ```
56
36
 
57
- If you want start collection, but not wait for the result, use `start()`. You can then later use `wait()` if you want:
37
+ Unexpected exceptions are never converted into a `Result`. They propagate with
38
+ their original class and backtrace because they indicate a bug in the library or
39
+ calling application.
58
40
 
59
- ```ruby
60
- result = collector.start # => nil
61
- # do other stuff
62
- result = collector.wait
63
- ```
41
+ Message handlers can reject schema-valid but semantically invalid peer input by
42
+ raising `RSMP::PeerMessageError` or one of its domain subclasses, normally
43
+ `RSMP::MessageRejected`. The receive boundary converts only this explicit error
44
+ family to a peer failure. It does not rescue arbitrary `StandardError` values.
64
45
 
65
- ### Custom filtering
66
- You can use a block to do extra filtering. The block will be callled for each messages that passes the Filter provided when initializing the collector.
46
+ ## Validation
67
47
 
68
- The block must return nil or a list of symbols to indicate whether the message should be kept, and whether collection should be cancelled.
48
+ Schema mismatches are expected when communicating with an external peer.
49
+ `RSMP::Schema.validate` and `message.validate` return an `RSMP::Validation`:
69
50
 
70
51
  ```ruby
71
- result = collector.collect do |message|
72
- :keep, :cancel # example of how to keep the message and cancel collection
52
+ validation = message.validate(core: '3.3.0', tlc: '1.3.0')
53
+
54
+ unless validation.valid?
55
+ warn validation.message
56
+ validation.violations.each { |violation| warn violation.inspect }
73
57
  end
74
58
  ```
75
59
 
76
- `:keep` keeps (collect) this message
77
- `:cancel` cancel collection
60
+ `message.validate!` is the explicit raising variant. Invalid API arguments,
61
+ missing schema configuration, and unknown schema versions still raise directly
62
+ because they are programming or configuration errors rather than invalid peer
63
+ messages.
78
64
 
79
- Note that you cannot use `return` in a block. You can either simply provide the values as the last expresssion in the block, or use next().
65
+ ## Collectors
80
66
 
81
- Exceptions in the block will cause the collector to abort. If the collect! or wait! variants are used, the exception is propagated to the caller.
82
-
83
- ### Bang version
84
- The method collect!() will raise exceptions in case of errors, and will return the collect message directly.
67
+ A collector attaches to a message distributor, such as `SiteProxy` or
68
+ `SupervisorProxy`, and waits for matching messages. Give it a count, a timeout,
69
+ or a block that completes or cancels the collection.
85
70
 
86
71
  ```ruby
87
- message = collector.collect! # => collected message.
72
+ filter = RSMP::Filter.new(type: 'Alarm', ingoing: true, component: 'DL1')
73
+ collector = RSMP::Collector.new(proxy, filter: filter, num: 2, timeout: 5)
74
+ result = collector.collect
88
75
  ```
89
76
 
90
- Similar, `wait!()` will raise an exception in case of timeouts or errors:
77
+ A successful collector returns `Result<RSMP::Collection>`. A collection is an
78
+ immutable snapshot containing `messages` and, for state collectors, `reached`
79
+ and `matcher_status`.
91
80
 
92
81
  ```ruby
93
- message = collector.wait! # => collected message.
82
+ if result.success?
83
+ result.value.messages.each { |message| puts message }
84
+ end
94
85
  ```
95
86
 
87
+ `collect!` and `wait!` return the message array directly and raise
88
+ `RSMP::OperationError` for an expected failure.
96
89
 
97
- ### Schema Errors and Disconnects
98
- The collector can optionally cancel collection in special cases, controlled by the `:cancel` option provided when contructing the collector.
90
+ To start without waiting:
99
91
 
100
92
  ```ruby
101
- options = {
102
- cancel: {
103
- disconnect: true,
104
- schema_error: true
105
- }
106
- }
107
- result = collector.collect options
93
+ collector.start
94
+ # Perform another operation.
95
+ result = collector.wait
108
96
  ```
109
97
 
110
- disconnect: If the proxy which provides messages experience a disconnect, the collector will cancel collection.
111
-
112
- schema_error: If the proxy receives a message with a schema error, the collector will cancel collection, if the the invalid message has the correct message type.
113
-
114
- ### NotAck
115
- A typical scenaria is that you send a command or status request, and want to collect the response. But if the original message is rejected by the site, you will received a NotAck instead of a reply. The collector classes can handle this, as long as you provide the message id of the original request in the `m_id` key of teh options when you construct the collector.
116
-
117
- If a NotAck is received with a matching `oMId` (original message id), the collection is cancelled.
98
+ Collectors start in an inactive state, become active after `start`, and detach
99
+ from the distributor exactly once when they succeed or fail. A matching
100
+ `MessageNotAck`, timeout, invalid peer message, connection end, or explicit
101
+ `cancel` resolves the collector with a failure. An exception raised by a custom
102
+ collector callback rejects its completion and propagates unchanged.
118
103
 
119
- ## StatusCollector
120
- Waits for a set of status criteria to be met.
121
-
122
- Note that a single RSMP status message can contain multiple status items. Unlike MessageCollector, a StatusCollector therefore operates on items, rather than messages, and you can't specify a number of messages to collect.
123
-
124
-
125
- ### Criteria
126
- You construct a StatusCollector with set of criteria, specifying the status codes, names, and optionally values that must be met.
127
-
128
- ### Collecting
129
- When you start collection, it will complete once all criteria are all fulfilled, the timeout is reached or a custom filtering block aborts the collection.
104
+ Custom blocks return `:keep` to retain a matching message. They can also call
105
+ `collector.cancel(reason)` explicitly.
130
106
 
131
107
  ```ruby
132
- collector = StatusCollector.new(options)
133
- result = matcher.collect(timeout: 5)
134
- ```
135
-
136
- ### Custom filtering
137
- You can use a block to do extra filtering. The block will be called for each individual status item that fulfils all criteria, like status code and name, component, etc.
138
-
139
- Like with MessageCollector, the block must return a hash specifing whether to keep the message and whether to continue collection.
108
+ result = collector.collect do |message|
109
+ next :keep if useful?(message)
140
110
 
141
- ```ruby
142
- matcher = StatusCollector.new(options)
143
- result = matcher.collect(options) do |message,item|
144
- next(:keep) if good_item?(item) # keep item
111
+ collector.cancel('No longer needed') if finished?
145
112
  end
146
113
  ```
147
114
 
148
- ## Sending commands
149
- The method `send_command` sends a CommandRequest to the site and returns the sent message. `component:` defaults to `main.c_id`.
150
-
151
- ```ruby
152
- message = send_command(
153
- [{'cCI' => 'M0001', 'n' => 'status', 'v' => 'NormalControl'}],
154
- component: 'AA+BBCCC=DDDEE001'
155
- )
156
- ```
115
+ ## Sending and collecting atomically
157
116
 
158
- To send and wait for the CommandResponse, use `send_command_and_collect`. It returns a collector; call `.ok!` to raise on NotAck or timeout.
117
+ High-level send methods follow the same non-bang/bang convention:
159
118
 
160
119
  ```ruby
161
- collector = send_command_and_collect(
162
- [{'cCI' => 'M0001', 'n' => 'status', 'v' => 'NormalControl'}],
163
- within: 5,
164
- component: 'AA+BBCCC=DDDEE001'
165
- )
166
- collector.ok!
120
+ result = proxy.send_command(command_list, component: 'C1')
121
+ message = proxy.send_command!(command_list, component: 'C1')
167
122
  ```
168
123
 
169
- ## Requesting status
170
- The method `request_status` sends a StatusRequest to the site and returns `{ sent: message }`. `component:` defaults to `main.c_id`.
124
+ Methods ending in `_and_collect` start their collector before sending, so an
125
+ immediate peer response cannot be missed. They return `Result<RSMP::Exchange>`.
126
+ The exchange contains the request and the immutable completed collection.
171
127
 
172
128
  ```ruby
173
- result = request_status(
174
- [{'sCI' => 'S0001', 'n' => 'signalgroupstatus'}],
175
- component: 'AA+BBCCC=DDDEE001'
176
- )
177
- result[:sent] # => the StatusRequest message
178
- ```
129
+ result = proxy.send_command_and_collect(command_list, component: 'C1', within: 5)
179
130
 
180
- To send and wait for the StatusResponse, use `request_status_and_collect`. It returns a collector; call `.ok!` to raise on NotAck or timeout.
181
-
182
- ```ruby
183
- collector = request_status_and_collect(
184
- [{'sCI' => 'S0001', 'n' => 'signalgroupstatus'}],
185
- within: 5,
186
- component: 'AA+BBCCC=DDDEE001'
187
- )
188
- collector.ok!
189
- ```
190
-
191
- ## Subscribing to status updates
192
- The method `subscribe_to_status` sends a StatusSubscribe message to the site and returns `{ sent: message }`. `component:` defaults to `main.c_id`.
193
-
194
- ### Without collection
195
-
196
- ```ruby
197
- result = subscribe_to_status(
198
- [{'sCI' => 'S0001', 'n' => 'signalgroupstatus', 'uRt' => '1'}],
199
- component: 'AA+BBCCC=DDDEE001'
200
- )
201
- result[:sent] # => the StatusSubscribe message
202
- ```
203
-
204
- If you want to manually collect incoming status updates after subscribing, start a collector before subscribing so you don't miss early responses:
205
-
206
- ```ruby
207
- task = async do
208
- MessageCollector.new(options).collect(num: 5, timeout: 10)
131
+ if result.success?
132
+ exchange = result.value
133
+ puts exchange.request
134
+ puts exchange.collection.messages
209
135
  end
210
- subscribe_to_status(status_list)
211
- task.wait
212
136
  ```
213
137
 
214
- ### With collection
215
-
216
- Use `subscribe_to_status_and_collect` to subscribe and collect status updates matching the criteria. It returns a collector; call `.ok!` to raise on NotAck or timeout.
138
+ Status requests and subscriptions use the same shape:
217
139
 
218
140
  ```ruby
219
- collector = subscribe_to_status_and_collect(
220
- [{'sCI' => 'S0001', 'n' => 'signalgroupstatus', 'uRt' => '1'}],
221
- within: 5,
222
- component: 'AA+BBCCC=DDDEE001'
223
- )
224
- collector.ok!
141
+ result = proxy.request_status_and_collect(status_list, component: 'C1', within: 5)
142
+ result = proxy.subscribe_to_status_and_collect(subscription_list, component: 'C1', within: 5)
225
143
  ```
226
144
 
145
+ The raising variants are `send_command_and_collect!`,
146
+ `request_status_and_collect!`, and `subscribe_to_status_and_collect!`.
@@ -1,12 +1,18 @@
1
1
  # Message distribution
2
2
 
3
- Proxy - - Distributor --> Receivers
3
+ ```text
4
+ Proxy -- Distributor --> message receivers
5
+ |
6
+ +-- EventSource ----> explicit node event subscribers
7
+ ```
4
8
 
5
- A proxy distributes messages to receivers, when they are installed.
9
+ A proxy distributes messages to receivers when they are installed. Distribution
10
+ uses a receiver snapshot, so a receiver may safely detach during delivery.
6
11
 
7
12
  Collectors are special receivers that wait for specific messages, and are used to implement methods for waiting for RSMP responses, statuses, alarms, etc.
8
13
 
9
- Note that Archive is not a receiver, and does not receive messages via the Distributor. Instead the Archive gets and stores messages via the log() interface in the Logging module. The reason is that the items that the Archive and the Logger contain other data as well as the message, like error messages, warnings, text descriptions, colors codes, etc. The Distributor and Receiver handles only Message objects.
14
+ Archive is not a receiver. It stores messages and other log entries through the
15
+ logging interface.
10
16
 
11
17
  ## Distributor
12
18
  A module that handles distributing messages to receivers.
@@ -14,9 +20,17 @@ A module that handles distributing messages to receivers.
14
20
  ## Receiver
15
21
  Receives messages as long as it's installed into a distributor.
16
22
 
23
+ Collectors also receive relevant `RSMP::Event` values, such as
24
+ `:invalid_message` and `:connection_ended`. This lets expected peer and
25
+ transport failures resolve a pending collection without raising.
26
+
17
27
  ## Collector
18
28
  Includes the Receiver module to wait for specific messages. Once received
19
29
  the client receives the collection.
20
30
 
21
31
  ## Proxy
22
32
  A proxy includes the Distributor module and distributes each message to receivers after processing it.
33
+
34
+ Long-running occurrences are additionally published to the owning node's
35
+ explicit event subscribers. Event delivery is synchronous and ordered; there is
36
+ no implicit global error queue.