nonnative 2.1.0 → 2.10.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.
@@ -18,6 +18,9 @@ module Nonnative
18
18
  # @param configuration [Nonnative::Configuration] the configuration to run
19
19
  def initialize(configuration)
20
20
  @configuration = configuration
21
+ @services = nil
22
+ @servers = nil
23
+ @processes = nil
21
24
  end
22
25
 
23
26
  # Starts all configured runners and yields results for each process/server.
@@ -27,10 +30,14 @@ module Nonnative
27
30
  # @yieldparam name [String, nil] runner name
28
31
  # @yieldparam values [Object] runner-specific return value from `start` (e.g. `[pid, running]` for processes)
29
32
  # @yieldparam result [Boolean] result of the port readiness check (`true` if ready in time)
30
- # @return [void]
33
+ # @return [Array<String>] lifecycle and readiness-check errors collected while starting
31
34
  def start(&)
32
- services.each(&:start)
33
- [servers, processes].each { |t| process(t, :start, :open?, &) }
35
+ errors = []
36
+
37
+ errors.concat(service_lifecycle(services, :start, :start))
38
+ [servers, processes].each { |t| errors.concat(process(t, :start, :open?, :start, &)) }
39
+
40
+ errors
34
41
  end
35
42
 
36
43
  # Stops all configured runners and yields results for each process/server.
@@ -40,10 +47,32 @@ module Nonnative
40
47
  # @yieldparam name [String, nil] runner name
41
48
  # @yieldparam id [Object] runner-specific identifier returned by `stop` (e.g. pid or object_id)
42
49
  # @yieldparam result [Boolean] result of the port shutdown check (`true` if closed in time)
43
- # @return [void]
50
+ # @return [Array<String>] lifecycle and shutdown-check errors collected while stopping
44
51
  def stop(&)
45
- [processes, servers].each { |t| process(t, :stop, :closed?, &) }
46
- services.each(&:stop)
52
+ errors = []
53
+
54
+ [processes, servers].each { |t| errors.concat(process(t, :stop, :closed?, :stop, &)) }
55
+ errors.concat(service_lifecycle(services, :stop, :stop))
56
+
57
+ errors
58
+ end
59
+
60
+ # Stops only runners that have already been instantiated in this pool.
61
+ #
62
+ # This is used to rollback partial startup after a failed {#start} without constructing new runner
63
+ # wrappers as a side effect.
64
+ #
65
+ # @yieldparam name [String, nil] runner name
66
+ # @yieldparam id [Object] runner-specific identifier returned by `stop`
67
+ # @yieldparam result [Boolean] result of the port shutdown check (`true` if closed in time)
68
+ # @return [Array<String>] lifecycle and shutdown-check errors collected while rolling back
69
+ def rollback(&)
70
+ errors = []
71
+
72
+ [existing_processes, existing_servers].each { |t| errors.concat(process(t, :stop, :closed?, :stop, &)) }
73
+ errors.concat(service_lifecycle(existing_services, :stop, :stop))
74
+
75
+ errors
47
76
  end
48
77
 
49
78
  # Finds a running process runner by configured name.
@@ -96,41 +125,103 @@ module Nonnative
96
125
  end
97
126
 
98
127
  def processes
99
- @processes ||= configuration.processes.map do |p|
100
- [Nonnative::Process.new(p), Nonnative::Port.new(p)]
128
+ return @processes unless @processes.nil?
129
+
130
+ @processes = []
131
+ configuration.processes.each do |p|
132
+ @processes << [Nonnative::Process.new(p), Nonnative::Port.new(p)]
101
133
  end
134
+
135
+ @processes
102
136
  end
103
137
 
104
138
  def servers
105
- @servers ||= configuration.servers.map do |s|
106
- [s.klass.new(s), Nonnative::Port.new(s)]
139
+ return @servers unless @servers.nil?
140
+
141
+ @servers = []
142
+ configuration.servers.each do |s|
143
+ @servers << [s.klass.new(s), Nonnative::Port.new(s)]
107
144
  end
145
+
146
+ @servers
108
147
  end
109
148
 
110
149
  def services
111
- @services ||= configuration.services.map { |s| Nonnative::Service.new(s) }
150
+ return @services unless @services.nil?
151
+
152
+ @services = []
153
+ configuration.services.each do |s|
154
+ @services << Nonnative::Service.new(s)
155
+ end
156
+
157
+ @services
158
+ end
159
+
160
+ def existing_processes
161
+ @processes || []
162
+ end
163
+
164
+ def existing_servers
165
+ @servers || []
166
+ end
167
+
168
+ def existing_services
169
+ @services || []
112
170
  end
113
171
 
114
- def process(all, type_method, port_method, &)
115
- types = []
116
- pids = []
117
- threads = []
172
+ def service_lifecycle(all, type_method, action)
173
+ all.each_with_object([]) do |service, errors|
174
+ service.send(type_method)
175
+ rescue StandardError => e
176
+ errors << lifecycle_error(action, service, e)
177
+ end
178
+ end
179
+
180
+ def process(all, type_method, port_method, action, &)
181
+ checks = []
182
+ errors = []
118
183
 
119
184
  all.each do |type, port|
120
- types << type
121
- pids << type.send(type_method)
122
- threads << Thread.new { port.send(port_method) }
185
+ values = type.send(type_method)
186
+ checks << [type, values, Thread.new { check_port(port, port_method) }]
187
+ rescue StandardError => e
188
+ errors << lifecycle_error(action, type, e)
123
189
  end
124
190
 
125
- ports = threads.map(&:value)
191
+ errors.concat(yield_results(checks, action, &))
192
+ end
126
193
 
127
- yield_results(types, pids, ports, &)
194
+ def check_port(port, port_method)
195
+ { result: port.send(port_method) }
196
+ rescue StandardError => e
197
+ { error: e }
128
198
  end
129
199
 
130
- def yield_results(all, pids, ports)
131
- all.zip(pids, ports).each do |type, values, result|
132
- yield type.name, values, result
200
+ def yield_results(checks, action, &)
201
+ checks.each_with_object([]) do |(type, values, thread), errors|
202
+ result = thread.value
203
+ if result[:error]
204
+ errors << port_error(action, type, result[:error])
205
+ elsif block_given?
206
+ yield type.name, values, result[:result]
207
+ end
133
208
  end
134
209
  end
210
+
211
+ def lifecycle_error(action, type, error)
212
+ "#{action.to_s.capitalize} failed for #{runner_name(type)}: #{error.class} - #{error.message}"
213
+ end
214
+
215
+ def port_error(action, type, error)
216
+ check = action == :start ? 'readiness' : 'shutdown'
217
+ "#{check.capitalize} check failed for #{runner_name(type)}: #{error.class} - #{error.message}"
218
+ end
219
+
220
+ def runner_name(type)
221
+ name = type.name
222
+ return "runner '#{name}'" if name
223
+
224
+ type.class.to_s
225
+ end
135
226
  end
136
227
  end
@@ -45,11 +45,12 @@ module Nonnative
45
45
  def stop
46
46
  if process_exists?
47
47
  process_kill
48
- proxy.stop
49
48
  wait_stop
50
49
  end
51
50
 
52
51
  pid
52
+ ensure
53
+ proxy.stop
53
54
  end
54
55
 
55
56
  # Returns a memoized memory reader for the spawned process.
@@ -4,5 +4,5 @@ module Nonnative
4
4
  # The current gem version.
5
5
  #
6
6
  # @return [String]
7
- VERSION = '2.1.0'
7
+ VERSION = '2.10.0'
8
8
  end
data/lib/nonnative.rb CHANGED
@@ -53,7 +53,6 @@ require 'rest-client'
53
53
  require 'retriable'
54
54
  require 'concurrent'
55
55
  require 'config'
56
- require 'cucumber'
57
56
  require 'get_process_mem'
58
57
  require 'rspec-benchmark'
59
58
  require 'rspec/expectations'
@@ -109,10 +108,10 @@ require 'nonnative/header'
109
108
  # @see Nonnative::Pool for lifecycle orchestration once started
110
109
  module Nonnative
111
110
  class << self
112
- # Returns the current runner pool (created on {Nonnative.start}).
111
+ # Returns or overrides the current runner pool (created on {Nonnative.start}).
113
112
  #
114
113
  # @return [Nonnative::Pool, nil] the pool instance, or `nil` if not started yet
115
- attr_reader :pool
114
+ attr_accessor :pool
116
115
 
117
116
  # Loads one or more configuration files using the `config` gem.
118
117
  #
@@ -210,13 +209,19 @@ module Nonnative
210
209
  def start
211
210
  @pool ||= Nonnative::Pool.new(configuration)
212
211
  errors = []
213
-
214
- @pool.start do |name, values, result|
212
+ errors.concat(@pool.start do |name, values, result|
215
213
  id, started = values
216
- errors << "Started #{name} with id #{id}, though did respond in time" if !started || !result
217
- end
214
+ errors << "Started #{name} with id #{id}, though did not respond in time" if !started || !result
215
+ end)
216
+ nil
217
+ rescue StandardError => e
218
+ errors << unexpected_lifecycle_error(:start, e)
219
+ ensure
220
+ if errors.any?
221
+ errors.concat(rollback_start)
218
222
 
219
- raise Nonnative::StartError, errors.join("\n") unless errors.empty?
223
+ raise Nonnative::StartError, errors.join("\n")
224
+ end
220
225
  end
221
226
 
222
227
  # Stops all configured processes and servers, then services, and waits for shutdown.
@@ -224,14 +229,16 @@ module Nonnative
224
229
  # @return [void]
225
230
  # @raise [Nonnative::StopError] if one or more runners fail to stop in time
226
231
  def stop
227
- return if @pool.nil?
228
-
229
232
  errors = []
233
+ return if @pool.nil?
230
234
 
231
- @pool.stop do |name, id, result|
232
- errors << "Stopped #{name} with id #{id}, though did respond in time" unless result
233
- end
234
-
235
+ errors.concat(@pool.stop do |name, id, result|
236
+ errors << "Stopped #{name} with id #{id}, though did not respond in time" unless result
237
+ end)
238
+ nil
239
+ rescue StandardError => e
240
+ errors << unexpected_lifecycle_error(:stop, e)
241
+ ensure
235
242
  raise Nonnative::StopError, errors.join("\n") unless errors.empty?
236
243
  end
237
244
 
@@ -242,6 +249,22 @@ module Nonnative
242
249
  @configuration = nil
243
250
  end
244
251
 
252
+ # Closes and clears the memoized logger instance.
253
+ #
254
+ # @return [void]
255
+ def clear_logger
256
+ @logger&.close
257
+ ensure
258
+ @logger = nil
259
+ end
260
+
261
+ # Clears the memoized observability client.
262
+ #
263
+ # @return [void]
264
+ def clear_observability
265
+ @observability = nil
266
+ end
267
+
245
268
  # Clears the memoized pool instance.
246
269
  #
247
270
  # @return [void]
@@ -249,10 +272,12 @@ module Nonnative
249
272
  @pool = nil
250
273
  end
251
274
 
252
- # Clears memoized configuration and pool.
275
+ # Clears memoized configuration, logger, observability client, and pool.
253
276
  #
254
277
  # @return [void]
255
278
  def clear
279
+ clear_logger
280
+ clear_observability
256
281
  clear_configuration
257
282
  clear_pool
258
283
  end
@@ -264,5 +289,24 @@ module Nonnative
264
289
  def reset
265
290
  Nonnative.pool.reset
266
291
  end
292
+
293
+ private
294
+
295
+ def rollback_start
296
+ errors = []
297
+ return errors if @pool.nil?
298
+
299
+ errors.concat(@pool.rollback do |name, id, result|
300
+ errors << "Rollback failed for #{name} with id #{id}, because it did not stop in time" unless result
301
+ end)
302
+ rescue StandardError => e
303
+ errors << unexpected_lifecycle_error(:rollback, e)
304
+ ensure
305
+ clear_pool if errors.empty?
306
+ end
307
+
308
+ def unexpected_lifecycle_error(action, error)
309
+ "#{action.to_s.capitalize} failed with #{error.class}: #{error.message}"
310
+ end
267
311
  end
268
312
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nonnative
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro Falkowski