raptor 0.22.3 → 0.23.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 +30 -0
- data/Dockerfile +3 -1
- data/README.md +38 -31
- data/docs/raptor-vs-puma.md +44 -38
- data/docs/talk.md +1295 -0
- data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
- data/ext/raptor_http/extconf.rb +2 -0
- data/ext/raptor_http/raptor_http.c +91 -119
- data/ext/raptor_http2/raptor_http2.c +272 -223
- data/ext/raptor_native/raptor_native.c +38 -20
- data/lib/rackup/handler/raptor.rb +12 -11
- data/lib/raptor/binder.rb +12 -12
- data/lib/raptor/cli.rb +18 -18
- data/lib/raptor/cluster.rb +44 -44
- data/lib/raptor/control_server.rb +3 -2
- data/lib/raptor/detached_body.rb +6 -2
- data/lib/raptor/http.rb +7 -5
- data/lib/raptor/http1.rb +232 -207
- data/lib/raptor/http2.rb +312 -265
- data/lib/raptor/reactor.rb +138 -148
- data/lib/raptor/reuseport_bpf.rb +62 -38
- data/lib/raptor/server.rb +45 -34
- data/lib/raptor/stats.rb +15 -3
- data/lib/raptor/thread_locals.rb +4 -3
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/rackup/handler/raptor.rbs +2 -1
- data/sig/generated/raptor/binder.rbs +9 -9
- data/sig/generated/raptor/cli.rbs +6 -6
- data/sig/generated/raptor/cluster.rbs +9 -9
- data/sig/generated/raptor/control_server.rbs +2 -1
- data/sig/generated/raptor/detached_body.rbs +5 -1
- data/sig/generated/raptor/http.rbs +3 -3
- data/sig/generated/raptor/http1.rbs +100 -91
- data/sig/generated/raptor/http2.rbs +200 -189
- data/sig/generated/raptor/reactor.rbs +107 -118
- data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
- data/sig/generated/raptor/server.rbs +26 -26
- data/sig/generated/raptor/stats.rbs +3 -2
- data/sig/generated/raptor/thread_locals.rbs +4 -3
- metadata +2 -2
- data/docs/brisrails-talk.md +0 -2398
|
@@ -4,27 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
#include "ruby.h"
|
|
6
6
|
#include "ruby/io.h"
|
|
7
|
-
#include
|
|
7
|
+
#include "ruby/thread.h"
|
|
8
8
|
#include <errno.h>
|
|
9
9
|
#include <limits.h>
|
|
10
|
+
#include <sys/uio.h>
|
|
10
11
|
|
|
11
12
|
#ifdef __linux__
|
|
12
13
|
#include <sched.h>
|
|
13
14
|
#include <sys/prctl.h>
|
|
14
|
-
#include <unistd.h>
|
|
15
15
|
#endif
|
|
16
16
|
|
|
17
17
|
static VALUE eEAGAINWaitWritable;
|
|
18
18
|
|
|
19
|
-
static VALUE
|
|
20
|
-
(void)self;
|
|
19
|
+
static VALUE vector_io_writev_nonblock(VALUE self, VALUE io, VALUE strings) {
|
|
21
20
|
Check_Type(strings, T_ARRAY);
|
|
22
21
|
long len = RARRAY_LEN(strings);
|
|
23
22
|
if (len == 0) return LONG2NUM(0);
|
|
24
23
|
if (len > IOV_MAX) len = IOV_MAX;
|
|
25
24
|
|
|
26
25
|
int fd = rb_io_descriptor(io);
|
|
27
|
-
struct iovec *iov =
|
|
26
|
+
struct iovec *iov = ALLOCA_N(struct iovec, len);
|
|
28
27
|
|
|
29
28
|
for (long i = 0; i < len; i++) {
|
|
30
29
|
VALUE str = RARRAY_AREF(strings, i);
|
|
@@ -43,8 +42,9 @@ static VALUE raptor_native_writev_nonblock(VALUE self, VALUE io, VALUE strings)
|
|
|
43
42
|
return LONG2NUM((long)written);
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// Pins the calling thread to the Nth CPU of its current affinity mask, so
|
|
46
|
+
// worker indexes map onto the CPUs the process is actually allowed to use.
|
|
47
|
+
static VALUE cpu_pin(VALUE self, VALUE cpu) {
|
|
48
48
|
#ifdef __linux__
|
|
49
49
|
int worker_index = NUM2INT(cpu);
|
|
50
50
|
cpu_set_t current;
|
|
@@ -55,7 +55,10 @@ static VALUE raptor_native_pin_to_cpu(VALUE self, VALUE cpu) {
|
|
|
55
55
|
int seen = 0;
|
|
56
56
|
for (int candidate = 0; candidate < CPU_SETSIZE; candidate++) {
|
|
57
57
|
if (!CPU_ISSET(candidate, ¤t)) continue;
|
|
58
|
-
if (seen == worker_index) {
|
|
58
|
+
if (seen == worker_index) {
|
|
59
|
+
cpu_id = candidate;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
59
62
|
seen++;
|
|
60
63
|
}
|
|
61
64
|
if (cpu_id < 0) return Qfalse;
|
|
@@ -66,13 +69,11 @@ static VALUE raptor_native_pin_to_cpu(VALUE self, VALUE cpu) {
|
|
|
66
69
|
if (sched_setaffinity(0, sizeof(set), &set) < 0) rb_sys_fail("sched_setaffinity");
|
|
67
70
|
return Qtrue;
|
|
68
71
|
#else
|
|
69
|
-
(void)cpu;
|
|
70
72
|
return Qfalse;
|
|
71
73
|
#endif
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
static VALUE
|
|
75
|
-
(void)self;
|
|
76
|
+
static VALUE cpu_count(VALUE self) {
|
|
76
77
|
#ifdef __linux__
|
|
77
78
|
cpu_set_t set;
|
|
78
79
|
CPU_ZERO(&set);
|
|
@@ -83,8 +84,24 @@ static VALUE raptor_native_cpu_count(VALUE self) {
|
|
|
83
84
|
#endif
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
#ifdef __linux__
|
|
88
|
+
static void *cpu_yield_without_gvl(void *unused) {
|
|
89
|
+
sched_yield();
|
|
90
|
+
return NULL;
|
|
91
|
+
}
|
|
92
|
+
#endif
|
|
93
|
+
|
|
94
|
+
// Thread.pass cannot yield to native threads that have not rejoined
|
|
95
|
+
// Ruby's ready queue.
|
|
96
|
+
static VALUE cpu_yield(VALUE self) {
|
|
97
|
+
rb_thread_schedule();
|
|
98
|
+
#ifdef __linux__
|
|
99
|
+
rb_thread_call_without_gvl(cpu_yield_without_gvl, NULL, NULL, NULL);
|
|
100
|
+
#endif
|
|
101
|
+
return Qnil;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static VALUE subreaper_enable(VALUE self) {
|
|
88
105
|
#if defined(__linux__) && defined(PR_SET_CHILD_SUBREAPER)
|
|
89
106
|
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) < 0) rb_sys_fail("prctl");
|
|
90
107
|
return Qtrue;
|
|
@@ -96,18 +113,19 @@ static VALUE raptor_native_enable_subreaper(VALUE self) {
|
|
|
96
113
|
RUBY_FUNC_EXPORTED void Init_raptor_native(void) {
|
|
97
114
|
rb_ext_ractor_safe(true);
|
|
98
115
|
|
|
116
|
+
rb_global_variable(&eEAGAINWaitWritable);
|
|
117
|
+
eEAGAINWaitWritable = rb_const_get(rb_cIO, rb_intern("EAGAINWaitWritable"));
|
|
118
|
+
|
|
99
119
|
VALUE mRaptor = rb_define_module("Raptor");
|
|
100
120
|
|
|
101
121
|
VALUE mVectorIO = rb_define_module_under(mRaptor, "VectorIO");
|
|
102
|
-
rb_define_singleton_method(mVectorIO, "writev_nonblock",
|
|
122
|
+
rb_define_singleton_method(mVectorIO, "writev_nonblock", vector_io_writev_nonblock, 2);
|
|
103
123
|
|
|
104
124
|
VALUE mCPU = rb_define_module_under(mRaptor, "CPU");
|
|
105
|
-
rb_define_singleton_method(mCPU, "pin",
|
|
106
|
-
rb_define_singleton_method(mCPU, "count",
|
|
125
|
+
rb_define_singleton_method(mCPU, "pin", cpu_pin, 1);
|
|
126
|
+
rb_define_singleton_method(mCPU, "count", cpu_count, 0);
|
|
127
|
+
rb_define_singleton_method(mCPU, "yield", cpu_yield, 0);
|
|
107
128
|
|
|
108
129
|
VALUE mSubreaper = rb_define_module_under(mRaptor, "Subreaper");
|
|
109
|
-
rb_define_singleton_method(mSubreaper, "enable",
|
|
110
|
-
|
|
111
|
-
eEAGAINWaitWritable = rb_const_get(rb_cIO, rb_intern("EAGAINWaitWritable"));
|
|
112
|
-
rb_global_variable(&eEAGAINWaitWritable);
|
|
130
|
+
rb_define_singleton_method(mSubreaper, "enable", subreaper_enable, 0);
|
|
113
131
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# rbs_inline: enabled
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "concurrent/utility/processor_counter"
|
|
5
|
-
|
|
6
4
|
module Rackup
|
|
7
5
|
module Handler
|
|
8
6
|
# Rack handler for booting Raptor through Rackup, `rails server`, or any
|
|
@@ -18,7 +16,8 @@ module Rackup
|
|
|
18
16
|
#
|
|
19
17
|
# @param app [#call] the Rack application to serve
|
|
20
18
|
# @param options [Hash] handler options provided by Rackup or the host
|
|
21
|
-
# @yield
|
|
19
|
+
# @yield before the cluster starts running
|
|
20
|
+
# @yieldparam cluster [::Raptor::Cluster] the cluster instance
|
|
22
21
|
# @return [void]
|
|
23
22
|
#
|
|
24
23
|
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
|
|
@@ -62,7 +61,7 @@ module Rackup
|
|
|
62
61
|
def self.build_cluster_options(app, options)
|
|
63
62
|
defaults = DEFAULT_OPTIONS.dup
|
|
64
63
|
|
|
65
|
-
if user_supplied_options = options.delete(:user_supplied_options)
|
|
64
|
+
if (user_supplied_options = options.delete(:user_supplied_options))
|
|
66
65
|
(options.keys - user_supplied_options).each do |key|
|
|
67
66
|
defaults[key] = options.delete(key)
|
|
68
67
|
end
|
|
@@ -71,7 +70,9 @@ module Rackup
|
|
|
71
70
|
cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
|
|
72
71
|
config_path = options[:Config] || ::Raptor::CLI.default_config_path
|
|
73
72
|
config = config_path ? ::Raptor::CLI.load_config_file(config_path) : {}
|
|
74
|
-
|
|
73
|
+
environment_workers = Integer(ENV["RAPTOR_WORKERS"], 10) if ENV["RAPTOR_WORKERS"]
|
|
74
|
+
environment_threads = Integer(ENV["RAPTOR_THREADS"], 10) if ENV["RAPTOR_THREADS"]
|
|
75
|
+
threads = (options[:Threads] || environment_threads || config[:threads] || cli_defaults[:threads]).to_i
|
|
75
76
|
max_threads = options[:MaxThreads] || ENV["RAPTOR_MAX_THREADS"] || config.fetch(:max_threads, cli_defaults[:max_threads])
|
|
76
77
|
|
|
77
78
|
result = {
|
|
@@ -81,13 +82,13 @@ module Rackup
|
|
|
81
82
|
config[:binds] || ["tcp://#{defaults[:Host]}:#{defaults[:Port]}"]
|
|
82
83
|
end,
|
|
83
84
|
socket_backlog: (config[:socket_backlog] || cli_defaults[:socket_backlog]).to_i,
|
|
84
|
-
drain_accept_queue: config.
|
|
85
|
-
workers: (options[:Workers] ||
|
|
85
|
+
drain_accept_queue: config.fetch(:drain_accept_queue, cli_defaults[:drain_accept_queue]),
|
|
86
|
+
workers: (options[:Workers] || environment_workers || config[:workers] || cli_defaults[:workers]).to_i,
|
|
86
87
|
threads: threads,
|
|
87
88
|
max_threads: ::Raptor::CLI.parse_max_threads(max_threads, threads: threads),
|
|
88
|
-
cpu_affinity: config.
|
|
89
|
-
clean_thread_locals: config.
|
|
90
|
-
clean_fiber_locals: config.
|
|
89
|
+
cpu_affinity: config.fetch(:cpu_affinity, cli_defaults[:cpu_affinity]),
|
|
90
|
+
clean_thread_locals: config.fetch(:clean_thread_locals, cli_defaults[:clean_thread_locals]),
|
|
91
|
+
clean_fiber_locals: config.fetch(:clean_fiber_locals, cli_defaults[:clean_fiber_locals]),
|
|
91
92
|
app: app
|
|
92
93
|
}
|
|
93
94
|
result[:rackup] = config[:rackup] if config.key?(:rackup)
|
|
@@ -105,7 +106,7 @@ module Rackup
|
|
|
105
106
|
result[:before_worker_boot] = config.fetch(:before_worker_boot, cli_defaults[:before_worker_boot])
|
|
106
107
|
result[:before_worker_shutdown] = config.fetch(:before_worker_shutdown, cli_defaults[:before_worker_shutdown])
|
|
107
108
|
result[:before_refork] = config.fetch(:before_refork, cli_defaults[:before_refork])
|
|
108
|
-
result[:stats_file] = config.
|
|
109
|
+
result[:stats_file] = config.fetch(:stats_file, cli_defaults[:stats_file])
|
|
109
110
|
result[:control_url] = config[:control_url] if config.key?(:control_url)
|
|
110
111
|
result[:pid_file] = config[:pid_file] if config.key?(:pid_file)
|
|
111
112
|
result[:stdout_file] = config[:stdout_file] if config.key?(:stdout_file)
|
data/lib/raptor/binder.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# rbs_inline: enabled
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "openssl"
|
|
4
5
|
require "socket"
|
|
5
6
|
require "uri"
|
|
6
7
|
|
|
@@ -56,17 +57,17 @@ module Raptor
|
|
|
56
57
|
# Returns the array of bind URIs.
|
|
57
58
|
#
|
|
58
59
|
# @return [Array<String>]
|
|
59
|
-
attr_reader :bind_uris
|
|
60
|
+
attr_reader :bind_uris #: Array[String]
|
|
60
61
|
|
|
61
62
|
# Returns the kernel `listen()` queue depth for TCP/SSL listeners.
|
|
62
63
|
#
|
|
63
64
|
# @return [Integer]
|
|
64
|
-
attr_reader :socket_backlog
|
|
65
|
+
attr_reader :socket_backlog #: Integer
|
|
65
66
|
|
|
66
67
|
# Returns the array of listening sockets.
|
|
67
68
|
#
|
|
68
69
|
# @return [Array<TCPServer, UNIXServer, SslListener, H2cListener>]
|
|
69
|
-
attr_reader :listeners
|
|
70
|
+
attr_reader :listeners #: Array[TCPServer | UNIXServer | SslListener | H2cListener]
|
|
70
71
|
|
|
71
72
|
# Creates a new Binder and binds each URI. `localhost` expands to both
|
|
72
73
|
# IPv4 and IPv6 loopback; when `inherited_fds` supplies file descriptors
|
|
@@ -113,8 +114,8 @@ module Raptor
|
|
|
113
114
|
end
|
|
114
115
|
end
|
|
115
116
|
|
|
116
|
-
# Returns the port of the first TCP
|
|
117
|
-
# is configured.
|
|
117
|
+
# Returns the port of the first TCP, SSL, or cleartext HTTP/2 listener,
|
|
118
|
+
# or 0 when none is configured.
|
|
118
119
|
#
|
|
119
120
|
# @return [Integer]
|
|
120
121
|
#
|
|
@@ -185,7 +186,7 @@ module Raptor
|
|
|
185
186
|
|
|
186
187
|
# Creates fresh listeners for the given URI.
|
|
187
188
|
#
|
|
188
|
-
# @param uri [URI] the parsed bind URI
|
|
189
|
+
# @param uri [URI::Generic] the parsed bind URI
|
|
189
190
|
# @return [Array<TCPServer, UNIXServer, SslListener, H2cListener>]
|
|
190
191
|
# @raise [UnknownBindSchemeError] if the URI scheme is not supported
|
|
191
192
|
#
|
|
@@ -208,7 +209,7 @@ module Raptor
|
|
|
208
209
|
# Reconstructs listeners for the given URI from inherited file
|
|
209
210
|
# descriptors.
|
|
210
211
|
#
|
|
211
|
-
# @param uri [URI] the parsed bind URI the FDs were bound to
|
|
212
|
+
# @param uri [URI::Generic] the parsed bind URI the FDs were bound to
|
|
212
213
|
# @param filenos [Array<Integer>] file descriptors to wrap
|
|
213
214
|
# @return [Array<TCPServer, UNIXServer, SslListener, H2cListener>]
|
|
214
215
|
# @raise [UnknownBindSchemeError] if the URI scheme is not supported
|
|
@@ -240,7 +241,7 @@ module Raptor
|
|
|
240
241
|
# @rbs (String? host, Integer? port) -> Array[TCPServer]
|
|
241
242
|
def create_tcp_listeners(host, port)
|
|
242
243
|
if host == "localhost"
|
|
243
|
-
return loopback_addresses.
|
|
244
|
+
return loopback_addresses.flat_map { |address| create_tcp_listeners(address, port) }
|
|
244
245
|
end
|
|
245
246
|
|
|
246
247
|
host = host[1..-2] if host&.start_with?("[")
|
|
@@ -250,6 +251,7 @@ module Raptor
|
|
|
250
251
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
251
252
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) if Socket.const_defined?(:SO_REUSEPORT)
|
|
252
253
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
254
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT, 1) if Socket.const_defined?(:TCP_DEFER_ACCEPT)
|
|
253
255
|
socket.bind(addrinfo)
|
|
254
256
|
socket.listen(@socket_backlog)
|
|
255
257
|
|
|
@@ -299,7 +301,7 @@ module Raptor
|
|
|
299
301
|
#
|
|
300
302
|
# @param host [String, nil] hostname or IP address to bind to
|
|
301
303
|
# @param port [Integer, nil] port number to bind to
|
|
302
|
-
# @param ssl_params [Hash
|
|
304
|
+
# @param ssl_params [Hash{String => String}] SSL options (`cert` and `key` file paths)
|
|
303
305
|
# @return [Array<SslListener>] array containing the created SSL listener(s)
|
|
304
306
|
#
|
|
305
307
|
# @rbs (String? host, Integer? port, Hash[String, String] ssl_params) -> Array[SslListener]
|
|
@@ -312,13 +314,11 @@ module Raptor
|
|
|
312
314
|
# Builds a frozen `OpenSSL::SSL::SSLContext` configured for HTTP/2 and
|
|
313
315
|
# HTTP/1.1 ALPN negotiation.
|
|
314
316
|
#
|
|
315
|
-
# @param ssl_params [Hash
|
|
317
|
+
# @param ssl_params [Hash{String => String}] SSL options (`cert` and `key` file paths)
|
|
316
318
|
# @return [OpenSSL::SSL::SSLContext]
|
|
317
319
|
#
|
|
318
320
|
# @rbs (Hash[String, String] ssl_params) -> OpenSSL::SSL::SSLContext
|
|
319
321
|
def build_ssl_context(ssl_params)
|
|
320
|
-
require "openssl"
|
|
321
|
-
|
|
322
322
|
OpenSSL::SSL::SSLContext.new.tap do |ssl_context|
|
|
323
323
|
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(ssl_params["cert"]))
|
|
324
324
|
ssl_context.key = OpenSSL::PKey.read(File.read(ssl_params["key"]))
|
data/lib/raptor/cli.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "optparse"
|
|
|
7
7
|
require "concurrent/utility/processor_counter"
|
|
8
8
|
|
|
9
9
|
require_relative "cluster"
|
|
10
|
+
require_relative "version"
|
|
10
11
|
|
|
11
12
|
module Raptor
|
|
12
13
|
# Command-line interface for the Raptor web server.
|
|
@@ -48,18 +49,18 @@ module Raptor
|
|
|
48
49
|
write_timeout: 5,
|
|
49
50
|
max_body_size: nil,
|
|
50
51
|
body_spool_threshold: 1024 * 1024
|
|
51
|
-
},
|
|
52
|
+
}.freeze,
|
|
52
53
|
http1: {
|
|
53
54
|
ractors: nil,
|
|
54
55
|
persistent_data_timeout: 65,
|
|
55
56
|
max_keepalive_requests: 1000
|
|
56
|
-
},
|
|
57
|
+
}.freeze,
|
|
57
58
|
http2: {
|
|
58
59
|
ractors: nil,
|
|
59
60
|
max_concurrent_streams: 100,
|
|
60
61
|
keepalive_interval: 10,
|
|
61
62
|
keepalive_timeout: 5
|
|
62
|
-
},
|
|
63
|
+
}.freeze,
|
|
63
64
|
worker_boot_timeout: 60,
|
|
64
65
|
worker_timeout: 60,
|
|
65
66
|
worker_drain_timeout: 25,
|
|
@@ -74,7 +75,7 @@ module Raptor
|
|
|
74
75
|
pid_file: nil,
|
|
75
76
|
stdout_file: nil,
|
|
76
77
|
stderr_file: nil,
|
|
77
|
-
access_log_file: nil
|
|
78
|
+
access_log_file: nil
|
|
78
79
|
}.freeze
|
|
79
80
|
|
|
80
81
|
DEFAULT_CONFIG_PATHS = ["raptor.rb", "config/raptor.rb"].freeze
|
|
@@ -131,17 +132,17 @@ module Raptor
|
|
|
131
132
|
max_threads
|
|
132
133
|
end
|
|
133
134
|
|
|
134
|
-
# @rbs @command: Symbol
|
|
135
135
|
# @rbs @options: Hash[Symbol, untyped]
|
|
136
|
+
# @rbs @command: Symbol
|
|
136
137
|
# @rbs @parser: OptionParser
|
|
137
138
|
|
|
138
|
-
# Creates a new CLI
|
|
139
|
-
#
|
|
140
|
-
# flag.
|
|
139
|
+
# Creates a new CLI from `argv`. A rackup file may be given as the
|
|
140
|
+
# first positional argument; every other value is parsed as a flag.
|
|
141
141
|
#
|
|
142
142
|
# @param argv [Array<String>] command-line arguments to parse
|
|
143
143
|
# @return [void]
|
|
144
144
|
# @raise [OptionParser::ParseError] if invalid options are provided
|
|
145
|
+
# @raise [ArgumentError] if the config file or max threads value is invalid
|
|
145
146
|
#
|
|
146
147
|
# @rbs (Array[String] argv) -> void
|
|
147
148
|
def initialize(argv)
|
|
@@ -203,7 +204,7 @@ module Raptor
|
|
|
203
204
|
end
|
|
204
205
|
end
|
|
205
206
|
|
|
206
|
-
# Returns the path from a `-c`/`--config` flag in `argv`,
|
|
207
|
+
# Returns the path from a `-c`/`--config` flag in `argv`, recognizing
|
|
207
208
|
# all four OptionParser-accepted forms (`-c PATH`, `-cPATH`,
|
|
208
209
|
# `--config PATH`, `--config=PATH`).
|
|
209
210
|
#
|
|
@@ -258,6 +259,8 @@ module Raptor
|
|
|
258
259
|
#
|
|
259
260
|
# @rbs () -> OptionParser
|
|
260
261
|
def create_parser
|
|
262
|
+
cli_binds = []
|
|
263
|
+
|
|
261
264
|
OptionParser.new do |opts|
|
|
262
265
|
opts.banner = "Usage: raptor [options] [rackup file]"
|
|
263
266
|
|
|
@@ -266,11 +269,8 @@ module Raptor
|
|
|
266
269
|
end
|
|
267
270
|
|
|
268
271
|
opts.on("-b", "--bind URI", String, "Bind address (default: tcp://0.0.0.0:9292)") do |bind|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
else
|
|
272
|
-
@options[:binds] << bind
|
|
273
|
-
end
|
|
272
|
+
cli_binds << bind
|
|
273
|
+
@options[:binds] = cli_binds
|
|
274
274
|
end
|
|
275
275
|
|
|
276
276
|
opts.on("--socket-backlog NUM", Integer, "Socket listen backlog (default: 1024)") do |num|
|
|
@@ -333,7 +333,7 @@ module Raptor
|
|
|
333
333
|
@options[:connection][:body_spool_threshold] = bytes
|
|
334
334
|
end
|
|
335
335
|
|
|
336
|
-
opts.on("--http1-ractors NUM", Integer, "Number of HTTP/1.1 pipeline ractors per worker (default: `round(cores / workers)`, clamped to 1
|
|
336
|
+
opts.on("--http1-ractors NUM", Integer, "Number of HTTP/1.1 pipeline ractors per worker (default: `round(cores / workers)`, clamped to 1..#{Cluster::HTTP1_RACTOR_COUNT_CAP})") do |num|
|
|
337
337
|
@options[:http1][:ractors] = num
|
|
338
338
|
end
|
|
339
339
|
|
|
@@ -345,7 +345,7 @@ module Raptor
|
|
|
345
345
|
@options[:http1][:max_keepalive_requests] = num
|
|
346
346
|
end
|
|
347
347
|
|
|
348
|
-
opts.on("--http2-ractors NUM", Integer, "Number of HTTP/2 pipeline ractors per worker (default: `round(cores / workers)`, clamped to 1
|
|
348
|
+
opts.on("--http2-ractors NUM", Integer, "Number of HTTP/2 pipeline ractors per worker (default: `round(cores / workers)`, clamped to 1..#{Cluster::HTTP2_RACTOR_COUNT_CAP})") do |num|
|
|
349
349
|
@options[:http2][:ractors] = num
|
|
350
350
|
end
|
|
351
351
|
|
|
@@ -377,7 +377,7 @@ module Raptor
|
|
|
377
377
|
@options[:worker_shutdown_timeout] = timeout
|
|
378
378
|
end
|
|
379
379
|
|
|
380
|
-
opts.on("--refork-after NUM", Integer, "Refork workers from a warmed source after any worker crosses NUM requests; 0 disables (default: 1000)") do |num|
|
|
380
|
+
opts.on("--refork-after NUM", Integer, "Refork workers from a warmed source after any worker crosses NUM requests; 0 disables (default: 1000 on Linux, off elsewhere)") do |num|
|
|
381
381
|
@options[:refork_after] = num
|
|
382
382
|
end
|
|
383
383
|
|
|
@@ -411,7 +411,7 @@ module Raptor
|
|
|
411
411
|
end
|
|
412
412
|
|
|
413
413
|
opts.on("-v", "--version", "Show version") do
|
|
414
|
-
puts
|
|
414
|
+
puts VERSION
|
|
415
415
|
exit
|
|
416
416
|
end
|
|
417
417
|
end
|
data/lib/raptor/cluster.rb
CHANGED
|
@@ -9,16 +9,18 @@ require "concurrent/utility/processor_counter"
|
|
|
9
9
|
require "rack/builder"
|
|
10
10
|
require "ractor-pool"
|
|
11
11
|
|
|
12
|
-
require_relative "log"
|
|
13
12
|
require_relative "binder"
|
|
14
13
|
require_relative "control_server"
|
|
15
|
-
require_relative "reactor"
|
|
16
|
-
require_relative "server"
|
|
17
|
-
require_relative "reuseport_bpf"
|
|
18
14
|
require_relative "http1"
|
|
19
15
|
require_relative "http2"
|
|
16
|
+
require_relative "log"
|
|
17
|
+
require_relative "raptor_native"
|
|
18
|
+
require_relative "reactor"
|
|
19
|
+
require_relative "reuseport_bpf"
|
|
20
|
+
require_relative "server"
|
|
20
21
|
require_relative "stats"
|
|
21
22
|
require_relative "systemd"
|
|
23
|
+
require_relative "version"
|
|
22
24
|
|
|
23
25
|
module Raptor
|
|
24
26
|
# Forks and supervises worker processes. Handles graceful shutdown on
|
|
@@ -100,7 +102,7 @@ module Raptor
|
|
|
100
102
|
# @rbs @access_log_io: IO?
|
|
101
103
|
# @rbs @launch_command: String?
|
|
102
104
|
# @rbs @launch_argv: Array[String]?
|
|
103
|
-
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void
|
|
105
|
+
# @rbs @on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
104
106
|
# @rbs @binder: Binder
|
|
105
107
|
# @rbs @server_port: Integer
|
|
106
108
|
# @rbs @app: untyped
|
|
@@ -115,7 +117,7 @@ module Raptor
|
|
|
115
117
|
# @rbs @hot_restart_requested: bool
|
|
116
118
|
# @rbs @refork_thresholds: Array[Integer]
|
|
117
119
|
# @rbs @refork_requested: bool
|
|
118
|
-
# @rbs @
|
|
120
|
+
# @rbs @refork_threshold_index: Integer
|
|
119
121
|
# @rbs @seed_pid: Integer?
|
|
120
122
|
# @rbs @seed_ready: bool
|
|
121
123
|
# @rbs @seed_vacated_index: Integer?
|
|
@@ -200,7 +202,7 @@ module Raptor
|
|
|
200
202
|
|
|
201
203
|
Dir.chdir(options[:chdir]) if options[:chdir]
|
|
202
204
|
|
|
203
|
-
inherited_fds = if raw = ENV.delete(INHERITED_FDS_ENV)
|
|
205
|
+
inherited_fds = if (raw = ENV.delete(INHERITED_FDS_ENV))
|
|
204
206
|
JSON.parse(raw)
|
|
205
207
|
elsif (systemd_fds = Systemd.listen_fds).any?
|
|
206
208
|
Systemd.clear_listen_env
|
|
@@ -225,7 +227,7 @@ module Raptor
|
|
|
225
227
|
|
|
226
228
|
@refork_thresholds = normalize_refork_thresholds(options[:refork_after])
|
|
227
229
|
@refork_requested = false
|
|
228
|
-
@
|
|
230
|
+
@refork_threshold_index = 0
|
|
229
231
|
@seed_pid = nil
|
|
230
232
|
@seed_ready = false
|
|
231
233
|
@seed_vacated_index = nil
|
|
@@ -261,7 +263,7 @@ module Raptor
|
|
|
261
263
|
|
|
262
264
|
File.open(@pid_file, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pid_file
|
|
263
265
|
|
|
264
|
-
@bpf_active = ReuseportBPF.setup(@worker_count)
|
|
266
|
+
@bpf_active = ReuseportBPF.setup(@worker_count, @binder.listeners.grep(TCPServer))
|
|
265
267
|
|
|
266
268
|
@control_server&.bind
|
|
267
269
|
@worker_count.times { |index| spawn_worker(index) }
|
|
@@ -310,9 +312,11 @@ module Raptor
|
|
|
310
312
|
@stats.all
|
|
311
313
|
end
|
|
312
314
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
#
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
# Returns cluster statistics for the control server. For an adaptive
|
|
318
|
+
# pool, `max_threads` is the number of threads currently running so
|
|
319
|
+
# utilization reflects the capacity available at that moment.
|
|
316
320
|
#
|
|
317
321
|
# @return [Hash] cluster statistics
|
|
318
322
|
#
|
|
@@ -354,11 +358,9 @@ module Raptor
|
|
|
354
358
|
}
|
|
355
359
|
end
|
|
356
360
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def timestamp(timestamp)
|
|
361
|
-
Time.at(timestamp).utc.iso8601(6)
|
|
361
|
+
# @rbs (Numeric time) -> String
|
|
362
|
+
def timestamp(time)
|
|
363
|
+
Time.at(time).utc.iso8601(6)
|
|
362
364
|
end
|
|
363
365
|
|
|
364
366
|
# Returns the inherited-FDs hash for a systemd socket-activation handoff,
|
|
@@ -449,7 +451,7 @@ module Raptor
|
|
|
449
451
|
def pid_alive?(pid)
|
|
450
452
|
Process.kill(0, pid)
|
|
451
453
|
true
|
|
452
|
-
rescue Errno::ESRCH
|
|
454
|
+
rescue Errno::ESRCH
|
|
453
455
|
false
|
|
454
456
|
rescue Errno::EPERM
|
|
455
457
|
true
|
|
@@ -513,14 +515,14 @@ module Raptor
|
|
|
513
515
|
return unless candidate
|
|
514
516
|
|
|
515
517
|
candidate_index, candidate_requests = candidate
|
|
516
|
-
threshold = @refork_thresholds[@
|
|
518
|
+
threshold = @refork_thresholds[@refork_threshold_index]
|
|
517
519
|
|
|
518
520
|
if @refork_requested
|
|
519
521
|
@refork_requested = false
|
|
520
522
|
elsif !threshold || candidate_requests < threshold
|
|
521
523
|
return
|
|
522
524
|
else
|
|
523
|
-
@
|
|
525
|
+
@refork_threshold_index += 1
|
|
524
526
|
end
|
|
525
527
|
|
|
526
528
|
promote_worker_to_seed(candidate_index)
|
|
@@ -561,7 +563,6 @@ module Raptor
|
|
|
561
563
|
|
|
562
564
|
retire_current_seed
|
|
563
565
|
Log.info "Promoting worker #{index} to seed for phased refork"
|
|
564
|
-
ReuseportBPF.mark_unavailable(index) if @bpf_active
|
|
565
566
|
Process.kill("URG", pid) rescue nil
|
|
566
567
|
@workers.delete(index)
|
|
567
568
|
@seed_pid = pid
|
|
@@ -800,7 +801,7 @@ module Raptor
|
|
|
800
801
|
trap("USR2", "IGNORE")
|
|
801
802
|
trap("URG") { promote_to_seed = true } if @fork_r
|
|
802
803
|
|
|
803
|
-
|
|
804
|
+
CPU.pin(index) if @cpu_affinity && CPU.count >= @worker_count
|
|
804
805
|
|
|
805
806
|
started_at = Process.clock_gettime(Process::CLOCK_REALTIME)
|
|
806
807
|
request_count = 0
|
|
@@ -813,7 +814,7 @@ module Raptor
|
|
|
813
814
|
backlog: 0,
|
|
814
815
|
busy_threads: 0,
|
|
815
816
|
thread_capacity: @thread_count,
|
|
816
|
-
started_at
|
|
817
|
+
started_at: started_at,
|
|
817
818
|
last_checkin: started_at,
|
|
818
819
|
booted: false
|
|
819
820
|
)
|
|
@@ -872,6 +873,7 @@ module Raptor
|
|
|
872
873
|
end
|
|
873
874
|
|
|
874
875
|
reactor = Reactor.new(
|
|
876
|
+
http1,
|
|
875
877
|
http1_ractor_pool,
|
|
876
878
|
http2_ractor_pool,
|
|
877
879
|
thread_pool,
|
|
@@ -881,24 +883,21 @@ module Raptor
|
|
|
881
883
|
)
|
|
882
884
|
reactor_thread = reactor.run
|
|
883
885
|
|
|
884
|
-
|
|
885
|
-
bpf_listeners = ReuseportBPF.create_worker_listeners(@binder.bind_uris, index, @binder.socket_backlog)
|
|
886
|
+
bpf_listeners = if @bpf_active
|
|
886
887
|
ReuseportBPF.enable_load_reporting(index)
|
|
887
|
-
|
|
888
|
-
bpf_listeners + non_tcp_listeners
|
|
888
|
+
ReuseportBPF.create_worker_listeners(index, @binder.socket_backlog)
|
|
889
889
|
else
|
|
890
|
-
|
|
890
|
+
[]
|
|
891
891
|
end
|
|
892
892
|
|
|
893
893
|
server = Server.new(
|
|
894
|
-
@binder,
|
|
895
894
|
reactor,
|
|
896
895
|
thread_pool,
|
|
897
896
|
http1,
|
|
898
897
|
http2,
|
|
899
898
|
connection_options: @connection_options,
|
|
900
899
|
drain_accept_queue: @drain_accept_queue,
|
|
901
|
-
listeners:
|
|
900
|
+
listeners: bpf_listeners + @binder.listeners,
|
|
902
901
|
worker_index: (index if @bpf_active)
|
|
903
902
|
)
|
|
904
903
|
server_thread = server.run
|
|
@@ -919,7 +918,7 @@ module Raptor
|
|
|
919
918
|
backlog: reactor.backlog,
|
|
920
919
|
busy_threads: thread_pool.active_count,
|
|
921
920
|
thread_capacity: thread_pool.size,
|
|
922
|
-
started_at
|
|
921
|
+
started_at: started_at,
|
|
923
922
|
last_checkin: Process.clock_gettime(Process::CLOCK_REALTIME),
|
|
924
923
|
booted: true
|
|
925
924
|
)
|
|
@@ -944,6 +943,8 @@ module Raptor
|
|
|
944
943
|
end
|
|
945
944
|
|
|
946
945
|
server_thread.join
|
|
946
|
+
# A seed never accepts again, and the workers it forks bind their own.
|
|
947
|
+
bpf_listeners.each(&:close) if promote_to_seed
|
|
947
948
|
http1.shutdown
|
|
948
949
|
http2.shutdown(reactor)
|
|
949
950
|
reactor.drain_detached_bodies(@worker_drain_timeout)
|
|
@@ -972,11 +973,6 @@ module Raptor
|
|
|
972
973
|
trap("TERM") { seed_shutdown = true }
|
|
973
974
|
trap("URG", "IGNORE")
|
|
974
975
|
|
|
975
|
-
child_pids = []
|
|
976
|
-
trap("CHLD") do
|
|
977
|
-
child_pids.reject! { Process.wait(_1, Process::WNOHANG) rescue true }
|
|
978
|
-
end
|
|
979
|
-
|
|
980
976
|
@resp_w.write([0].pack("L"))
|
|
981
977
|
|
|
982
978
|
until seed_shutdown
|
|
@@ -986,9 +982,15 @@ module Raptor
|
|
|
986
982
|
break unless bytes.is_a?(String) && bytes.bytesize == 8
|
|
987
983
|
|
|
988
984
|
slot_index, child_phase = bytes.unpack("LL")
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
985
|
+
# Forks through a short-lived intermediate process so the worker
|
|
986
|
+
# reparents to the subreaper master, which supervises and reaps it
|
|
987
|
+
# like a worker it forked itself.
|
|
988
|
+
intermediate_pid = fork do
|
|
989
|
+
pid = fork { run_worker(slot_index, child_phase) }
|
|
990
|
+
@resp_w.write([pid].pack("L")) rescue nil
|
|
991
|
+
exit!
|
|
992
|
+
end
|
|
993
|
+
Process.wait(intermediate_pid)
|
|
992
994
|
end
|
|
993
995
|
rescue Errno::EPIPE, IOError
|
|
994
996
|
end
|
|
@@ -1002,16 +1004,16 @@ module Raptor
|
|
|
1002
1004
|
#
|
|
1003
1005
|
# @rbs (AtomicThreadPool thread_pool) -> void
|
|
1004
1006
|
def drain_thread_pool(thread_pool)
|
|
1005
|
-
|
|
1007
|
+
drain_thread = Thread.new do
|
|
1006
1008
|
Thread.current.name = "Thread Pool Drain"
|
|
1007
1009
|
|
|
1008
1010
|
thread_pool.shutdown
|
|
1009
1011
|
end
|
|
1010
|
-
return if
|
|
1012
|
+
return if drain_thread.join(@worker_drain_timeout)
|
|
1011
1013
|
|
|
1012
1014
|
Log.warn "Force-killing in-flight app threads after #{@worker_drain_timeout}s drain timeout"
|
|
1013
1015
|
thread_pool.instance_variable_get(:@threads).each(&:kill)
|
|
1014
|
-
|
|
1016
|
+
drain_thread.join
|
|
1015
1017
|
end
|
|
1016
1018
|
|
|
1017
1019
|
# Returns a human-readable description of how a process exited.
|
|
@@ -1036,8 +1038,6 @@ module Raptor
|
|
|
1036
1038
|
#
|
|
1037
1039
|
# @rbs () -> void
|
|
1038
1040
|
def shutdown
|
|
1039
|
-
return if @shutdown
|
|
1040
|
-
|
|
1041
1041
|
@shutdown = true
|
|
1042
1042
|
end
|
|
1043
1043
|
|