raptor 0.24.0 → 0.26.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 +32 -24
- data/README.md +81 -57
- data/Rakefile +10 -3
- data/docs/raptor-vs-puma.md +4 -2
- data/docs/talk.md +1 -1
- data/lib/rackup/handler/raptor.rb +19 -12
- data/lib/raptor/binder.rb +17 -5
- data/lib/raptor/cli.rb +29 -2
- data/lib/raptor/cluster.rb +15 -130
- data/lib/raptor/embedded.rb +159 -0
- data/lib/raptor/reactor.rb +120 -73
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor/worker.rb +200 -0
- data/lib/raptor.rb +1 -1
- data/sig/generated/rackup/handler/raptor.rbs +8 -8
- data/sig/generated/raptor/cli.rbs +5 -0
- data/sig/generated/raptor/cluster.rbs +8 -32
- data/sig/generated/raptor/embedded.rbs +68 -0
- data/sig/generated/raptor/reactor.rbs +31 -16
- data/sig/generated/raptor/worker.rbs +77 -0
- data/sig/generated/raptor.rbs +1 -1
- metadata +7 -2
data/lib/raptor/binder.rb
CHANGED
|
@@ -176,12 +176,18 @@ module Raptor
|
|
|
176
176
|
#
|
|
177
177
|
# @rbs () -> void
|
|
178
178
|
def parse
|
|
179
|
-
@
|
|
179
|
+
@listeners = []
|
|
180
|
+
@uri_listeners = {}
|
|
181
|
+
@bind_uris.each do |bind_uri|
|
|
180
182
|
uri = URI.parse(bind_uri)
|
|
181
183
|
filenos = @inherited_fds[bind_uri]
|
|
182
|
-
|
|
184
|
+
listeners = filenos ? restore_listeners(uri, filenos) : create_listeners(uri)
|
|
185
|
+
@uri_listeners[bind_uri] = listeners
|
|
186
|
+
@listeners.concat(listeners)
|
|
183
187
|
end
|
|
184
|
-
|
|
188
|
+
rescue
|
|
189
|
+
close
|
|
190
|
+
raise
|
|
185
191
|
end
|
|
186
192
|
|
|
187
193
|
# Creates fresh listeners for the given URI.
|
|
@@ -241,7 +247,9 @@ module Raptor
|
|
|
241
247
|
# @rbs (String? host, Integer? port) -> Array[TCPServer]
|
|
242
248
|
def create_tcp_listeners(host, port)
|
|
243
249
|
if host == "localhost"
|
|
244
|
-
|
|
250
|
+
listeners = []
|
|
251
|
+
loopback_addresses.each { |address| listeners.concat(create_tcp_listeners(address, port)) }
|
|
252
|
+
return listeners
|
|
245
253
|
end
|
|
246
254
|
|
|
247
255
|
host = host[1..-2] if host&.start_with?("[")
|
|
@@ -258,6 +266,10 @@ module Raptor
|
|
|
258
266
|
tcp_server = TCPServer.for_fd(socket.fileno)
|
|
259
267
|
socket.autoclose = false
|
|
260
268
|
[tcp_server]
|
|
269
|
+
rescue
|
|
270
|
+
listeners&.each(&:close)
|
|
271
|
+
socket&.close
|
|
272
|
+
raise
|
|
261
273
|
end
|
|
262
274
|
|
|
263
275
|
# Creates a Unix domain server socket at the given path. Removes stale
|
|
@@ -306,8 +318,8 @@ module Raptor
|
|
|
306
318
|
#
|
|
307
319
|
# @rbs (String? host, Integer? port, Hash[String, String] ssl_params) -> Array[SslListener]
|
|
308
320
|
def create_ssl_listeners(host, port, ssl_params)
|
|
309
|
-
tcp_servers = create_tcp_listeners(host, port)
|
|
310
321
|
ssl_context = build_ssl_context(ssl_params)
|
|
322
|
+
tcp_servers = create_tcp_listeners(host, port)
|
|
311
323
|
tcp_servers.map { |tcp_server| SslListener.new(tcp_server: tcp_server, ssl_context: ssl_context) }
|
|
312
324
|
end
|
|
313
325
|
|
data/lib/raptor/cli.rb
CHANGED
|
@@ -175,11 +175,38 @@ module Raptor
|
|
|
175
175
|
#
|
|
176
176
|
# @rbs () -> void
|
|
177
177
|
def run
|
|
178
|
-
@command == :stats
|
|
178
|
+
if @command == :stats
|
|
179
|
+
run_stats
|
|
180
|
+
elsif @options[:workers].zero?
|
|
181
|
+
run_embedded
|
|
182
|
+
else
|
|
183
|
+
Cluster.run(@options)
|
|
184
|
+
end
|
|
179
185
|
end
|
|
180
186
|
|
|
181
187
|
private
|
|
182
188
|
|
|
189
|
+
# Serves in the CLI process until interrupted or terminated.
|
|
190
|
+
#
|
|
191
|
+
# @rbs () -> void
|
|
192
|
+
def run_embedded
|
|
193
|
+
require_relative "embedded"
|
|
194
|
+
|
|
195
|
+
$stdout.sync = true
|
|
196
|
+
$stderr.sync = true
|
|
197
|
+
|
|
198
|
+
Dir.chdir(@options[:chdir]) if @options[:chdir]
|
|
199
|
+
app = @options[:app] || Rack::Builder.parse_file(@options[:rackup])
|
|
200
|
+
server = Embedded.new(app, **@options, log: true).start
|
|
201
|
+
handlers = ["INT", "TERM"].to_h do |signal|
|
|
202
|
+
[signal, trap(signal) { Thread.new { server.shutdown } }]
|
|
203
|
+
end
|
|
204
|
+
server.join
|
|
205
|
+
ensure
|
|
206
|
+
server&.shutdown
|
|
207
|
+
handlers&.each { |signal, handler| trap(signal, handler) }
|
|
208
|
+
end
|
|
209
|
+
|
|
183
210
|
# Reads and prints the stats file.
|
|
184
211
|
#
|
|
185
212
|
# @return [void]
|
|
@@ -282,7 +309,7 @@ module Raptor
|
|
|
282
309
|
@options[:drain_accept_queue] = bool
|
|
283
310
|
end
|
|
284
311
|
|
|
285
|
-
opts.on("-w", "--workers NUM", Integer, "Number of worker processes (default: #{DEFAULT_WORKER_COUNT})") do |num|
|
|
312
|
+
opts.on("-w", "--workers NUM", Integer, "Number of worker processes, or 0 for in-process serving (default: #{DEFAULT_WORKER_COUNT})") do |num|
|
|
286
313
|
@options[:workers] = num
|
|
287
314
|
end
|
|
288
315
|
|
data/lib/raptor/cluster.rb
CHANGED
|
@@ -4,24 +4,19 @@
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "time"
|
|
6
6
|
|
|
7
|
-
require "atomic-ruby/atomic_thread_pool"
|
|
8
7
|
require "concurrent/utility/processor_counter"
|
|
9
8
|
require "rack/builder"
|
|
10
|
-
require "ractor-pool"
|
|
11
9
|
|
|
12
10
|
require_relative "binder"
|
|
13
11
|
require_relative "control_server"
|
|
14
|
-
require_relative "http1"
|
|
15
|
-
require_relative "http2"
|
|
16
12
|
require_relative "log"
|
|
17
13
|
require_relative "plugin"
|
|
18
14
|
require_relative "raptor_native"
|
|
19
|
-
require_relative "reactor"
|
|
20
15
|
require_relative "reuseport_bpf"
|
|
21
|
-
require_relative "server"
|
|
22
16
|
require_relative "stats"
|
|
23
17
|
require_relative "systemd"
|
|
24
18
|
require_relative "version"
|
|
19
|
+
require_relative "worker"
|
|
25
20
|
|
|
26
21
|
module Raptor
|
|
27
22
|
# Forks and supervises worker processes. Handles graceful shutdown on
|
|
@@ -73,19 +68,13 @@ module Raptor
|
|
|
73
68
|
(cores.to_f / worker_count).round.clamp(1, HTTP2_RACTOR_COUNT_CAP)
|
|
74
69
|
end
|
|
75
70
|
|
|
76
|
-
# @rbs @drain_accept_queue: bool
|
|
77
71
|
# @rbs @worker_count: Integer
|
|
78
72
|
# @rbs @http1_ractor_count: Integer
|
|
79
73
|
# @rbs @http2_ractor_count: Integer
|
|
80
74
|
# @rbs @thread_count: Integer
|
|
81
75
|
# @rbs @max_thread_count: (Integer | Float)?
|
|
82
76
|
# @rbs @cpu_affinity: bool
|
|
83
|
-
# @rbs @clean_thread_locals: bool
|
|
84
|
-
# @rbs @clean_fiber_locals: bool
|
|
85
77
|
# @rbs @environment: String
|
|
86
|
-
# @rbs @connection_options: Hash[Symbol, untyped]
|
|
87
|
-
# @rbs @http1_options: Hash[Symbol, untyped]
|
|
88
|
-
# @rbs @http2_options: Hash[Symbol, untyped]
|
|
89
78
|
# @rbs @worker_boot_timeout: Integer
|
|
90
79
|
# @rbs @worker_timeout: Integer
|
|
91
80
|
# @rbs @worker_drain_timeout: Integer
|
|
@@ -103,7 +92,6 @@ module Raptor
|
|
|
103
92
|
# @rbs @access_log_io: IO?
|
|
104
93
|
# @rbs @launch_command: String?
|
|
105
94
|
# @rbs @launch_argv: Array[String]?
|
|
106
|
-
# @rbs @on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
107
95
|
# @rbs @binder: Binder
|
|
108
96
|
# @rbs @server_port: Integer
|
|
109
97
|
# @rbs @app: untyped
|
|
@@ -178,19 +166,13 @@ module Raptor
|
|
|
178
166
|
def initialize(options)
|
|
179
167
|
@options = options = options.dup
|
|
180
168
|
@plugins = []
|
|
181
|
-
@drain_accept_queue = options[:drain_accept_queue]
|
|
182
169
|
@worker_count = options[:workers]
|
|
183
170
|
@http1_ractor_count = options[:http1][:ractors] || self.class.default_http1_ractor_count(@worker_count)
|
|
184
171
|
@http2_ractor_count = options[:http2][:ractors] || self.class.default_http2_ractor_count(@worker_count)
|
|
185
172
|
@thread_count = options[:threads]
|
|
186
173
|
@max_thread_count = options[:max_threads]
|
|
187
174
|
@cpu_affinity = options[:cpu_affinity]
|
|
188
|
-
@clean_thread_locals = options[:clean_thread_locals]
|
|
189
|
-
@clean_fiber_locals = options[:clean_fiber_locals]
|
|
190
175
|
@environment = options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
191
|
-
@connection_options = options[:connection]
|
|
192
|
-
@http1_options = options[:http1]
|
|
193
|
-
@http2_options = options[:http2]
|
|
194
176
|
@worker_boot_timeout = options[:worker_boot_timeout]
|
|
195
177
|
@worker_timeout = options[:worker_timeout]
|
|
196
178
|
@worker_drain_timeout = options[:worker_drain_timeout]
|
|
@@ -208,7 +190,6 @@ module Raptor
|
|
|
208
190
|
@access_log_io = nil
|
|
209
191
|
@launch_command = options[:launch_command]
|
|
210
192
|
@launch_argv = options[:launch_argv]
|
|
211
|
-
@on_error = options[:on_error]
|
|
212
193
|
|
|
213
194
|
Dir.chdir(options[:chdir]) if options[:chdir]
|
|
214
195
|
|
|
@@ -850,7 +831,6 @@ module Raptor
|
|
|
850
831
|
CPU.pin(index) if @cpu_affinity && CPU.count >= @worker_count
|
|
851
832
|
|
|
852
833
|
started_at = Process.clock_gettime(Process::CLOCK_REALTIME)
|
|
853
|
-
request_count = 0
|
|
854
834
|
|
|
855
835
|
@stats.write(
|
|
856
836
|
index,
|
|
@@ -867,70 +847,6 @@ module Raptor
|
|
|
867
847
|
|
|
868
848
|
@before_worker_boot.each { |hook| hook.parameters.empty? ? hook.call : hook.call(index) }
|
|
869
849
|
|
|
870
|
-
reactor = nil
|
|
871
|
-
|
|
872
|
-
counting_app = ->(env) {
|
|
873
|
-
request_count += 1
|
|
874
|
-
@app.call(env)
|
|
875
|
-
}
|
|
876
|
-
thread_pool = AtomicThreadPool.new(size: @thread_count, max_size: @max_thread_count)
|
|
877
|
-
http1 = Http1.new(
|
|
878
|
-
counting_app,
|
|
879
|
-
@server_port,
|
|
880
|
-
connection_options: @connection_options,
|
|
881
|
-
http1_options: @http1_options,
|
|
882
|
-
access_log_io: @access_log_io,
|
|
883
|
-
clean_thread_locals: @clean_thread_locals,
|
|
884
|
-
clean_fiber_locals: @clean_fiber_locals,
|
|
885
|
-
on_error: @on_error
|
|
886
|
-
)
|
|
887
|
-
http2 = Http2.new(
|
|
888
|
-
counting_app,
|
|
889
|
-
@server_port,
|
|
890
|
-
connection_options: @connection_options,
|
|
891
|
-
http2_options: @http2_options,
|
|
892
|
-
access_log_io: @access_log_io,
|
|
893
|
-
clean_thread_locals: @clean_thread_locals,
|
|
894
|
-
clean_fiber_locals: @clean_fiber_locals,
|
|
895
|
-
on_error: @on_error
|
|
896
|
-
)
|
|
897
|
-
http1_ractor_pool = RactorPool.new(
|
|
898
|
-
size: @http1_ractor_count,
|
|
899
|
-
worker: http1.parser_worker,
|
|
900
|
-
name: "HTTP/1.1"
|
|
901
|
-
) do |parsed_result|
|
|
902
|
-
begin
|
|
903
|
-
http1.handle_parsed_request(parsed_result, reactor, thread_pool)
|
|
904
|
-
rescue => error
|
|
905
|
-
Log.rescued_error(error)
|
|
906
|
-
end
|
|
907
|
-
end
|
|
908
|
-
|
|
909
|
-
http2_ractor_pool = if @binder.http2?
|
|
910
|
-
RactorPool.new(
|
|
911
|
-
size: @http2_ractor_count,
|
|
912
|
-
worker: http2.parser_worker,
|
|
913
|
-
name: "HTTP/2"
|
|
914
|
-
) do |parsed_result|
|
|
915
|
-
begin
|
|
916
|
-
http2.handle_parsed_request(parsed_result, reactor, thread_pool)
|
|
917
|
-
rescue => error
|
|
918
|
-
Log.rescued_error(error)
|
|
919
|
-
end
|
|
920
|
-
end
|
|
921
|
-
end
|
|
922
|
-
|
|
923
|
-
reactor = Reactor.new(
|
|
924
|
-
http1,
|
|
925
|
-
http1_ractor_pool,
|
|
926
|
-
http2_ractor_pool,
|
|
927
|
-
thread_pool,
|
|
928
|
-
connection_options: @connection_options,
|
|
929
|
-
http1_options: @http1_options,
|
|
930
|
-
http2_options: @http2_options
|
|
931
|
-
)
|
|
932
|
-
reactor_thread = reactor.run
|
|
933
|
-
|
|
934
850
|
bpf_listeners = if @bpf_active
|
|
935
851
|
ReuseportBPF.enable_load_reporting(index)
|
|
936
852
|
ReuseportBPF.create_worker_listeners(index, @binder.socket_backlog)
|
|
@@ -938,17 +854,17 @@ module Raptor
|
|
|
938
854
|
[]
|
|
939
855
|
end
|
|
940
856
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
thread_pool,
|
|
944
|
-
http1,
|
|
945
|
-
http2,
|
|
946
|
-
connection_options: @connection_options,
|
|
947
|
-
drain_accept_queue: @drain_accept_queue,
|
|
857
|
+
worker = Worker.new(
|
|
858
|
+
@app,
|
|
948
859
|
listeners: bpf_listeners + @binder.listeners,
|
|
860
|
+
port: @server_port,
|
|
861
|
+
options: @options,
|
|
862
|
+
http1_ractors: @http1_ractor_count,
|
|
863
|
+
http2_ractors: @http2_ractor_count,
|
|
864
|
+
access_log_io: @access_log_io,
|
|
949
865
|
worker_index: (index if @bpf_active)
|
|
950
866
|
)
|
|
951
|
-
|
|
867
|
+
worker.start
|
|
952
868
|
|
|
953
869
|
Log.info "Worker #{index} booted"
|
|
954
870
|
|
|
@@ -960,10 +876,10 @@ module Raptor
|
|
|
960
876
|
index,
|
|
961
877
|
pid: Process.pid,
|
|
962
878
|
phase: phase,
|
|
963
|
-
requests:
|
|
964
|
-
backlog: reactor.backlog,
|
|
965
|
-
busy_threads: thread_pool.active_count,
|
|
966
|
-
thread_capacity: thread_pool.size,
|
|
879
|
+
requests: worker.requests,
|
|
880
|
+
backlog: worker.reactor.backlog,
|
|
881
|
+
busy_threads: worker.thread_pool.active_count,
|
|
882
|
+
thread_capacity: worker.thread_pool.size,
|
|
967
883
|
started_at: started_at,
|
|
968
884
|
last_checkin: Process.clock_gettime(Process::CLOCK_REALTIME),
|
|
969
885
|
booted: true
|
|
@@ -975,30 +891,20 @@ module Raptor
|
|
|
975
891
|
end
|
|
976
892
|
|
|
977
893
|
until shutdown_requested || promote_to_seed
|
|
978
|
-
break unless
|
|
894
|
+
break unless worker.running?
|
|
979
895
|
|
|
980
896
|
sleep 0.5
|
|
981
897
|
end
|
|
982
898
|
|
|
983
899
|
if promote_to_seed
|
|
984
900
|
@before_refork.each(&:call)
|
|
985
|
-
server.stop_accepting
|
|
986
901
|
else
|
|
987
902
|
@before_worker_shutdown.each { |hook| hook.parameters.empty? ? hook.call : hook.call(index) }
|
|
988
|
-
server.shutdown
|
|
989
903
|
end
|
|
990
904
|
|
|
991
|
-
|
|
905
|
+
worker.shutdown(close_listeners: !promote_to_seed)
|
|
992
906
|
# A seed never accepts again, and the workers it forks bind their own.
|
|
993
907
|
bpf_listeners.each(&:close) if promote_to_seed
|
|
994
|
-
http1.shutdown
|
|
995
|
-
http2.shutdown(reactor)
|
|
996
|
-
reactor.drain_detached_bodies(@worker_drain_timeout)
|
|
997
|
-
drain_thread_pool(thread_pool)
|
|
998
|
-
reactor.shutdown
|
|
999
|
-
reactor_thread.join
|
|
1000
|
-
http1_ractor_pool.shutdown
|
|
1001
|
-
http2_ractor_pool&.shutdown
|
|
1002
908
|
stats_thread.join
|
|
1003
909
|
|
|
1004
910
|
run_seed_loop(index) if promote_to_seed
|
|
@@ -1041,27 +947,6 @@ module Raptor
|
|
|
1041
947
|
rescue Errno::EPIPE, IOError
|
|
1042
948
|
end
|
|
1043
949
|
|
|
1044
|
-
# Shuts down the worker's application thread pool, force-killing the
|
|
1045
|
-
# underlying threads if in-flight requests have not finished within
|
|
1046
|
-
# `worker_drain_timeout` seconds.
|
|
1047
|
-
#
|
|
1048
|
-
# @param thread_pool [AtomicThreadPool] the worker's thread pool
|
|
1049
|
-
# @return [void]
|
|
1050
|
-
#
|
|
1051
|
-
# @rbs (AtomicThreadPool thread_pool) -> void
|
|
1052
|
-
def drain_thread_pool(thread_pool)
|
|
1053
|
-
drain_thread = Thread.new do
|
|
1054
|
-
Thread.current.name = "Thread Pool Drain"
|
|
1055
|
-
|
|
1056
|
-
thread_pool.shutdown
|
|
1057
|
-
end
|
|
1058
|
-
return if drain_thread.join(@worker_drain_timeout)
|
|
1059
|
-
|
|
1060
|
-
Log.warn "Force-killing in-flight app threads after #{@worker_drain_timeout}s drain timeout"
|
|
1061
|
-
thread_pool.instance_variable_get(:@threads).each(&:kill)
|
|
1062
|
-
drain_thread.join
|
|
1063
|
-
end
|
|
1064
|
-
|
|
1065
950
|
# Returns a human-readable description of how a process exited.
|
|
1066
951
|
#
|
|
1067
952
|
# @param status [Process::Status] the exit status of the process
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
@worker = Worker.new(
|
|
65
|
+
@app,
|
|
66
|
+
listeners: @binder.listeners,
|
|
67
|
+
port: @port,
|
|
68
|
+
options: @options,
|
|
69
|
+
http1_ractors: @options[:http1][:ractors] || Cluster.default_http1_ractor_count(1),
|
|
70
|
+
http2_ractors: @options[:http2][:ractors] || Cluster.default_http2_ractor_count(1)
|
|
71
|
+
)
|
|
72
|
+
@worker.start
|
|
73
|
+
rescue
|
|
74
|
+
@stopped = true
|
|
75
|
+
@binder&.close
|
|
76
|
+
raise
|
|
77
|
+
end
|
|
78
|
+
log_initialization if @log
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Starts serving and waits until shutdown.
|
|
85
|
+
#
|
|
86
|
+
# @rbs () -> void
|
|
87
|
+
def run
|
|
88
|
+
start
|
|
89
|
+
join
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Drains requests and closes the server's sockets, threads, and Ractors.
|
|
93
|
+
# Calling shutdown more than once has no effect.
|
|
94
|
+
#
|
|
95
|
+
# @rbs () -> void
|
|
96
|
+
def shutdown
|
|
97
|
+
with_lifecycle do
|
|
98
|
+
return if @stopped
|
|
99
|
+
|
|
100
|
+
@stopped = true
|
|
101
|
+
begin
|
|
102
|
+
@worker&.shutdown
|
|
103
|
+
ensure
|
|
104
|
+
@binder&.close
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Waits for termination and propagates serving thread failures.
|
|
110
|
+
#
|
|
111
|
+
# @rbs () -> void
|
|
112
|
+
def join
|
|
113
|
+
worker = with_lifecycle { @worker }
|
|
114
|
+
raise ThreadError, "server has not been started" unless worker
|
|
115
|
+
|
|
116
|
+
worker.join
|
|
117
|
+
ensure
|
|
118
|
+
shutdown if worker
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# @rbs () -> void
|
|
124
|
+
def log_initialization
|
|
125
|
+
environment = @options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
126
|
+
thread_count = @options[:threads]
|
|
127
|
+
max_threads = @options[:max_threads]
|
|
128
|
+
thread_limit = if max_threads == Float::INFINITY
|
|
129
|
+
" (scaling, no limit)"
|
|
130
|
+
elsif max_threads > thread_count
|
|
131
|
+
" (scaling up to #{max_threads})"
|
|
132
|
+
else
|
|
133
|
+
""
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
Log.info "Server initializing:"
|
|
137
|
+
Log.info "├─ Version: #{VERSION}"
|
|
138
|
+
Log.info "├─ Ruby Version: #{RUBY_DESCRIPTION}"
|
|
139
|
+
Log.info "├─ Environment: #{environment}"
|
|
140
|
+
Log.info "├─ PID: #{Process.pid}"
|
|
141
|
+
Log.info "├─ #{thread_count} worker thread#{"s" if thread_count > 1}#{thread_limit}"
|
|
142
|
+
Log.info "└─ Listening on #{@addresses.join(", ")}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @rbs () { () -> untyped } -> untyped
|
|
146
|
+
def with_lifecycle
|
|
147
|
+
thread = Thread.current
|
|
148
|
+
@lifecycle_available.wait do
|
|
149
|
+
@lifecycle_owner.swap { |owner| owner || thread }.equal?(thread)
|
|
150
|
+
end
|
|
151
|
+
yield
|
|
152
|
+
ensure
|
|
153
|
+
if @lifecycle_owner.value.equal?(thread)
|
|
154
|
+
@lifecycle_owner.swap { nil }
|
|
155
|
+
@lifecycle_available.broadcast
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|