nonnative 3.36.0 → 3.38.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.
- checksums.yaml +4 -4
- data/README.md +10 -2
- data/lib/nonnative/configuration_server.rb +2 -1
- data/lib/nonnative/fault_injection_proxy.rb +51 -22
- data/lib/nonnative/grpc_server.rb +5 -2
- data/lib/nonnative/http_server.rb +6 -1
- data/lib/nonnative/pool.rb +8 -4
- data/lib/nonnative/server.rb +40 -13
- data/lib/nonnative/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d53bdf381bf4cb897538cc4520e499b0116e8fc31566930ae1612859dda4d6a
|
|
4
|
+
data.tar.gz: 809f8545b4fe5e6d49289840572247148c38f2a0cd3647919b5b77f473b88b5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34adbd873e0eedd1b994aff7afe5ebc33834ab3f820dcbd2b551d619c851b083ae897828c88ad6c5b185b3d174390364aca1fdd9be85440fc2a5175e574eb686
|
|
7
|
+
data.tar.gz: 06ea5ded95fd98a9ba589cf5b5a8a8d567e07e295fbe72445332997ccb21b1649aa8edb0fe21054a17b91c527e023aa4e44c0724ce66ca0e43c4f39c3f7c4a69
|
data/README.md
CHANGED
|
@@ -78,8 +78,9 @@ Common runner fields:
|
|
|
78
78
|
Process/server fields:
|
|
79
79
|
- `ports`: client-facing ports. These are also used for readiness/shutdown port checks.
|
|
80
80
|
- `timeout`: max time (seconds) for each readiness/shutdown check. For processes, the same value also
|
|
81
|
-
bounds optional HTTP/gRPC probes and graceful child exit after the stop signal.
|
|
82
|
-
|
|
81
|
+
bounds optional HTTP/gRPC probes and graceful child exit after the stop signal. For servers, it
|
|
82
|
+
also bounds worker-thread cleanup after the stop hook returns. Defaults to `1.0`. A value of `0`
|
|
83
|
+
fails immediately; setting it to `nil` programmatically does the same.
|
|
83
84
|
- `wait`: small sleep (seconds) between lifecycle steps.
|
|
84
85
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
85
86
|
|
|
@@ -437,6 +438,13 @@ module Nonnative
|
|
|
437
438
|
end
|
|
438
439
|
```
|
|
439
440
|
|
|
441
|
+
Rollback calls `perform_stop` for every successfully constructed server, even when a later
|
|
442
|
+
constructor fails before any server thread starts. Keep `perform_stop` safe in that state and use it
|
|
443
|
+
to release resources acquired during initialization. After `perform_stop` requests shutdown,
|
|
444
|
+
Nonnative waits up to the server `timeout` for the owned thread to finish. A thread that exceeds the
|
|
445
|
+
timeout is terminated and causes `Nonnative.stop` to raise `Nonnative::StopError`; the server can
|
|
446
|
+
still be started again afterward.
|
|
447
|
+
|
|
440
448
|
Set it up programmatically:
|
|
441
449
|
|
|
442
450
|
```ruby
|
|
@@ -14,7 +14,8 @@ module Nonnative
|
|
|
14
14
|
# @return [Class] a class that implements `#initialize(service)` and the hooks expected by {Nonnative::Server}
|
|
15
15
|
attr_accessor :klass
|
|
16
16
|
|
|
17
|
-
# @return [Numeric]
|
|
17
|
+
# @return [Numeric] lifecycle timeout (seconds) used for port checks and worker-thread cleanup
|
|
18
|
+
# (defaults to `1.0`)
|
|
18
19
|
attr_accessor :timeout
|
|
19
20
|
|
|
20
21
|
# @return [String] log file path used by server implementations (for example Puma/gRPC log files)
|
|
@@ -52,6 +52,8 @@ module Nonnative
|
|
|
52
52
|
# @see Nonnative::Proxy
|
|
53
53
|
# @see Nonnative::SocketPairFactory
|
|
54
54
|
class FaultInjectionProxy < Nonnative::Proxy
|
|
55
|
+
# Used both to flush the accept-queue barrier and as the aggregate deadline for draining active
|
|
56
|
+
# worker threads during stop.
|
|
55
57
|
STOP_DRAIN_TIMEOUT = 1
|
|
56
58
|
|
|
57
59
|
class Connection
|
|
@@ -62,10 +64,9 @@ module Nonnative
|
|
|
62
64
|
@socket = socket
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
def
|
|
67
|
+
def close_sockets
|
|
66
68
|
pair&.close
|
|
67
69
|
socket.close unless socket.closed?
|
|
68
|
-
thread&.terminate
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
@@ -88,6 +89,7 @@ module Nonnative
|
|
|
88
89
|
# @return [void]
|
|
89
90
|
def start
|
|
90
91
|
mutex.synchronize { @stopping = false }
|
|
92
|
+
logger
|
|
91
93
|
@tcp_server = ::TCPServer.new(service.host, service.port)
|
|
92
94
|
@thread = Thread.new { perform_start }
|
|
93
95
|
|
|
@@ -100,7 +102,7 @@ module Nonnative
|
|
|
100
102
|
def stop
|
|
101
103
|
server = tcp_server
|
|
102
104
|
mark_stopping
|
|
103
|
-
close_connections
|
|
105
|
+
drain_workers(close_connections)
|
|
104
106
|
close_queued_connections(server)
|
|
105
107
|
server&.close
|
|
106
108
|
|
|
@@ -113,6 +115,7 @@ module Nonnative
|
|
|
113
115
|
|
|
114
116
|
@tcp_server = nil
|
|
115
117
|
@thread = nil
|
|
118
|
+
close_logger
|
|
116
119
|
|
|
117
120
|
Nonnative.logger.info "stopped with host '#{service.host}' and port '#{service.port}' for proxy 'fault_injection'"
|
|
118
121
|
end
|
|
@@ -230,7 +233,7 @@ module Nonnative
|
|
|
230
233
|
|
|
231
234
|
private
|
|
232
235
|
|
|
233
|
-
attr_reader :tcp_server, :thread, :connections, :mutex, :state, :
|
|
236
|
+
attr_reader :tcp_server, :thread, :connections, :mutex, :state, :stopping
|
|
234
237
|
|
|
235
238
|
def perform_start
|
|
236
239
|
loop do
|
|
@@ -240,10 +243,9 @@ module Nonnative
|
|
|
240
243
|
local_socket.close
|
|
241
244
|
next
|
|
242
245
|
end
|
|
243
|
-
|
|
244
|
-
accept_connection
|
|
246
|
+
Thread.start(local_socket) do |accepted_socket|
|
|
247
|
+
accept_connection(id, accepted_socket) if register_connection_thread(id)
|
|
245
248
|
end
|
|
246
|
-
attach_connection_thread(id, connection_thread)
|
|
247
249
|
end
|
|
248
250
|
rescue IOError, Errno::EBADF
|
|
249
251
|
nil
|
|
@@ -281,8 +283,29 @@ module Nonnative
|
|
|
281
283
|
active_connections.each do |id, connection|
|
|
282
284
|
close_connection(id, connection)
|
|
283
285
|
end
|
|
286
|
+
|
|
287
|
+
active_connections.filter_map { |_id, connection| connection.thread }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def drain_workers(workers)
|
|
291
|
+
deadline = monotonic_now + STOP_DRAIN_TIMEOUT
|
|
292
|
+
workers.each do |worker|
|
|
293
|
+
worker.join(remaining_time(deadline))
|
|
294
|
+
terminate_worker(worker)
|
|
295
|
+
end
|
|
284
296
|
end
|
|
285
297
|
|
|
298
|
+
def terminate_worker(worker)
|
|
299
|
+
return unless worker.alive?
|
|
300
|
+
|
|
301
|
+
worker.kill
|
|
302
|
+
worker.join
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def remaining_time(deadline) = [deadline - monotonic_now, 0].max
|
|
306
|
+
|
|
307
|
+
def monotonic_now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
308
|
+
|
|
286
309
|
def close_queued_connections(server)
|
|
287
310
|
return unless server
|
|
288
311
|
|
|
@@ -309,9 +332,7 @@ module Nonnative
|
|
|
309
332
|
wait
|
|
310
333
|
end
|
|
311
334
|
|
|
312
|
-
def read_state
|
|
313
|
-
mutex.synchronize { state }
|
|
314
|
-
end
|
|
335
|
+
def read_state = mutex.synchronize { state }
|
|
315
336
|
|
|
316
337
|
def register_connection(id, socket)
|
|
317
338
|
mutex.synchronize do
|
|
@@ -322,26 +343,34 @@ module Nonnative
|
|
|
322
343
|
end
|
|
323
344
|
end
|
|
324
345
|
|
|
325
|
-
def mark_stopping
|
|
326
|
-
mutex.synchronize { @stopping = true }
|
|
327
|
-
end
|
|
346
|
+
def mark_stopping = mutex.synchronize { @stopping = true }
|
|
328
347
|
|
|
329
|
-
def
|
|
330
|
-
mutex.synchronize
|
|
331
|
-
|
|
348
|
+
def register_connection_thread(id)
|
|
349
|
+
mutex.synchronize do
|
|
350
|
+
connection = connections[id]
|
|
351
|
+
return false unless connection && !stopping
|
|
332
352
|
|
|
333
|
-
|
|
334
|
-
|
|
353
|
+
connection.thread = Thread.current
|
|
354
|
+
true
|
|
355
|
+
end
|
|
335
356
|
end
|
|
336
357
|
|
|
337
|
-
def
|
|
338
|
-
|
|
339
|
-
|
|
358
|
+
def attach_connection_pair(id, pair) = mutex.synchronize { connections[id]&.pair = pair }
|
|
359
|
+
|
|
360
|
+
def delete_connection(id) = mutex.synchronize { connections.delete(id) }
|
|
340
361
|
|
|
341
362
|
def close_connection(id, connection)
|
|
342
363
|
Nonnative.logger.info "closing connection for '#{id}' for proxy 'fault_injection'"
|
|
343
364
|
|
|
344
|
-
connection.
|
|
365
|
+
connection.close_sockets
|
|
345
366
|
end
|
|
367
|
+
|
|
368
|
+
def close_logger
|
|
369
|
+
@logger&.close
|
|
370
|
+
ensure
|
|
371
|
+
@logger = nil
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def logger = (@logger ||= Logger.new(service.proxy.log))
|
|
346
375
|
end
|
|
347
376
|
end
|
|
@@ -45,11 +45,14 @@ module Nonnative
|
|
|
45
45
|
server.run
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# Stops the gRPC server.
|
|
48
|
+
# Stops the gRPC server after it has started.
|
|
49
|
+
#
|
|
50
|
+
# Construction rollback can call this hook before `GRPC::RpcServer#run`; gRPC rejects `stop` in
|
|
51
|
+
# that state and has not acquired its listener yet, so there is nothing to release.
|
|
49
52
|
#
|
|
50
53
|
# @return [void]
|
|
51
54
|
def perform_stop
|
|
52
|
-
server.stop
|
|
55
|
+
server.stop unless server.running_state == :not_started
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
# Waits until the gRPC server reports it is running, or the configured timeout elapses.
|
|
@@ -72,9 +72,14 @@ module Nonnative
|
|
|
72
72
|
|
|
73
73
|
# Gracefully shuts down the Puma server.
|
|
74
74
|
#
|
|
75
|
+
# Signals shutdown through Puma's own control pipe (`Puma::Server#stop`) rather than calling
|
|
76
|
+
# `graceful_shutdown` directly, so the accept loop wakes up reliably; calling `graceful_shutdown`
|
|
77
|
+
# without that signal leaves the accept loop dependent on noticing a closed socket, which is not
|
|
78
|
+
# guaranteed to happen promptly on every platform.
|
|
79
|
+
#
|
|
75
80
|
# @return [void]
|
|
76
81
|
def perform_stop
|
|
77
|
-
server.
|
|
82
|
+
server.stop(true)
|
|
78
83
|
ensure
|
|
79
84
|
close_log
|
|
80
85
|
end
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -46,8 +46,8 @@ module Nonnative
|
|
|
46
46
|
|
|
47
47
|
# Stops all configured runners and collects lifecycle and shutdown errors.
|
|
48
48
|
#
|
|
49
|
-
# Processes and servers are stopped first and checked for shutdown,
|
|
50
|
-
# stopped when configured.
|
|
49
|
+
# Processes and servers are stopped first and checked for shutdown, including bounded server
|
|
50
|
+
# thread cleanup, then service proxies are stopped when configured.
|
|
51
51
|
#
|
|
52
52
|
# @return [Array<String>] lifecycle and shutdown-check errors collected while stopping
|
|
53
53
|
def stop
|
|
@@ -240,7 +240,7 @@ module Nonnative
|
|
|
240
240
|
id, stopped = Array(values).then { |v| [v.first, v.fetch(1, true)] }
|
|
241
241
|
errors = []
|
|
242
242
|
errors << "Stopped #{runner.name} with id #{id}, though did not respond in time for #{port.description}" unless ready
|
|
243
|
-
errors << "Stopped #{runner.name} with id #{id}, though the
|
|
243
|
+
errors << "Stopped #{runner.name} with id #{id}, though the #{exit_subject(runner)} did not exit in time" unless stopped
|
|
244
244
|
errors
|
|
245
245
|
end
|
|
246
246
|
|
|
@@ -248,7 +248,7 @@ module Nonnative
|
|
|
248
248
|
id, stopped = Array(values).then { |v| [v.first, v.fetch(1, true)] }
|
|
249
249
|
errors = []
|
|
250
250
|
errors << "Rollback failed for #{runner.name} with id #{id}, because it did not stop in time for #{port.description}" unless ready
|
|
251
|
-
errors << "Rollback failed for #{runner.name} with id #{id}, because the
|
|
251
|
+
errors << "Rollback failed for #{runner.name} with id #{id}, because the #{exit_subject(runner)} did not exit in time" unless stopped
|
|
252
252
|
errors
|
|
253
253
|
end
|
|
254
254
|
|
|
@@ -265,6 +265,10 @@ module Nonnative
|
|
|
265
265
|
"Started #{runner_name(service)}, though did not respond in time for readiness: #{checks.map(&:endpoint).join(', ')}"
|
|
266
266
|
end
|
|
267
267
|
|
|
268
|
+
def exit_subject(runner)
|
|
269
|
+
runner.is_a?(Nonnative::Server) ? 'server thread' : 'process'
|
|
270
|
+
end
|
|
271
|
+
|
|
268
272
|
def runner_name(type)
|
|
269
273
|
name = type.name
|
|
270
274
|
return "runner '#{name}'" if name
|
data/lib/nonnative/server.rb
CHANGED
|
@@ -12,6 +12,9 @@ module Nonnative
|
|
|
12
12
|
# - {#perform_start} (to bind/listen and begin serving), and
|
|
13
13
|
# - {#perform_stop} (to gracefully shut down).
|
|
14
14
|
#
|
|
15
|
+
# {#stop} calls the stop hook once for every successfully constructed lifecycle, even if startup
|
|
16
|
+
# never created a worker thread. This lets rollback release resources acquired by a constructor.
|
|
17
|
+
#
|
|
15
18
|
# The underlying configuration is a {Nonnative::ConfigurationServer}.
|
|
16
19
|
#
|
|
17
20
|
# @see Nonnative::ConfigurationServer
|
|
@@ -22,17 +25,22 @@ module Nonnative
|
|
|
22
25
|
super
|
|
23
26
|
|
|
24
27
|
@timeout = Nonnative::Timeout.new(service.timeout)
|
|
28
|
+
@cleanup_required = true
|
|
25
29
|
end
|
|
26
30
|
|
|
27
|
-
# Starts the server thread if it is not already
|
|
31
|
+
# Starts the server thread if it is not already running.
|
|
32
|
+
#
|
|
33
|
+
# A thread retained from a stop that exceeded its timeout is treated as not running, so a server
|
|
34
|
+
# can be restarted even if a prior {#stop} never observed that thread's actual termination.
|
|
28
35
|
#
|
|
29
36
|
# @return [Array<(Integer, TrueClass)>]
|
|
30
37
|
# a tuple of:
|
|
31
38
|
# - a stable identifier for this server instance (`object_id`)
|
|
32
39
|
# - `true` (thread creation itself is considered started; readiness is checked separately)
|
|
33
40
|
def start
|
|
34
|
-
unless thread
|
|
41
|
+
unless thread&.alive?
|
|
35
42
|
@error = nil
|
|
43
|
+
@cleanup_required = true
|
|
36
44
|
@thread = Thread.new do
|
|
37
45
|
perform_start
|
|
38
46
|
rescue StandardError => e
|
|
@@ -49,23 +57,27 @@ module Nonnative
|
|
|
49
57
|
[object_id, true]
|
|
50
58
|
end
|
|
51
59
|
|
|
52
|
-
# Stops the server if
|
|
60
|
+
# Stops the server if its current lifecycle still requires cleanup.
|
|
53
61
|
#
|
|
54
|
-
# Calls {#perform_stop}
|
|
62
|
+
# Calls {#perform_stop} even if startup never created a worker thread, then waits up to the
|
|
63
|
+
# configured timeout for any owned thread to finish. A thread that exceeds the timeout is
|
|
64
|
+
# terminated and reported through {Nonnative::Pool}. A later stop retries draining a retained
|
|
65
|
+
# thread without calling {#perform_stop} again.
|
|
55
66
|
#
|
|
56
|
-
# @return [Integer] the server identifier
|
|
67
|
+
# @return [Integer, Array<(Integer, FalseClass)>] the server identifier when cleanup finishes, or
|
|
68
|
+
# the identifier and `false` when the owned thread exceeds the configured timeout
|
|
57
69
|
def stop
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
thread.terminate
|
|
70
|
+
owned_thread = thread
|
|
71
|
+
return object_id unless @cleanup_required || owned_thread
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
perform_cleanup
|
|
74
|
+
stopped = drain_thread(owned_thread) == :drained
|
|
75
|
+
wait_stop
|
|
76
|
+
@thread = nil unless owned_thread&.alive?
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
end
|
|
78
|
+
Nonnative.logger.info "stopped server '#{service.name}'" if stopped
|
|
67
79
|
|
|
68
|
-
object_id
|
|
80
|
+
stopped ? object_id : [object_id, false]
|
|
69
81
|
end
|
|
70
82
|
|
|
71
83
|
# Describes how the server thread terminated before becoming ready, for lifecycle diagnostics.
|
|
@@ -85,5 +97,20 @@ module Nonnative
|
|
|
85
97
|
private
|
|
86
98
|
|
|
87
99
|
attr_reader :thread, :timeout, :error
|
|
100
|
+
|
|
101
|
+
def perform_cleanup
|
|
102
|
+
return unless @cleanup_required
|
|
103
|
+
|
|
104
|
+
perform_stop
|
|
105
|
+
@cleanup_required = false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def drain_thread(owned_thread)
|
|
109
|
+
return :drained if owned_thread.nil? || !owned_thread.alive?
|
|
110
|
+
return :drained if timeout.perform { owned_thread.join }
|
|
111
|
+
|
|
112
|
+
owned_thread.terminate
|
|
113
|
+
:timed_out
|
|
114
|
+
end
|
|
88
115
|
end
|
|
89
116
|
end
|
data/lib/nonnative/version.rb
CHANGED