gritz-core 0.2.0 → 0.3.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/CHANGELOG.md +7 -0
- data/README.md +2 -0
- data/lib/gritz/cli.rb +31 -5
- data/lib/gritz/configuration.rb +17 -8
- data/lib/gritz/context.rb +31 -2
- data/lib/gritz/controller.rb +14 -0
- data/lib/gritz/core/version.rb +1 -1
- data/lib/gritz/dispatcher.rb +9 -3
- data/lib/gritz/metrics/aggregator.rb +119 -0
- data/lib/gritz/metrics/recorder.rb +89 -0
- data/lib/gritz/middleware/exception_mapper.rb +1 -3
- data/lib/gritz/middleware/logging.rb +27 -3
- data/lib/gritz/middleware/metrics.rb +29 -0
- data/lib/gritz/middleware/stack.rb +1 -1
- data/lib/gritz/supervisor/admin_server.rb +121 -0
- data/lib/gritz/supervisor/launcher.rb +381 -0
- data/lib/gritz/supervisor/master.rb +220 -29
- data/lib/gritz/supervisor/status_channel.rb +15 -3
- data/lib/gritz/supervisor/worker_handle.rb +4 -3
- data/lib/gritz/testing/cluster.rb +43 -7
- data/lib/gritz/worker/runner.rb +108 -16
- metadata +26 -1
data/lib/gritz/worker/runner.rb
CHANGED
|
@@ -7,27 +7,37 @@ module Gritz
|
|
|
7
7
|
# Owns transport resources and heartbeats in one serving process.
|
|
8
8
|
# @api private
|
|
9
9
|
class Runner
|
|
10
|
-
def initialize(index:, config:, logger:, status_io: nil)
|
|
10
|
+
def initialize(index:, config:, logger:, status_io: nil, owner_channel: nil)
|
|
11
11
|
@index = index
|
|
12
12
|
@config = config
|
|
13
13
|
@logger = logger
|
|
14
|
-
@
|
|
14
|
+
@owner_channel = owner_channel
|
|
15
|
+
@status = owner_channel || (Supervisor::StatusChannel.new(status_io) if status_io)
|
|
16
|
+
@recorder = Metrics::Recorder.new
|
|
17
|
+
@metrics = Metrics::Aggregator.new
|
|
18
|
+
@metric_seq = 0
|
|
19
|
+
@born_at = monotonic
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
def run
|
|
18
23
|
@exit_status = 0
|
|
19
24
|
begin
|
|
20
|
-
@signals = Supervisor::SignalQueue.new(signals: %w[TERM INT QUIT HUP])
|
|
25
|
+
@signals = Supervisor::SignalQueue.new(signals: %w[TERM INT QUIT HUP USR1 USR2])
|
|
21
26
|
report("booting")
|
|
22
27
|
require "gritz/native"
|
|
23
28
|
@boot_started = true
|
|
24
29
|
@config.preload! unless @config.preload_app?
|
|
25
30
|
@config.run_hooks(:on_worker_boot, @index)
|
|
26
31
|
router = Router.new(controllers: @config.controllers, strict: @config.strict_routes, logger: @logger)
|
|
27
|
-
dispatcher = Dispatcher.new(router:, middleware: @config.middleware, logger: @logger
|
|
32
|
+
dispatcher = Dispatcher.new(router:, middleware: @config.middleware, logger: @logger, metrics: @recorder,
|
|
33
|
+
worker: @index, log_format: @config.log_format, log_redact: @config.log_redact)
|
|
28
34
|
@adapter = Transport::Native.new(config: @config, dispatcher:, logger: @logger)
|
|
29
35
|
@port = @adapter.bind(@config.bind)
|
|
30
36
|
@adapter.start
|
|
37
|
+
unless @status
|
|
38
|
+
@admin = Supervisor::AdminServer.new(bind: @config.admin_bind, status: -> { snapshot }, ready: -> { ready? },
|
|
39
|
+
metrics: -> { @metrics.render(workers: [@last_status].compact) }, logger: @logger)
|
|
40
|
+
end
|
|
31
41
|
@logger.info("Gritz #{Core::VERSION} listening on #{@config.bind.sub(/:\d+\z/, ":#{@port}")}")
|
|
32
42
|
report("ready")
|
|
33
43
|
serve
|
|
@@ -50,35 +60,99 @@ module Gritz
|
|
|
50
60
|
@signals.drain.each do |name|
|
|
51
61
|
case name
|
|
52
62
|
when "HUP" then @logger.reopen
|
|
63
|
+
when "USR1"
|
|
64
|
+
if @config.workers.zero?
|
|
65
|
+
@logger.warn("USR1 requires workers > 0; use USR2 to reload a single-process server")
|
|
66
|
+
else
|
|
67
|
+
drain
|
|
68
|
+
end
|
|
69
|
+
when "USR2"
|
|
70
|
+
if @owner_channel
|
|
71
|
+
@pending_reexec = true
|
|
72
|
+
else
|
|
73
|
+
@logger.warn("USR2 requires the gritz launcher")
|
|
74
|
+
end
|
|
53
75
|
when "QUIT"
|
|
54
|
-
|
|
76
|
+
drain
|
|
55
77
|
@adapter.kill
|
|
56
78
|
@transport_stopped = true
|
|
57
79
|
return 0
|
|
58
80
|
when "TERM", "INT"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@
|
|
62
|
-
return 0
|
|
81
|
+
drain
|
|
82
|
+
# Supervised children have already waited in the master's drain phase.
|
|
83
|
+
@drain_until ||= monotonic + (@config.workers.zero? ? @config.drain_delay : 0)
|
|
63
84
|
end
|
|
64
85
|
end
|
|
65
86
|
now = monotonic
|
|
87
|
+
if @drain_until && now >= @drain_until
|
|
88
|
+
@stop_deadline = now + @config.shutdown_timeout
|
|
89
|
+
@adapter.stop(deadline: Time.now + @config.shutdown_timeout)
|
|
90
|
+
@transport_stopped = true
|
|
91
|
+
return 0
|
|
92
|
+
end
|
|
93
|
+
@status&.flush
|
|
94
|
+
if @pending_reexec && @owner_channel.write(type: "reexec", pid: Process.pid)
|
|
95
|
+
@pending_reexec = false
|
|
96
|
+
end
|
|
97
|
+
publish_metrics
|
|
98
|
+
@admin&.poll
|
|
66
99
|
if now >= next_status
|
|
67
|
-
report("ready")
|
|
100
|
+
report(@draining ? "draining" : "ready")
|
|
68
101
|
next_status = now + @config.status_interval
|
|
69
102
|
end
|
|
70
|
-
|
|
103
|
+
next_event = [next_status, @drain_until].compact.min
|
|
104
|
+
IO.select([@signals.io, *@admin&.ios.to_a], nil, nil, (next_event - monotonic).clamp(0, 0.05))
|
|
71
105
|
end
|
|
72
106
|
end
|
|
73
107
|
|
|
74
108
|
def report(state, include_stats: true, **extra)
|
|
75
|
-
return unless @status
|
|
76
|
-
|
|
77
109
|
stats = include_stats && @adapter ? @adapter.stats : {}
|
|
110
|
+
@recorder.observe_rejected(stats.fetch(:rejected_total, 0)) if include_stats && @adapter
|
|
111
|
+
evaluate_health if state == "ready"
|
|
78
112
|
row = { inflight: 0, capacity: @config.threads, requests_total: 0, oldest_inflight_age: 0 }.merge(stats)
|
|
79
113
|
row.merge!(pid: Process.pid, index: @index, state:, ts: monotonic, port: @port,
|
|
80
|
-
busy_threads: stats.fetch(:busy_threads, stats.fetch(:busy, 0))
|
|
81
|
-
|
|
114
|
+
busy_threads: stats.fetch(:busy_threads, stats.fetch(:busy, 0)), healthy: state == "ready" && @healthy,
|
|
115
|
+
checks: @checks || {}, worker_started_at: @born_at)
|
|
116
|
+
@last_status = row.merge(extra)
|
|
117
|
+
@status&.write(@owner_channel ? snapshot : @last_status)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def snapshot
|
|
121
|
+
{ type: "status", pid: Process.pid, state: @draining ? "draining" : "running", desired: 1, workers: [@last_status].compact }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def ready? = !@draining && @last_status && @last_status[:state] == "ready" && @last_status[:healthy]
|
|
125
|
+
|
|
126
|
+
def evaluate_health
|
|
127
|
+
@checks = @config.health_checks.to_h do |name, callback|
|
|
128
|
+
[name, callback.call ? true : false]
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
@logger.warn("Health check #{name}: #{e.message}")
|
|
131
|
+
[name, false]
|
|
132
|
+
end
|
|
133
|
+
@healthy = @adapter.update_health(ready: true, checks: @checks)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def drain
|
|
137
|
+
return if @draining
|
|
138
|
+
|
|
139
|
+
@draining = true
|
|
140
|
+
@adapter.drain!
|
|
141
|
+
report("draining")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def publish_metrics
|
|
145
|
+
@pending_delta ||= @recorder.take_delta
|
|
146
|
+
return true unless @pending_delta
|
|
147
|
+
|
|
148
|
+
packet = { type: "metrics", pid: Process.pid, worker_pid: Process.pid, worker_started_at: @born_at,
|
|
149
|
+
seq: @metric_seq + 1, delta: @pending_delta }
|
|
150
|
+
accepted = @status ? @status.write(packet) : @metrics.apply(self, packet)
|
|
151
|
+
if accepted
|
|
152
|
+
@metric_seq += 1
|
|
153
|
+
@pending_delta = nil
|
|
154
|
+
end
|
|
155
|
+
accepted
|
|
82
156
|
end
|
|
83
157
|
|
|
84
158
|
def fail_worker(error)
|
|
@@ -90,10 +164,28 @@ module Gritz
|
|
|
90
164
|
def finish
|
|
91
165
|
cleanup { @adapter&.kill unless @transport_stopped }
|
|
92
166
|
cleanup { @config.run_hooks(:on_worker_shutdown, @index) if @boot_started }
|
|
93
|
-
|
|
167
|
+
cleanup { @recorder.observe_rejected(@adapter.stats.fetch(:rejected_total, 0)) if @adapter }
|
|
168
|
+
deadline = @stop_deadline || (monotonic + @config.shutdown_timeout)
|
|
169
|
+
loop do
|
|
170
|
+
@status&.flush
|
|
171
|
+
accepted = publish_metrics
|
|
172
|
+
@pending_delta ||= @recorder.take_delta if accepted
|
|
173
|
+
break if accepted && !@pending_delta && (@status.nil? || @status.flush)
|
|
174
|
+
break if monotonic >= deadline || @status&.closed?
|
|
175
|
+
|
|
176
|
+
@status&.io&.wait_writable((deadline - monotonic).clamp(0, 0.01))
|
|
177
|
+
end
|
|
178
|
+
until report("stopped", include_stats: false, exit_status: @exit_status) != false
|
|
179
|
+
break if monotonic >= deadline || @status&.closed?
|
|
180
|
+
|
|
181
|
+
@status.flush
|
|
182
|
+
@status.io.wait_writable((deadline - monotonic).clamp(0, 0.01)) unless @status.closed?
|
|
183
|
+
end
|
|
184
|
+
@status.io.wait_writable((deadline - monotonic).clamp(0, 0.01)) until !@status || @status.flush || @status.closed? || monotonic >= deadline
|
|
94
185
|
ensure
|
|
95
186
|
begin
|
|
96
187
|
@signals&.close
|
|
188
|
+
@admin&.close
|
|
97
189
|
ensure
|
|
98
190
|
@status&.close
|
|
99
191
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gritz-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
@@ -9,6 +9,26 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fiddle
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3'
|
|
12
32
|
- !ruby/object:Gem::Dependency
|
|
13
33
|
name: google-protobuf
|
|
14
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -113,12 +133,17 @@ files:
|
|
|
113
133
|
- lib/gritz/errors.rb
|
|
114
134
|
- lib/gritz/fork_guard.rb
|
|
115
135
|
- lib/gritz/method_descriptor.rb
|
|
136
|
+
- lib/gritz/metrics/aggregator.rb
|
|
137
|
+
- lib/gritz/metrics/recorder.rb
|
|
116
138
|
- lib/gritz/middleware/context.rb
|
|
117
139
|
- lib/gritz/middleware/exception_mapper.rb
|
|
118
140
|
- lib/gritz/middleware/logging.rb
|
|
141
|
+
- lib/gritz/middleware/metrics.rb
|
|
119
142
|
- lib/gritz/middleware/request_id.rb
|
|
120
143
|
- lib/gritz/middleware/stack.rb
|
|
121
144
|
- lib/gritz/router.rb
|
|
145
|
+
- lib/gritz/supervisor/admin_server.rb
|
|
146
|
+
- lib/gritz/supervisor/launcher.rb
|
|
122
147
|
- lib/gritz/supervisor/master.rb
|
|
123
148
|
- lib/gritz/supervisor/signal_queue.rb
|
|
124
149
|
- lib/gritz/supervisor/status_channel.rb
|