raptor 0.25.0 → 0.27.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/.buildkite/pipeline.yml +34 -8
- data/AGENTS.md +25 -0
- data/CHANGELOG.md +35 -25
- data/README.md +82 -67
- data/Rakefile +10 -3
- data/lib/rackup/handler/raptor.rb +19 -12
- data/lib/raptor/binder.rb +17 -5
- data/lib/raptor/cli.rb +32 -2
- data/lib/raptor/cluster.rb +62 -141
- data/lib/raptor/embedded.rb +225 -0
- data/lib/raptor/plugin.rb +1 -0
- data/lib/raptor/reactor.rb +21 -4
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor/worker.rb +200 -0
- data/sig/generated/rackup/handler/raptor.rbs +8 -8
- data/sig/generated/raptor/cli.rbs +5 -0
- data/sig/generated/raptor/cluster.rbs +22 -35
- data/sig/generated/raptor/embedded.rbs +93 -0
- data/sig/generated/raptor/plugin.rbs +1 -0
- data/sig/generated/raptor/worker.rbs +77 -0
- metadata +6 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "atomic-ruby/atom"
|
|
5
|
+
require "atomic-ruby/atomic_condition_variable"
|
|
6
|
+
|
|
7
|
+
require_relative "cli"
|
|
8
|
+
require_relative "worker"
|
|
9
|
+
|
|
10
|
+
module Raptor
|
|
11
|
+
# Serves an existing Rack application in the caller's process without
|
|
12
|
+
# installing signal handlers or changing process-wide configuration.
|
|
13
|
+
#
|
|
14
|
+
class Embedded
|
|
15
|
+
# @rbs @app: untyped
|
|
16
|
+
# @rbs @options: Hash[Symbol, untyped]
|
|
17
|
+
# @rbs @binds: Array[String]
|
|
18
|
+
# @rbs @log: bool
|
|
19
|
+
# @rbs @lifecycle_owner: Atom
|
|
20
|
+
# @rbs @lifecycle_available: AtomicConditionVariable
|
|
21
|
+
# @rbs @started: bool
|
|
22
|
+
# @rbs @stopped: bool
|
|
23
|
+
# @rbs @binder: Binder?
|
|
24
|
+
# @rbs @worker: Worker?
|
|
25
|
+
|
|
26
|
+
attr_reader :port #: Integer
|
|
27
|
+
attr_reader :addresses #: Array[String]
|
|
28
|
+
|
|
29
|
+
# Creates an in-process server. A port of zero selects an available port.
|
|
30
|
+
# Thread and protocol options follow the regular server configuration.
|
|
31
|
+
# Set log to report startup information.
|
|
32
|
+
#
|
|
33
|
+
# @rbs (untyped app, ?host: String, ?port: Integer, ?log: bool, **untyped options) -> void
|
|
34
|
+
def initialize(app, host: "127.0.0.1", port: 0, log: false, **options)
|
|
35
|
+
@app = app
|
|
36
|
+
@port = port
|
|
37
|
+
@log = log
|
|
38
|
+
@addresses = []
|
|
39
|
+
@options = CLI::DEFAULT_OPTIONS.merge(options)
|
|
40
|
+
CLI::NESTED_OPTION_KEYS.each do |key|
|
|
41
|
+
@options[key] = CLI::DEFAULT_OPTIONS[key].merge(options[key] || {})
|
|
42
|
+
end
|
|
43
|
+
@options[:max_threads] = CLI.parse_max_threads(@options[:max_threads], threads: @options[:threads])
|
|
44
|
+
@binds = options[:binds] || ["tcp://#{host}:#{port}"]
|
|
45
|
+
@lifecycle_owner = Atom.new(nil)
|
|
46
|
+
@lifecycle_available = AtomicConditionVariable.new
|
|
47
|
+
@started = false
|
|
48
|
+
@stopped = false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Starts serving and returns once the request-handling stack is ready.
|
|
52
|
+
# Startup errors are raised in the calling thread.
|
|
53
|
+
#
|
|
54
|
+
# @rbs () -> Embedded
|
|
55
|
+
def start
|
|
56
|
+
with_lifecycle do
|
|
57
|
+
raise ThreadError, "server has already been started or stopped" if @started || @stopped
|
|
58
|
+
|
|
59
|
+
@started = true
|
|
60
|
+
begin
|
|
61
|
+
@binder = Binder.new(@binds, socket_backlog: @options[:socket_backlog])
|
|
62
|
+
@port = @binder.server_port
|
|
63
|
+
@addresses = @binder.addresses
|
|
64
|
+
start_worker
|
|
65
|
+
rescue
|
|
66
|
+
@stopped = true
|
|
67
|
+
@binder&.close
|
|
68
|
+
raise
|
|
69
|
+
end
|
|
70
|
+
log_initialization if @log
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Starts serving and waits until shutdown.
|
|
77
|
+
#
|
|
78
|
+
# @rbs () -> void
|
|
79
|
+
def run
|
|
80
|
+
start
|
|
81
|
+
join
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Drains serving work while retaining the bound listeners for resume.
|
|
85
|
+
# Connections may queue, but requests are not served while paused.
|
|
86
|
+
# Existing keep-alive connections close. Repeated pauses have no effect.
|
|
87
|
+
#
|
|
88
|
+
# Pause before master forks and resume in the parent. Close the inherited
|
|
89
|
+
# instance before other worker boot hooks, and shut down when the plugin stops.
|
|
90
|
+
#
|
|
91
|
+
# @example From a plugin's start callback
|
|
92
|
+
# @server = Raptor::Embedded.new(app, port: 9293).start
|
|
93
|
+
# cluster.options[:before_fork] << proc { @server.pause }
|
|
94
|
+
# cluster.options[:after_fork] << proc { @server.resume }
|
|
95
|
+
# cluster.options[:before_worker_boot].unshift(proc { @server.shutdown })
|
|
96
|
+
#
|
|
97
|
+
# @rbs () -> Embedded
|
|
98
|
+
def pause
|
|
99
|
+
with_lifecycle do
|
|
100
|
+
raise ThreadError, "server is not running" unless @started && !@stopped
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
@worker&.shutdown(close_listeners: false)
|
|
104
|
+
@worker = nil
|
|
105
|
+
rescue
|
|
106
|
+
@stopped = true
|
|
107
|
+
@binder&.close
|
|
108
|
+
raise
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Resumes serving on the retained listeners. Has no effect while running.
|
|
116
|
+
# Shutdown remains terminal. Startup failures release the listeners and propagate.
|
|
117
|
+
#
|
|
118
|
+
# @rbs () -> Embedded
|
|
119
|
+
def resume
|
|
120
|
+
with_lifecycle do
|
|
121
|
+
raise ThreadError, "server has not been started or has been stopped" unless @started && !@stopped
|
|
122
|
+
|
|
123
|
+
begin
|
|
124
|
+
start_worker unless @worker
|
|
125
|
+
rescue
|
|
126
|
+
@stopped = true
|
|
127
|
+
@binder&.close
|
|
128
|
+
raise
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
self
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Drains requests and closes the server's sockets, threads, and Ractors.
|
|
136
|
+
# Calling shutdown more than once has no effect.
|
|
137
|
+
#
|
|
138
|
+
# @rbs () -> void
|
|
139
|
+
def shutdown
|
|
140
|
+
with_lifecycle do
|
|
141
|
+
return if @stopped
|
|
142
|
+
|
|
143
|
+
@stopped = true
|
|
144
|
+
begin
|
|
145
|
+
@worker&.shutdown
|
|
146
|
+
ensure
|
|
147
|
+
@binder&.close
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Waits across pauses until termination and propagates serving failures.
|
|
153
|
+
#
|
|
154
|
+
# @rbs () -> void
|
|
155
|
+
def join
|
|
156
|
+
started = with_lifecycle { @started }
|
|
157
|
+
raise ThreadError, "server has not been started" unless started
|
|
158
|
+
|
|
159
|
+
loop do
|
|
160
|
+
worker, stopped = with_lifecycle { [@worker, @stopped] }
|
|
161
|
+
break if stopped
|
|
162
|
+
|
|
163
|
+
if worker
|
|
164
|
+
worker.join
|
|
165
|
+
break if with_lifecycle { @worker.equal?(worker) }
|
|
166
|
+
else
|
|
167
|
+
@lifecycle_available.wait { @worker || @stopped }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
ensure
|
|
171
|
+
shutdown if started
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
# @rbs () -> void
|
|
177
|
+
def start_worker
|
|
178
|
+
@worker = Worker.new(
|
|
179
|
+
@app,
|
|
180
|
+
listeners: @binder.listeners,
|
|
181
|
+
port: @port,
|
|
182
|
+
options: @options,
|
|
183
|
+
http1_ractors: @options[:http1][:ractors] || Cluster.default_http1_ractor_count(1),
|
|
184
|
+
http2_ractors: @options[:http2][:ractors] || Cluster.default_http2_ractor_count(1)
|
|
185
|
+
)
|
|
186
|
+
@worker.start
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @rbs () -> void
|
|
190
|
+
def log_initialization
|
|
191
|
+
environment = @options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
192
|
+
thread_count = @options[:threads]
|
|
193
|
+
max_threads = @options[:max_threads]
|
|
194
|
+
thread_limit = if max_threads == Float::INFINITY
|
|
195
|
+
" (scaling, no limit)"
|
|
196
|
+
elsif max_threads > thread_count
|
|
197
|
+
" (scaling up to #{max_threads})"
|
|
198
|
+
else
|
|
199
|
+
""
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
Log.info "Server initializing:"
|
|
203
|
+
Log.info "├─ Version: #{VERSION}"
|
|
204
|
+
Log.info "├─ Ruby Version: #{RUBY_DESCRIPTION}"
|
|
205
|
+
Log.info "├─ Environment: #{environment}"
|
|
206
|
+
Log.info "├─ PID: #{Process.pid}"
|
|
207
|
+
Log.info "├─ #{thread_count} worker thread#{"s" if thread_count > 1}#{thread_limit}"
|
|
208
|
+
Log.info "└─ Listening on #{@addresses.join(", ")}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# @rbs () { () -> untyped } -> untyped
|
|
212
|
+
def with_lifecycle
|
|
213
|
+
thread = Thread.current
|
|
214
|
+
@lifecycle_available.wait do
|
|
215
|
+
@lifecycle_owner.swap { |owner| owner || thread }.equal?(thread)
|
|
216
|
+
end
|
|
217
|
+
yield
|
|
218
|
+
ensure
|
|
219
|
+
if @lifecycle_owner.value.equal?(thread)
|
|
220
|
+
@lifecycle_owner.swap { nil }
|
|
221
|
+
@lifecycle_available.broadcast
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
data/lib/raptor/plugin.rb
CHANGED
|
@@ -53,6 +53,7 @@ module Raptor
|
|
|
53
53
|
|
|
54
54
|
# Starts in the master before workers fork. Override to open resources
|
|
55
55
|
# and append callbacks to the cluster's lifecycle hook arrays.
|
|
56
|
+
# See {Cluster#initialize} for hooks and {Embedded#pause} for fork-safe Rack listeners.
|
|
56
57
|
#
|
|
57
58
|
# @rbs (Cluster cluster) -> void
|
|
58
59
|
def start(cluster)
|
data/lib/raptor/reactor.rb
CHANGED
|
@@ -263,12 +263,21 @@ module Raptor
|
|
|
263
263
|
#
|
|
264
264
|
# @rbs () -> Thread
|
|
265
265
|
def run
|
|
266
|
-
|
|
266
|
+
ready = AtomicConditionVariable.new
|
|
267
|
+
startup_error = nil
|
|
268
|
+
thread = Thread.new do
|
|
267
269
|
Thread.current.name = "Reactor"
|
|
268
270
|
@thread = Thread.current
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
begin
|
|
272
|
+
selector = IO::Event::Selector.new(Fiber.current)
|
|
273
|
+
@wakeup = Fiber.new { loop { selector.transfer } }
|
|
274
|
+
@selector = selector
|
|
275
|
+
rescue => error
|
|
276
|
+
startup_error = error
|
|
277
|
+
ready.signal
|
|
278
|
+
next
|
|
279
|
+
end
|
|
280
|
+
ready.signal
|
|
272
281
|
|
|
273
282
|
until stopped?
|
|
274
283
|
begin
|
|
@@ -310,6 +319,14 @@ module Raptor
|
|
|
310
319
|
@thread = nil
|
|
311
320
|
@selector.close
|
|
312
321
|
end
|
|
322
|
+
|
|
323
|
+
ready.wait { @selector || startup_error }
|
|
324
|
+
if startup_error
|
|
325
|
+
thread.join
|
|
326
|
+
raise startup_error
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
thread
|
|
313
330
|
end
|
|
314
331
|
|
|
315
332
|
# Adds a new client connection to the reactor.
|
data/lib/raptor/version.rb
CHANGED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "atomic-ruby/atomic_thread_pool"
|
|
5
|
+
require "ractor-pool"
|
|
6
|
+
|
|
7
|
+
require_relative "http1"
|
|
8
|
+
require_relative "http2"
|
|
9
|
+
require_relative "reactor"
|
|
10
|
+
require_relative "server"
|
|
11
|
+
|
|
12
|
+
module Raptor
|
|
13
|
+
# Runs the request-handling stack shared by forked and embedded servers.
|
|
14
|
+
#
|
|
15
|
+
class Worker
|
|
16
|
+
# @rbs @app: untyped
|
|
17
|
+
# @rbs @listeners: Array[TCPServer | UNIXServer | Binder::SslListener | Binder::H2cListener]
|
|
18
|
+
# @rbs @port: Integer
|
|
19
|
+
# @rbs @options: Hash[Symbol, untyped]
|
|
20
|
+
# @rbs @http1_ractors: Integer
|
|
21
|
+
# @rbs @http2_ractors: Integer
|
|
22
|
+
# @rbs @access_log_io: IO?
|
|
23
|
+
# @rbs @worker_index: Integer?
|
|
24
|
+
# @rbs @stopped: bool
|
|
25
|
+
# @rbs @http1: Http1?
|
|
26
|
+
# @rbs @http2: Http2?
|
|
27
|
+
# @rbs @http1_ractor_pool: RactorPool?
|
|
28
|
+
# @rbs @http2_ractor_pool: RactorPool?
|
|
29
|
+
# @rbs @reactor_thread: Thread?
|
|
30
|
+
# @rbs @server: Server?
|
|
31
|
+
# @rbs @server_thread: Thread?
|
|
32
|
+
|
|
33
|
+
attr_reader :requests #: Integer
|
|
34
|
+
attr_reader :reactor #: Reactor?
|
|
35
|
+
attr_reader :thread_pool #: AtomicThreadPool?
|
|
36
|
+
|
|
37
|
+
# Creates a serving stack using the supplied listeners and configuration.
|
|
38
|
+
#
|
|
39
|
+
# @rbs (untyped app, listeners: Array[TCPServer | UNIXServer | Binder::SslListener | Binder::H2cListener], port: Integer, options: Hash[Symbol, untyped], http1_ractors: Integer, http2_ractors: Integer, ?access_log_io: IO?, ?worker_index: Integer?) -> void
|
|
40
|
+
def initialize(app, listeners:, port:, options:, http1_ractors:, http2_ractors:, access_log_io: nil, worker_index: nil)
|
|
41
|
+
@app = app
|
|
42
|
+
@listeners = listeners
|
|
43
|
+
@port = port
|
|
44
|
+
@options = options
|
|
45
|
+
@http1_ractors = http1_ractors
|
|
46
|
+
@http2_ractors = http2_ractors
|
|
47
|
+
@access_log_io = access_log_io
|
|
48
|
+
@worker_index = worker_index
|
|
49
|
+
@requests = 0
|
|
50
|
+
@stopped = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Starts the application pool, protocol parsers, reactor, and acceptor.
|
|
54
|
+
#
|
|
55
|
+
# @rbs () -> void
|
|
56
|
+
def start
|
|
57
|
+
counting_app = ->(env) {
|
|
58
|
+
@requests += 1
|
|
59
|
+
@app.call(env)
|
|
60
|
+
}
|
|
61
|
+
@thread_pool = AtomicThreadPool.new(size: @options[:threads], max_size: @options[:max_threads])
|
|
62
|
+
@http1 = Http1.new(
|
|
63
|
+
counting_app,
|
|
64
|
+
@port,
|
|
65
|
+
connection_options: @options[:connection],
|
|
66
|
+
http1_options: @options[:http1],
|
|
67
|
+
access_log_io: @access_log_io,
|
|
68
|
+
clean_thread_locals: @options[:clean_thread_locals],
|
|
69
|
+
clean_fiber_locals: @options[:clean_fiber_locals],
|
|
70
|
+
on_error: @options[:on_error]
|
|
71
|
+
)
|
|
72
|
+
@http2 = Http2.new(
|
|
73
|
+
counting_app,
|
|
74
|
+
@port,
|
|
75
|
+
connection_options: @options[:connection],
|
|
76
|
+
http2_options: @options[:http2],
|
|
77
|
+
access_log_io: @access_log_io,
|
|
78
|
+
clean_thread_locals: @options[:clean_thread_locals],
|
|
79
|
+
clean_fiber_locals: @options[:clean_fiber_locals],
|
|
80
|
+
on_error: @options[:on_error]
|
|
81
|
+
)
|
|
82
|
+
@http1_ractor_pool = RactorPool.new(
|
|
83
|
+
size: @http1_ractors,
|
|
84
|
+
worker: @http1.parser_worker,
|
|
85
|
+
name: "HTTP/1.1"
|
|
86
|
+
) do |parsed_result|
|
|
87
|
+
begin
|
|
88
|
+
@http1.handle_parsed_request(parsed_result, @reactor, @thread_pool)
|
|
89
|
+
rescue => error
|
|
90
|
+
Log.rescued_error(error)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if @listeners.any? { |listener| listener.is_a?(Binder::SslListener) || listener.is_a?(Binder::H2cListener) }
|
|
95
|
+
@http2_ractor_pool = RactorPool.new(
|
|
96
|
+
size: @http2_ractors,
|
|
97
|
+
worker: @http2.parser_worker,
|
|
98
|
+
name: "HTTP/2"
|
|
99
|
+
) do |parsed_result|
|
|
100
|
+
begin
|
|
101
|
+
@http2.handle_parsed_request(parsed_result, @reactor, @thread_pool)
|
|
102
|
+
rescue => error
|
|
103
|
+
Log.rescued_error(error)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@reactor = Reactor.new(
|
|
109
|
+
@http1,
|
|
110
|
+
@http1_ractor_pool,
|
|
111
|
+
@http2_ractor_pool,
|
|
112
|
+
@thread_pool,
|
|
113
|
+
connection_options: @options[:connection],
|
|
114
|
+
http1_options: @options[:http1],
|
|
115
|
+
http2_options: @options[:http2]
|
|
116
|
+
)
|
|
117
|
+
@reactor_thread = @reactor.run
|
|
118
|
+
@server = Server.new(
|
|
119
|
+
@reactor,
|
|
120
|
+
@thread_pool,
|
|
121
|
+
@http1,
|
|
122
|
+
@http2,
|
|
123
|
+
connection_options: @options[:connection],
|
|
124
|
+
drain_accept_queue: @options[:drain_accept_queue],
|
|
125
|
+
listeners: @listeners,
|
|
126
|
+
worker_index: @worker_index
|
|
127
|
+
)
|
|
128
|
+
@server_thread = @server.run
|
|
129
|
+
rescue
|
|
130
|
+
shutdown
|
|
131
|
+
raise
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns whether both serving threads are still running.
|
|
135
|
+
#
|
|
136
|
+
# @rbs () -> bool
|
|
137
|
+
def running?
|
|
138
|
+
!!(@server_thread&.alive? && @reactor_thread&.alive?)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Waits until serving stops, propagating background thread failures.
|
|
142
|
+
#
|
|
143
|
+
# @rbs () -> void
|
|
144
|
+
def join
|
|
145
|
+
@server_thread.join(0.1) while running?
|
|
146
|
+
@server_thread.value if @server_thread && !@server_thread.alive?
|
|
147
|
+
@reactor_thread.value if @reactor_thread && !@reactor_thread.alive?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Drains requests and releases the serving threads and parser Ractors.
|
|
151
|
+
# A seed keeps its listeners open for the cluster to manage.
|
|
152
|
+
#
|
|
153
|
+
# @rbs (?close_listeners: bool) -> void
|
|
154
|
+
def shutdown(close_listeners: true)
|
|
155
|
+
return if @stopped
|
|
156
|
+
|
|
157
|
+
@stopped = true
|
|
158
|
+
begin
|
|
159
|
+
if close_listeners
|
|
160
|
+
@server&.shutdown
|
|
161
|
+
else
|
|
162
|
+
@server&.stop_accepting
|
|
163
|
+
end
|
|
164
|
+
@server_thread&.join
|
|
165
|
+
ensure
|
|
166
|
+
@http1&.shutdown
|
|
167
|
+
if @reactor_thread&.alive?
|
|
168
|
+
@http2&.shutdown(@reactor)
|
|
169
|
+
@reactor.drain_detached_bodies(@options[:worker_drain_timeout])
|
|
170
|
+
end
|
|
171
|
+
drain_thread_pool if @thread_pool
|
|
172
|
+
@reactor&.shutdown
|
|
173
|
+
begin
|
|
174
|
+
@reactor_thread&.join
|
|
175
|
+
ensure
|
|
176
|
+
@http1_ractor_pool&.shutdown
|
|
177
|
+
@http2_ractor_pool&.shutdown
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
# Waits for app work to finish within the configured drain timeout.
|
|
185
|
+
#
|
|
186
|
+
# @rbs () -> void
|
|
187
|
+
def drain_thread_pool
|
|
188
|
+
drain_thread = Thread.new do
|
|
189
|
+
Thread.current.name = "Thread Pool Drain"
|
|
190
|
+
|
|
191
|
+
@thread_pool.shutdown
|
|
192
|
+
end
|
|
193
|
+
return if drain_thread.join(@options[:worker_drain_timeout])
|
|
194
|
+
|
|
195
|
+
Log.warn "Force-killing in-flight app threads after #{@options[:worker_drain_timeout]}s drain timeout"
|
|
196
|
+
@thread_pool.instance_variable_get(:@threads).each(&:kill)
|
|
197
|
+
drain_thread.join
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -7,16 +7,16 @@ module Rackup
|
|
|
7
7
|
module Raptor
|
|
8
8
|
DEFAULT_OPTIONS: untyped
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
# Serves the given Rack application using the handler options.
|
|
11
11
|
#
|
|
12
12
|
# @param app [#call] the Rack application to serve
|
|
13
13
|
# @param options [Hash] handler options provided by Rackup or the host
|
|
14
|
-
# @yield before the
|
|
15
|
-
# @yieldparam
|
|
14
|
+
# @yield before the server starts running
|
|
15
|
+
# @yieldparam server [::Raptor::Cluster, ::Raptor::Embedded] the server instance
|
|
16
16
|
# @return [void]
|
|
17
17
|
#
|
|
18
|
-
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
|
|
19
|
-
def self.run: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
|
|
18
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster | ::Raptor::Embedded) -> void } -> void
|
|
19
|
+
def self.run: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, **untyped options) { (::Raptor::Cluster | ::Raptor::Embedded) -> void } -> void
|
|
20
20
|
|
|
21
21
|
# Returns the handler-specific options surfaced by `rackup --help`.
|
|
22
22
|
#
|
|
@@ -25,17 +25,17 @@ module Rackup
|
|
|
25
25
|
# @rbs () -> Hash[String, String]
|
|
26
26
|
def self.valid_options: () -> Hash[String, String]
|
|
27
27
|
|
|
28
|
-
# Builds
|
|
28
|
+
# Builds Raptor server configuration from Rack handler options.
|
|
29
29
|
#
|
|
30
30
|
# Options not explicitly supplied by the user (per the `:user_supplied_options`
|
|
31
31
|
# key) are treated as host-provided defaults.
|
|
32
32
|
#
|
|
33
33
|
# @param app [#call] the Rack application to serve
|
|
34
34
|
# @param options [Hash] handler options provided by Rackup or the host
|
|
35
|
-
# @return [Hash{Symbol => untyped}]
|
|
35
|
+
# @return [Hash{Symbol => untyped}] server configuration
|
|
36
36
|
#
|
|
37
37
|
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
|
|
38
|
-
def self.
|
|
38
|
+
def self.build_options: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -44,10 +44,6 @@ module Raptor
|
|
|
44
44
|
# @rbs (Integer worker_count, ?cores: Integer) -> Integer
|
|
45
45
|
def self.default_http2_ractor_count: (Integer worker_count, ?cores: Integer) -> Integer
|
|
46
46
|
|
|
47
|
-
@stats_file: String?
|
|
48
|
-
|
|
49
|
-
@control_server: ControlServer?
|
|
50
|
-
|
|
51
47
|
@pid_file: String?
|
|
52
48
|
|
|
53
49
|
@stdout_file: String?
|
|
@@ -62,34 +58,36 @@ module Raptor
|
|
|
62
58
|
|
|
63
59
|
@launch_argv: Array[String]?
|
|
64
60
|
|
|
65
|
-
@on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
66
|
-
|
|
67
61
|
@binder: Binder
|
|
68
62
|
|
|
63
|
+
@server_port: Integer
|
|
64
|
+
|
|
65
|
+
@app: untyped
|
|
66
|
+
|
|
69
67
|
@plugins: Array[Plugin]
|
|
70
68
|
|
|
69
|
+
@control_server: ControlServer?
|
|
70
|
+
|
|
71
|
+
@stats_file: String?
|
|
72
|
+
|
|
71
73
|
@before_refork: Array[Proc]
|
|
72
74
|
|
|
75
|
+
@after_worker_exit: Array[Proc]
|
|
76
|
+
|
|
77
|
+
@after_worker_drain: Array[Proc]
|
|
78
|
+
|
|
73
79
|
@before_worker_shutdown: Array[Proc]
|
|
74
80
|
|
|
75
81
|
@before_worker_boot: Array[Proc]
|
|
76
82
|
|
|
83
|
+
@after_fork: Array[Proc]
|
|
84
|
+
|
|
77
85
|
@before_fork: Array[Proc]
|
|
78
86
|
|
|
79
87
|
@worker_shutdown_timeout: Integer
|
|
80
88
|
|
|
81
89
|
@worker_drain_timeout: Integer
|
|
82
90
|
|
|
83
|
-
@worker_timeout: Integer
|
|
84
|
-
|
|
85
|
-
@worker_boot_timeout: Integer
|
|
86
|
-
|
|
87
|
-
@http2_options: Hash[Symbol, untyped]
|
|
88
|
-
|
|
89
|
-
@http1_options: Hash[Symbol, untyped]
|
|
90
|
-
|
|
91
|
-
@connection_options: Hash[Symbol, untyped]
|
|
92
|
-
|
|
93
91
|
@bpf_active: bool
|
|
94
92
|
|
|
95
93
|
@resp_w: IO?
|
|
@@ -126,20 +124,18 @@ module Raptor
|
|
|
126
124
|
|
|
127
125
|
@timed_out: Set[Integer]
|
|
128
126
|
|
|
127
|
+
@worker_indices: Hash[Integer, Integer]
|
|
128
|
+
|
|
129
129
|
@workers: Hash[Integer, Integer]
|
|
130
130
|
|
|
131
131
|
@shutdown: bool
|
|
132
132
|
|
|
133
|
-
@
|
|
133
|
+
@worker_timeout: Integer
|
|
134
134
|
|
|
135
|
-
@
|
|
135
|
+
@worker_boot_timeout: Integer
|
|
136
136
|
|
|
137
137
|
@environment: String
|
|
138
138
|
|
|
139
|
-
@clean_fiber_locals: bool
|
|
140
|
-
|
|
141
|
-
@clean_thread_locals: bool
|
|
142
|
-
|
|
143
139
|
@cpu_affinity: bool
|
|
144
140
|
|
|
145
141
|
@max_thread_count: (Integer | Float)?
|
|
@@ -152,8 +148,6 @@ module Raptor
|
|
|
152
148
|
|
|
153
149
|
@worker_count: Integer
|
|
154
150
|
|
|
155
|
-
@drain_accept_queue: bool
|
|
156
|
-
|
|
157
151
|
# Returns the cluster configuration available to plugins.
|
|
158
152
|
#
|
|
159
153
|
# @return [Hash{Symbol => untyped}]
|
|
@@ -183,9 +177,12 @@ module Raptor
|
|
|
183
177
|
# @option options [Integer] :worker_drain_timeout seconds a worker waits for in-flight requests during shutdown before force-killing app threads
|
|
184
178
|
# @option options [Integer] :worker_shutdown_timeout seconds to wait for graceful worker exit before force-killing
|
|
185
179
|
# @option options [Integer, Array<Integer>, nil] :refork_after request-count threshold(s) at which a warm worker is promoted to a fork source for phased refork; nil or 0 disables. Requires `PR_SET_CHILD_SUBREAPER` (Linux)
|
|
186
|
-
# @option options [Array<Proc>] :before_fork procs called in the master before
|
|
180
|
+
# @option options [Array<Proc>] :before_fork procs called in the master before each direct worker fork, not seed forks
|
|
181
|
+
# @option options [Array<Proc>] :after_fork procs called in the master after each direct fork attempt, including failures
|
|
187
182
|
# @option options [Array<Proc>] :before_worker_boot procs called with the worker index before it begins serving
|
|
188
183
|
# @option options [Array<Proc>] :before_worker_shutdown procs called with the worker index before its graceful shutdown
|
|
184
|
+
# @option options [Array<Proc>] :after_worker_drain procs called with the worker index after draining, before exit or seed promotion
|
|
185
|
+
# @option options [Array<Proc>] :after_worker_exit procs called in the master after reaping, with the worker index, pid, and Process::Status
|
|
189
186
|
# @option options [Array<Proc>] :before_refork procs called in a worker before it transitions to a seed
|
|
190
187
|
# @option options [Array<String, Symbol>] :plugins plugins to load and start in order
|
|
191
188
|
# @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
|
|
@@ -420,16 +417,6 @@ module Raptor
|
|
|
420
417
|
# @rbs (Integer index) -> void
|
|
421
418
|
def run_seed_loop: (Integer index) -> void
|
|
422
419
|
|
|
423
|
-
# Shuts down the worker's application thread pool, force-killing the
|
|
424
|
-
# underlying threads if in-flight requests have not finished within
|
|
425
|
-
# `worker_drain_timeout` seconds.
|
|
426
|
-
#
|
|
427
|
-
# @param thread_pool [AtomicThreadPool] the worker's thread pool
|
|
428
|
-
# @return [void]
|
|
429
|
-
#
|
|
430
|
-
# @rbs (AtomicThreadPool thread_pool) -> void
|
|
431
|
-
def drain_thread_pool: (AtomicThreadPool thread_pool) -> void
|
|
432
|
-
|
|
433
420
|
# Returns a human-readable description of how a process exited.
|
|
434
421
|
#
|
|
435
422
|
# @param status [Process::Status] the exit status of the process
|