rsmp 0.48.3 → 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 (108) 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. data/schemas/tlc/1.0.10/commands/M0001.json +3 -3
  87. data/schemas/tlc/1.0.10/source/sxl.yaml +3 -3
  88. data/schemas/tlc/1.0.10/sxl_index.json +3 -3
  89. data/schemas/tlc/1.0.13/commands/M0001.json +3 -3
  90. data/schemas/tlc/1.0.13/source/sxl.yaml +3 -3
  91. data/schemas/tlc/1.0.13/sxl_index.json +3 -3
  92. data/schemas/tlc/1.0.14/commands/M0001.json +3 -3
  93. data/schemas/tlc/1.0.14/source/sxl.yaml +3 -3
  94. data/schemas/tlc/1.0.14/sxl_index.json +3 -3
  95. data/schemas/tlc/1.0.15/commands/M0001.json +3 -3
  96. data/schemas/tlc/1.0.15/source/sxl.yaml +3 -3
  97. data/schemas/tlc/1.0.15/sxl_index.json +3 -3
  98. data/schemas/tlc/1.0.7/commands/M0001.json +3 -3
  99. data/schemas/tlc/1.0.7/source/sxl.yaml +3 -3
  100. data/schemas/tlc/1.0.7/sxl_index.json +3 -3
  101. data/schemas/tlc/1.0.8/commands/M0001.json +3 -3
  102. data/schemas/tlc/1.0.8/source/sxl.yaml +3 -3
  103. data/schemas/tlc/1.0.8/sxl_index.json +3 -3
  104. data/schemas/tlc/1.0.9/commands/M0001.json +3 -3
  105. data/schemas/tlc/1.0.9/source/sxl.yaml +3 -3
  106. data/schemas/tlc/1.0.9/sxl_index.json +3 -3
  107. metadata +25 -4
  108. data/lib/rsmp/collect/collector/status.rb +0 -34
@@ -1,148 +1,96 @@
1
- # Tasks
1
+ # Async task ownership
2
2
 
3
- ## Concurrency
4
- When you use a site or a supervisor, it runs asyncronously so you can run several concurrently, or do other things concurrently, like sending messages and waiting for reponses.
3
+ RSMP uses Ruby's fiber scheduler and the Async gem for connections, readers,
4
+ timers, and waits. A site, supervisor, or proxy is not itself an Async task. It
5
+ owns a main task exposed through `task`.
5
6
 
6
- Concurrency is provided by Ruby Fiber Scheduler and the Async gem. and used to:
7
+ ## Starting and waiting
7
8
 
8
- - Runn multiple sites or supervisors
9
- - Run multiple connections from each site or supervisor
10
- - Wait for messages, connections or states
11
-
12
- Classes don't inherit from Async::Task. Instead they include task as instance variables. This means that the hierachy of objects and tasks can be different.
13
-
14
- ## Task module
15
- The `Task` module is used by the `Node` and `Proxy` classes and defines a life cycle for handling async tasks.
16
-
17
- A single main tasks is kept in `@task`. If subclasses need subtask, they can start them as needed, inside the main task.
18
-
19
- You first call `start`. If `@task` already exists, it will return immedatiately.
20
- Otherwise an async task is created and stored in `@task`, and `run` is called inside this task, to handle any long-running jobs, like listening for incoming messages. By default `run` calls `start_subtasks`, but should be overriden to do actual work as well.
21
- The call to `start` returns immediately, with the async task running concurently.
22
-
23
- If you want to stop the task, call `stop`. It will fist call `stop_subtasks` and then `stop_task` which calls Async#stop on `@task` and sets `@task` to `nil`.
24
-
25
- ## Stopping tasks
26
- If a task stops itself, code after the call to `stop` will not be run, unless you use an ensure block:
9
+ `start` must run below an active Async parent. It returns the created
10
+ `Async::Task`; it never creates a hidden root reactor.
27
11
 
28
12
  ```ruby
29
- require 'async'
30
-
31
13
  Async do |task|
32
- task.stop
33
- puts "I just stopped" # this will not be reaced, because the task was stopped
34
- end
14
+ site = RSMP::Site.new(site_settings: settings)
15
+ site.start(parent: task)
35
16
 
36
- Async do |task|
37
- task.stop
38
- ensure
39
- puts "I just stopped" # this will be reached
17
+ # Other work can run here.
18
+ site.stop
40
19
  end
41
20
  ```
42
- This can happen if either the task stops itself directly, or if it stops task higher up in the task hierarchy.
43
-
44
- For example, when a timer task finds that an acknowledgement was not received in time, and then closes the connection by calling stop() on the Proxy, the proxy which will then in turn stop the timer task. So any code that should be run after that must be inside an ensure block.
45
-
46
- ## Proxies
47
- A site connects to one of more supervisors. A proxy is created and run to handle each connection.
48
-
49
- A site can connect to one or more supervisor. It creates a proxy for each and runs them.
50
-
51
- When the proxy is run, it creates an async task to read incoming messages. This reader task is stored in `@reader`and will be a sub task of the site/supervisor main task.
52
-
53
- A proxy also creates a timer task, stored in `@timer`. This task is used to check watchdog and acknowledgement timeouts.
54
-
55
- Proxies build on the Task module functionality by handling RSMP communication via a TCP socket.
56
-
57
- The TCP socket can be open or closed. The RSMP communication first goes through a handshake sequence before being ready. The `status` attribute keeps track of this, and can be either `:disconnected`, `:connected` or `:ready`
58
-
59
- Proxies provide `connect` and `close` for starting and stopping commununication.
60
21
 
61
- A supervisor proxies connects actively to a site proxy, whereas a site proxy waits for the supervisor proxy to connect.
22
+ Inside an Async task, the current task is the default parent, so `site.start` is
23
+ equivalent. Calling `start` again while the task is running returns the existing
24
+ task.
62
25
 
63
- A supervisor proxy is created at startup, and is responsible for creating the TCP socket and connecting to the supervisor.
26
+ `wait` uses `Async::Task#wait`. If the task ends because of an unexpected
27
+ exception, `wait` raises that same exception with its original backtrace.
28
+ Cancellation is handled by Async and is not translated into an operational
29
+ failure.
64
30
 
65
- A site proxy is also created at startup, but the socket is created in the supervisor by `Aync::Endpoint#accept`when a site connects.
31
+ ## Structured task trees
66
32
 
67
- ## Object Hierarchy
68
- A Site has one or more SupervisorProxies, which represent connections to the remote supervisor.
33
+ Every long-lived child has an explicit owner:
69
34
 
70
- A Supervisor has one or more SiteProxies, which represent connections to the remote site.
71
-
72
- SiteProxy and SupervisorProxy both inherit from Proxy and have a reader task and a timer task. These are just Async tasks, not separate classes.
73
-
74
- ## Task Hierachy
75
- When you start an Async task, the parent will be the currently running task, unless you specify a different parent task.
76
-
77
- The Async task hierarchy is similar to the object hierachy, with the difference that proxies have reader and timer tasks. And IO::Endpoint which is used to handle TCP connections concurently, will create some intermediate tasks:
78
-
79
- ```
80
- Supervisor @task
81
- accepting connections # this task is created by IO::Endpoint
82
- incoming connection 1 # this task is created by IO::Endpoint
83
- SiteProxy @task
84
- SiteProxy @reader
85
- SiteProxy @timer
86
- incoming connection 2 # this task is created by IO::Endpoint
87
- SiteProxy @task
88
- SiteProxy @reader
89
- SiteProxy @timer
35
+ ```text
36
+ Site or Supervisor main task
37
+ ├── listener or connection proxy tasks
38
+ ├── site status timer
39
+ └── explicit termination waiter
90
40
 
41
+ Proxy main task
42
+ ├── connection reader
43
+ └── connection timer
91
44
  ```
92
45
 
46
+ Sibling tasks are registered in an `Async::Barrier`. Their owner observes task
47
+ completion in completion order. A reader's expected EOF or transport failure is
48
+ returned as an `RSMP::Result`, closes that connection session, and can lead to a
49
+ reconnect. An exception from message processing or a timer is not caught as a
50
+ connection error: it propagates through the barrier to the proxy and then the
51
+ node owner.
93
52
 
94
- A Site run from the CLI, before it connects to a supervisor:
53
+ Session IDs prevent a late task from an old connection from closing or
54
+ publishing termination for a newer connection. Closing is idempotent and
55
+ cancels the remaining reader/timer sibling.
95
56
 
96
- ```
97
- #<Async::Reactor:0xb90 1 children (running)>
98
- #<Async::Task:0xba4 cli (running)>
99
- #<Async::Task:0xbcc RSMP::TLC::TrafficControllerSite main task (running)>
100
- #<Async::Task:0xbe0 RSMP::SupervisorProxy main task (running)>
101
- #<Async::Task:0xbf4 tlc timer (running)>
102
- ```
57
+ ## Finite waits
103
58
 
104
- After the site connects to a supervisor:
59
+ Finite waits return `RSMP::Result`:
105
60
 
61
+ ```ruby
62
+ result = supervisor.wait_for_site('RN+SI0001', timeout: 5)
63
+ result = proxy.wait_for_state(:ready, timeout: 5)
106
64
  ```
107
- #<Async::Reactor:0xadc 1 children (running)>
108
- #<Async::Task:0xaf0 cli (running)>
109
- #<Async::Task:0xb18 RSMP::TLC::TrafficControllerSite main task (running)>
110
- #<Async::Task:0xb2c RSMP::SupervisorProxy main task (running)>
111
- #<Async::Task:0xb40 reader (running)>
112
- #<Async::Task:0xb54 timer (running)>
113
- #<Async::Task:0xb68 tlc timer (running)>
114
- ```
115
-
116
65
 
66
+ A normal timeout is `Result::Failure` with code `:timeout`. Use
67
+ `wait_for_site!`, `wait_for_state!`, or another bang variant to raise
68
+ `RSMP::OperationError` explicitly.
117
69
 
118
- A supervisor run from the CLI, before any sites have connected:
70
+ ## Events from long-running services
119
71
 
120
- ```
121
- #<Async::Reactor:0x94c 1 children (running)>
122
- #<Async::Task:0x960 cli (running)>
123
- #<Async::Task:0x988 RSMP::Supervisor main task (running)>
124
- #<Async::Task:0x99c accepting connections #<Addrinfo: 0.0.0.0:14111 TCP> [fd=12] (running)>
125
- ```
72
+ Events report occurrences that are not the result of a single finite call, such
73
+ as a failed connection attempt, invalid peer message, missing acknowledgement,
74
+ missing watchdog, or connection end. Register an explicit subscriber on the
75
+ node:
126
76
 
127
- The supervisor after a site has connected
77
+ ```ruby
78
+ receiver = Object.new
79
+ receiver.define_singleton_method(:receive_event) do |event|
80
+ warn "#{event.type}: #{event.failure}"
81
+ end
128
82
 
83
+ site.add_event_receiver(receiver)
129
84
  ```
130
- #<Async::Reactor:0x94c 1 children (running)>
131
- #<Async::Task:0x960 cli (running)>
132
- #<Async::Task:0x988 RSMP::Supervisor main task (running)>
133
- #<Async::Task:0x99c accepting connections #<Addrinfo: 0.0.0.0:13111 TCP> [fd=12] (running)>
134
- #<Async::Task:0xac8 incoming connection #<Addrinfo: 127.0.0.1:51778 TCP> [fd=13] (running)>
135
- #<Async::Task:0xadc RSMP::SiteProxy main task (running)>
136
- #<Async::Task:0xaf0 reader (running)>
137
- #<Async::Task:0xb04 timer (running)>
138
- ```
139
-
140
- The task hierachy matters when you stop a task or iterate on subtasks. Note that calling `Task#wait`
141
- does not wait for subtasks, whereas Task#stop stops all subtasks.
142
85
 
143
- ## Transient tasks
144
- If you mark an Async task with `transient: true` when you created it, that task will be stopped aas soon as all normal task are completed. They will also be moved up the hierarchy if the parent is complete, but not the grandparent.
86
+ Events are ordered and delivered in the reactor. There is no implicit global
87
+ error queue. Subscribers must have an owner that drains or handles them. A
88
+ subscriber callback defect propagates unless the subscriber implements `crash`,
89
+ in which case it receives that exception through its own completion mechanism.
145
90
 
146
- Transient tasks can be used to cleanup, but using an `ensure` block in the transient task. When you call `stop` on a task, an Async::Stop exception is raised, which will run the code in the `ensure` block.
91
+ ## Restart and shutdown
147
92
 
148
- Some sus tests run in transient tasks. As soon as the main test code is complete, any subtasks like Sites or Supervisors that might otherwise prevent the test from completing will be stopped automatically.
93
+ `stop` cancels the owned main task and its descendants. A TLC restart request is
94
+ not an exception: it resolves an explicit `RSMP::Termination` with reason
95
+ `:restart`. The CLI observes this value, lets the old structured task tree clean
96
+ up, and then constructs a new site.
data/lib/rsmp/cli.rb CHANGED
@@ -21,6 +21,91 @@ module RSMP
21
21
  end
22
22
  end
23
23
 
24
+ # CLI subcommands for SXL dependency resolution and manifests.
25
+ class SXLCLI < Thor
26
+ namespace :sxl
27
+
28
+ desc 'resolve ROOT...', 'Resolve SXL dependencies and write a manifest'
29
+ method_option :source, type: :array, aliases: '-s', default: [],
30
+ banner: 'Local SXL file or directory paths'
31
+ method_option :out, type: :string, aliases: '-o', default: 'manifest.yaml',
32
+ banner: 'Manifest output path'
33
+ method_option :format, type: :string, default: RSMP::Schema.latest_core_version,
34
+ enum: RSMP::Schema.core_versions,
35
+ banner: 'RSMP Core manifest format version'
36
+ method_option :force, type: :boolean, aliases: '-f', default: false,
37
+ banner: 'Overwrite an existing manifest'
38
+ def resolve(*roots)
39
+ raise RSMP::SXL::Processing::Error, 'sxl resolve requires at least one root' if roots.empty?
40
+
41
+ ensure_manifest_output_available!
42
+ catalogue = RSMP::SXL::Processing::Catalogue.new(options[:source])
43
+ requirements = roots.map { |root| root_requirement(root, catalogue) }
44
+ documents = RSMP::SXL::Processing::Resolver.new(catalogue).resolve(requirements)
45
+ manifest = RSMP::SXL::Processing::Manifest.create(documents, format: options[:format])
46
+ write_manifest(manifest)
47
+ puts "Wrote #{options[:out]}"
48
+ rescue RSMP::SXL::Processing::Error, SystemCallError => e
49
+ fail_sxl_command(e)
50
+ end
51
+
52
+ desc 'verify [MANIFEST]', 'Verify an SXL manifest against local sources'
53
+ method_option :source, type: :array, aliases: '-s', default: [],
54
+ banner: 'Local SXL file or directory paths'
55
+ def verify(path = 'manifest.yaml')
56
+ catalogue = RSMP::SXL::Processing::Catalogue.new(options[:source])
57
+ manifest = RSMP::SXL::Processing::Manifest.load(path)
58
+ RSMP::SXL::Processing::Manifest.verify!(manifest, catalogue)
59
+ puts 'OK'
60
+ rescue RSMP::SXL::Processing::Error, SystemCallError => e
61
+ fail_sxl_command(e)
62
+ end
63
+
64
+ private
65
+
66
+ def root_requirement(root, catalogue)
67
+ return path_root_requirement(root, catalogue) if File.exist?(root)
68
+
69
+ name, requirement = root.split(':', 2)
70
+ unless requirement
71
+ raise RSMP::SXL::Processing::Error,
72
+ "Invalid root #{root.inspect}; expected an SXL YAML path or name:requirement"
73
+ end
74
+
75
+ RSMP::SXL::Processing::Document.validate_name!(name)
76
+ [name, RSMP::SXL::Processing::VersionRequirement.new(requirement)]
77
+ end
78
+
79
+ def path_root_requirement(path, catalogue)
80
+ raise RSMP::SXL::Processing::Error, "Root SXL #{path} is not a file" unless File.file?(path)
81
+
82
+ document = RSMP::SXL::Processing::Document.load(path)
83
+ catalogue.add_document(document)
84
+ [document.name, RSMP::SXL::Processing::VersionRequirement.new(document.version_string)]
85
+ end
86
+
87
+ def ensure_manifest_output_available!
88
+ output = options[:out]
89
+ return unless File.exist?(output)
90
+ raise RSMP::SXL::Processing::Error, "Manifest output #{output} is a directory" if File.directory?(output)
91
+ return if options[:force]
92
+
93
+ raise RSMP::SXL::Processing::Error,
94
+ "Refusing to overwrite existing manifest #{output}; use --force to replace it"
95
+ end
96
+
97
+ def write_manifest(manifest)
98
+ output = options[:out]
99
+ FileUtils.mkdir_p(File.dirname(File.expand_path(output)))
100
+ File.write(output, RSMP::SXL::Processing::Manifest.dump(manifest))
101
+ end
102
+
103
+ def fail_sxl_command(error)
104
+ puts "Error: #{error.message}"
105
+ exit 1
106
+ end
107
+ end
108
+
24
109
  # CLI subcommands for RSMP configuration validation.
25
110
  class ConfigCLI < Thor
26
111
  namespace :config
@@ -78,8 +163,9 @@ module RSMP
78
163
  puts "Error: #{e}"
79
164
  exit 1
80
165
  rescue StandardError => e
81
- puts "Uncaught error: #{e}"
82
- puts caller.join("\n")
166
+ warn "Uncaught error: #{e.class}: #{e.message}"
167
+ warn e.full_message(highlight: false, order: :top)
168
+ exit 1
83
169
  end
84
170
 
85
171
  desc 'supervisor', 'Run RSMP supervisor'
@@ -100,6 +186,7 @@ module RSMP
100
186
  end
101
187
 
102
188
  register SchemaCLI, 'schema', 'schema COMMAND', 'SXL schema commands'
189
+ register SXLCLI, 'sxl', 'sxl COMMAND', 'SXL dependency and manifest commands'
103
190
  register ConfigCLI, 'config', 'config COMMAND', 'Configuration commands'
104
191
 
105
192
  private
@@ -194,7 +281,10 @@ module RSMP
194
281
  loop do
195
282
  site = site_class.new(site_settings: settings, log_settings: log_settings)
196
283
  site.start
197
- site.wait
284
+ termination = site.wait
285
+ break unless termination&.success? && termination.value.reason == :restart
286
+
287
+ site.stop
198
288
  rescue Psych::SyntaxError => e
199
289
  puts "Cannot read config file #{e}"
200
290
  break
@@ -202,8 +292,6 @@ module RSMP
202
292
  RSMP::ConfigurationError => e
203
293
  puts "Cannot start site: #{e}"
204
294
  break
205
- rescue RSMP::Restart
206
- site.stop
207
295
  end
208
296
  end
209
297
  end