raptor 0.22.2 → 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 +35 -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 +233 -208
- data/lib/raptor/http2.rb +314 -267
- data/lib/raptor/reactor.rb +139 -149
- data/lib/raptor/reuseport_bpf.rb +62 -38
- data/lib/raptor/server.rb +53 -38
- 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
data/lib/raptor/reuseport_bpf.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "socket"
|
|
5
|
-
require "uri"
|
|
6
5
|
|
|
7
6
|
begin
|
|
8
7
|
require "libbpf-ruby"
|
|
@@ -15,13 +14,28 @@ module Raptor
|
|
|
15
14
|
#
|
|
16
15
|
# Auto-enabled on Linux when the `libbpf-ruby` gem is installed and the
|
|
17
16
|
# BPF object file has been compiled. Falls back silently to standard
|
|
18
|
-
# `SO_REUSEPORT` when either is missing
|
|
19
|
-
# the BPF
|
|
20
|
-
#
|
|
17
|
+
# `SO_REUSEPORT` when either is missing, or when there are more workers
|
|
18
|
+
# or listeners than the BPF maps have slots for. Raises when the kernel
|
|
19
|
+
# refuses the BPF program. Wraps `tcp://` bindings only; non-tcp bindings
|
|
20
|
+
# are left to the binder's shared listeners.
|
|
21
21
|
#
|
|
22
22
|
module ReuseportBPF
|
|
23
23
|
LIBBPF_LOADED = !!defined?(LibBPFRuby)
|
|
24
24
|
BPF_OBJECT_PATH = File.expand_path("../../ext/raptor_bpf/reuseport_select.bpf.o", __dir__)
|
|
25
|
+
TCP_INFO_UNACKED_OFFSET = 24
|
|
26
|
+
UNAVAILABLE_LOAD = 0xffffffff
|
|
27
|
+
MAX_WORKERS = 64
|
|
28
|
+
MAX_LISTENERS = 16
|
|
29
|
+
|
|
30
|
+
# @rbs self.@object: untyped
|
|
31
|
+
# @rbs self.@program_fd: Integer
|
|
32
|
+
# @rbs self.@socks_fd: Integer
|
|
33
|
+
# @rbs self.@loads_fd: Integer?
|
|
34
|
+
# @rbs self.@shared_listeners: Array[TCPServer]
|
|
35
|
+
# @rbs self.@worker_listeners: Array[TCPServer]
|
|
36
|
+
# @rbs self.@load_key: String
|
|
37
|
+
# @rbs self.@load_value: String
|
|
38
|
+
# @rbs self.@accepted_load_value: String
|
|
25
39
|
|
|
26
40
|
# Returns true when the preconditions for BPF-directed reuseport are
|
|
27
41
|
# met on this platform.
|
|
@@ -33,18 +47,22 @@ module Raptor
|
|
|
33
47
|
LIBBPF_LOADED && File.exist?(BPF_OBJECT_PATH)
|
|
34
48
|
end
|
|
35
49
|
|
|
36
|
-
# Prepares BPF-directed routing for `worker_count` workers
|
|
50
|
+
# Prepares BPF-directed routing for `worker_count` workers across the
|
|
51
|
+
# addresses `shared_listeners` are bound to.
|
|
37
52
|
#
|
|
38
53
|
# Returns true when BPF-directed dispatch is active. Returns false
|
|
39
|
-
# when the platform is unsupported
|
|
40
|
-
# the program.
|
|
54
|
+
# when the platform is unsupported or the counts exceed the map slots.
|
|
55
|
+
# Raises when the kernel refuses the program.
|
|
41
56
|
#
|
|
42
57
|
# @param worker_count [Integer] number of worker processes that will bind sockets
|
|
58
|
+
# @param shared_listeners [Array<TCPServer>] the binder's tcp listeners
|
|
43
59
|
# @return [Boolean]
|
|
44
60
|
#
|
|
45
|
-
# @rbs (Integer worker_count) -> bool
|
|
46
|
-
def self.setup(worker_count)
|
|
47
|
-
return false unless supported?
|
|
61
|
+
# @rbs (Integer worker_count, Array[TCPServer] shared_listeners) -> bool
|
|
62
|
+
def self.setup(worker_count, shared_listeners)
|
|
63
|
+
return false unless supported? && worker_count <= MAX_WORKERS && shared_listeners.length <= MAX_LISTENERS
|
|
64
|
+
|
|
65
|
+
@shared_listeners = shared_listeners
|
|
48
66
|
|
|
49
67
|
@object = LibBPFRuby::Object.new(BPF_OBJECT_PATH)
|
|
50
68
|
@program_fd = @object.program_fd("select_less_loaded")
|
|
@@ -55,32 +73,27 @@ module Raptor
|
|
|
55
73
|
true
|
|
56
74
|
end
|
|
57
75
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
76
|
+
# Creates this worker's BPF-routed listeners at the shared listeners'
|
|
77
|
+
# bound addresses, including resolved `localhost` addresses and ports
|
|
78
|
+
# assigned by the kernel for port 0.
|
|
60
79
|
#
|
|
61
|
-
# @param bind_uris [Array<String>] the configured bind URIs
|
|
62
80
|
# @param worker_index [Integer] slot index for this worker in the socks map
|
|
63
81
|
# @param socket_backlog [Integer] kernel listen() queue depth
|
|
64
82
|
# @return [Array<TCPServer>] the tcp listening sockets created for this worker
|
|
65
83
|
#
|
|
66
|
-
# @rbs (
|
|
67
|
-
def self.create_worker_listeners(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
next unless uri.scheme == "tcp"
|
|
71
|
-
|
|
72
|
-
host = uri.host
|
|
73
|
-
host = host[1..-2] if host&.start_with?("[")
|
|
74
|
-
|
|
75
|
-
addrinfo = Addrinfo.tcp(host, uri.port)
|
|
84
|
+
# @rbs (Integer worker_index, Integer socket_backlog) -> Array[TCPServer]
|
|
85
|
+
def self.create_worker_listeners(worker_index, socket_backlog)
|
|
86
|
+
@worker_listeners = @shared_listeners.each_with_index.map do |shared_listener, listener_index|
|
|
87
|
+
addrinfo = shared_listener.local_address
|
|
76
88
|
socket = Socket.new(addrinfo.afamily, Socket::SOCK_STREAM, 0)
|
|
77
89
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
78
90
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
|
|
79
91
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
92
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT, 1)
|
|
80
93
|
socket.bind(addrinfo)
|
|
81
94
|
socket.listen(socket_backlog)
|
|
82
95
|
|
|
83
|
-
LibBPFRuby.sockmap_update(@socks_fd, [worker_index].pack("L"), socket)
|
|
96
|
+
LibBPFRuby.sockmap_update(@socks_fd, [listener_index * MAX_WORKERS + worker_index].pack("L"), socket)
|
|
84
97
|
LibBPFRuby.attach_reuseport(socket, @program_fd)
|
|
85
98
|
|
|
86
99
|
tcp_server = TCPServer.for_fd(socket.fileno)
|
|
@@ -102,21 +115,22 @@ module Raptor
|
|
|
102
115
|
@accepted_load_value = String.new("\x00\x00\x00\x00", encoding: Encoding::ASCII_8BIT)
|
|
103
116
|
end
|
|
104
117
|
|
|
105
|
-
# Publishes this worker's
|
|
118
|
+
# Publishes this worker's load to the BPF map.
|
|
106
119
|
#
|
|
107
120
|
# @param backlog [Integer] the worker's current reactor backlog
|
|
108
121
|
# @return [void]
|
|
109
122
|
#
|
|
110
123
|
# @rbs (Integer backlog) -> void
|
|
111
124
|
def self.update_load(backlog)
|
|
112
|
-
|
|
113
|
-
@load_value.setbyte(
|
|
114
|
-
@load_value.setbyte(
|
|
115
|
-
@load_value.setbyte(
|
|
125
|
+
load = backlog + accept_queue_length
|
|
126
|
+
@load_value.setbyte(0, load & 0xff)
|
|
127
|
+
@load_value.setbyte(1, (load >> 8) & 0xff)
|
|
128
|
+
@load_value.setbyte(2, (load >> 16) & 0xff)
|
|
129
|
+
@load_value.setbyte(3, (load >> 24) & 0xff)
|
|
116
130
|
LibBPFRuby.map_update(@loads_fd, @load_key, @load_value)
|
|
117
131
|
end
|
|
118
132
|
|
|
119
|
-
# Publishes an accept-side
|
|
133
|
+
# Publishes an accept-side load estimate. This uses a separate value
|
|
120
134
|
# buffer because the server and load reporter call into the map from
|
|
121
135
|
# different threads.
|
|
122
136
|
#
|
|
@@ -125,24 +139,34 @@ module Raptor
|
|
|
125
139
|
#
|
|
126
140
|
# @rbs (Integer backlog) -> void
|
|
127
141
|
def self.update_accepted_load(backlog)
|
|
128
|
-
|
|
129
|
-
@accepted_load_value.setbyte(
|
|
130
|
-
@accepted_load_value.setbyte(
|
|
131
|
-
@accepted_load_value.setbyte(
|
|
142
|
+
load = backlog + accept_queue_length
|
|
143
|
+
@accepted_load_value.setbyte(0, load & 0xff)
|
|
144
|
+
@accepted_load_value.setbyte(1, (load >> 8) & 0xff)
|
|
145
|
+
@accepted_load_value.setbyte(2, (load >> 16) & 0xff)
|
|
146
|
+
@accepted_load_value.setbyte(3, (load >> 24) & 0xff)
|
|
132
147
|
LibBPFRuby.map_update(@loads_fd, @load_key, @accepted_load_value)
|
|
133
148
|
end
|
|
134
149
|
|
|
135
|
-
#
|
|
136
|
-
#
|
|
150
|
+
# Parks a worker's load at the maximum so BPF favors other workers.
|
|
151
|
+
# Stop publishing its load first to avoid overwriting the sentinel.
|
|
137
152
|
#
|
|
138
153
|
# @param worker_index [Integer] slot index for the worker to mark
|
|
139
154
|
# @return [void]
|
|
140
155
|
#
|
|
141
156
|
# @rbs (Integer worker_index) -> void
|
|
142
157
|
def self.mark_unavailable(worker_index)
|
|
143
|
-
|
|
158
|
+
LibBPFRuby.map_update(@loads_fd, [worker_index + 1].pack("L"), [UNAVAILABLE_LOAD].pack("L"))
|
|
159
|
+
end
|
|
144
160
|
|
|
145
|
-
|
|
161
|
+
# Returns the number of connections waiting in this worker's accept
|
|
162
|
+
# queues, which the reactor backlog cannot see.
|
|
163
|
+
#
|
|
164
|
+
# @return [Integer]
|
|
165
|
+
#
|
|
166
|
+
# @rbs () -> Integer
|
|
167
|
+
def self.accept_queue_length
|
|
168
|
+
@worker_listeners.sum { |listener| listener.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).data.unpack1("L", offset: TCP_INFO_UNACKED_OFFSET) }
|
|
146
169
|
end
|
|
170
|
+
private_class_method :accept_queue_length
|
|
147
171
|
end
|
|
148
172
|
end
|
data/lib/raptor/server.rb
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# rbs_inline: enabled
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "openssl"
|
|
4
5
|
require "socket"
|
|
5
6
|
|
|
6
7
|
require "atomic-ruby/atomic_boolean"
|
|
7
8
|
|
|
9
|
+
require_relative "binder"
|
|
10
|
+
require_relative "log"
|
|
11
|
+
require_relative "raptor_native"
|
|
8
12
|
require_relative "reuseport_bpf"
|
|
9
13
|
|
|
10
14
|
module Raptor
|
|
11
|
-
# Accepts client connections on TCP, Unix, and
|
|
12
|
-
# dispatches them into the request pipeline.
|
|
13
|
-
# when the thread pool queue exceeds the pool size so overloaded
|
|
14
|
-
# workers stay responsive to in-flight requests.
|
|
15
|
+
# Accepts client connections on TCP, Unix, SSL, and cleartext HTTP/2
|
|
16
|
+
# listeners and dispatches them into the request pipeline.
|
|
15
17
|
#
|
|
16
18
|
class Server
|
|
17
19
|
HTTP_SCHEME = "http"
|
|
@@ -25,42 +27,44 @@ module Raptor
|
|
|
25
27
|
BACKPRESSURE_THRESHOLD_MULTIPLIER = 1.2
|
|
26
28
|
MIN_BACKPRESSURE_THRESHOLD = 8
|
|
27
29
|
|
|
28
|
-
# @rbs @binder: Binder
|
|
29
|
-
# @rbs @listeners: Array[TCPServer | UNIXServer | Binder::SslListener]
|
|
30
30
|
# @rbs @reactor: Reactor
|
|
31
31
|
# @rbs @thread_pool: AtomicThreadPool
|
|
32
32
|
# @rbs @http1: Http1
|
|
33
33
|
# @rbs @http2: Http2
|
|
34
34
|
# @rbs @first_data_timeout: Integer
|
|
35
|
+
# @rbs @listeners: Array[TCPServer | UNIXServer | Binder::SslListener | Binder::H2cListener]
|
|
35
36
|
# @rbs @drain_accept_queue: bool
|
|
36
|
-
# @rbs @
|
|
37
|
+
# @rbs @worker_index: Integer?
|
|
37
38
|
# @rbs @running: AtomicBoolean
|
|
39
|
+
# @rbs @thread: Thread?
|
|
40
|
+
# @rbs @load_reporter: Thread?
|
|
38
41
|
|
|
39
|
-
# Creates a new Server
|
|
42
|
+
# Creates a new Server.
|
|
40
43
|
#
|
|
41
|
-
# @param binder [Binder] the binder managing listening sockets
|
|
42
44
|
# @param reactor [Reactor] the reactor for handling client connections
|
|
43
45
|
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
44
46
|
# @param http1 [Http1] the HTTP/1.1 handler
|
|
45
|
-
# @param http2 [Http2] the HTTP/2 handler
|
|
47
|
+
# @param http2 [Http2] the HTTP/2 handler
|
|
46
48
|
# @param connection_options [Hash] per-connection timeout configuration, used to bound TLS handshakes
|
|
47
|
-
# @
|
|
49
|
+
# @option connection_options [Integer] :first_data_timeout timeout for completing the TLS handshake
|
|
50
|
+
# @param listeners [Array<TCPServer, UNIXServer, Binder::SslListener, Binder::H2cListener>] the per-worker listeners this server accepts on
|
|
48
51
|
# @param drain_accept_queue [Boolean] whether to drain the kernel accept queue on shutdown
|
|
49
|
-
# @param worker_index [Integer, nil]
|
|
52
|
+
# @param worker_index [Integer, nil] this worker's BPF slot index, or nil when BPF dispatch is inactive
|
|
50
53
|
# @return [void]
|
|
51
54
|
#
|
|
52
|
-
# @rbs (
|
|
53
|
-
def initialize(
|
|
54
|
-
@binder = binder
|
|
55
|
-
@listeners = listeners
|
|
55
|
+
# @rbs (Reactor reactor, AtomicThreadPool thread_pool, Http1 http1, Http2 http2, connection_options: Hash[Symbol, untyped], listeners: Array[TCPServer | UNIXServer | Binder::SslListener | Binder::H2cListener], ?drain_accept_queue: bool, ?worker_index: Integer?) -> void
|
|
56
|
+
def initialize(reactor, thread_pool, http1, http2, connection_options:, listeners:, drain_accept_queue: false, worker_index: nil)
|
|
56
57
|
@reactor = reactor
|
|
57
58
|
@thread_pool = thread_pool
|
|
58
59
|
@http1 = http1
|
|
59
60
|
@http2 = http2
|
|
60
61
|
@first_data_timeout = connection_options[:first_data_timeout]
|
|
62
|
+
@listeners = listeners
|
|
61
63
|
@drain_accept_queue = drain_accept_queue
|
|
62
|
-
@
|
|
64
|
+
@worker_index = worker_index
|
|
63
65
|
@running = AtomicBoolean.new(true)
|
|
66
|
+
@thread = nil
|
|
67
|
+
@load_reporter = nil
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
# Starts the server's main accept loop in a new thread.
|
|
@@ -69,30 +73,36 @@ module Raptor
|
|
|
69
73
|
#
|
|
70
74
|
# @rbs () -> Thread
|
|
71
75
|
def run
|
|
72
|
-
spawn_load_reporter if @
|
|
76
|
+
@load_reporter = spawn_load_reporter if @worker_index
|
|
73
77
|
|
|
74
|
-
Thread.new do
|
|
78
|
+
@thread = Thread.new do
|
|
75
79
|
Thread.current.name = "Server"
|
|
76
80
|
|
|
77
|
-
backpressure_threshold = [(@thread_pool.size * BACKPRESSURE_THRESHOLD_MULTIPLIER).ceil, MIN_BACKPRESSURE_THRESHOLD].max
|
|
78
|
-
|
|
79
81
|
while @running.true?
|
|
80
82
|
begin
|
|
81
|
-
ready_servers,
|
|
83
|
+
ready_servers, = IO.select(@listeners, nil, nil, 1)
|
|
82
84
|
rescue IOError, Errno::EBADF
|
|
83
85
|
break
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
next unless ready_servers
|
|
87
|
-
next if @reactor.backlog >= backpressure_threshold
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
backpressure_threshold = [(@thread_pool.size * BACKPRESSURE_THRESHOLD_MULTIPLIER).ceil, MIN_BACKPRESSURE_THRESHOLD].max
|
|
91
|
+
if @reactor.backlog >= backpressure_threshold || @thread_pool.queue_size > @thread_pool.size
|
|
92
|
+
CPU.yield
|
|
91
93
|
next
|
|
92
94
|
end
|
|
93
95
|
|
|
96
|
+
break if @running.false?
|
|
97
|
+
|
|
94
98
|
ready_servers.each { |listener| accept_connection(listener) }
|
|
95
99
|
end
|
|
100
|
+
|
|
101
|
+
if @worker_index
|
|
102
|
+
# Stop load updates before parking the slot so the sentinel survives.
|
|
103
|
+
@load_reporter.join
|
|
104
|
+
ReuseportBPF.mark_unavailable(@worker_index)
|
|
105
|
+
end
|
|
96
106
|
end
|
|
97
107
|
end
|
|
98
108
|
|
|
@@ -105,6 +115,7 @@ module Raptor
|
|
|
105
115
|
# @rbs () -> void
|
|
106
116
|
def stop_accepting
|
|
107
117
|
@running.make_false
|
|
118
|
+
@thread&.wakeup rescue nil
|
|
108
119
|
drain_accept_queue if @drain_accept_queue
|
|
109
120
|
end
|
|
110
121
|
|
|
@@ -121,12 +132,12 @@ module Raptor
|
|
|
121
132
|
|
|
122
133
|
private
|
|
123
134
|
|
|
124
|
-
# Starts a background thread that publishes this worker's
|
|
125
|
-
#
|
|
135
|
+
# Starts a background thread that publishes this worker's load to the
|
|
136
|
+
# BPF map for load-aware dispatch.
|
|
126
137
|
#
|
|
127
|
-
# @return [
|
|
138
|
+
# @return [Thread] the load reporter
|
|
128
139
|
#
|
|
129
|
-
# @rbs () ->
|
|
140
|
+
# @rbs () -> Thread
|
|
130
141
|
def spawn_load_reporter
|
|
131
142
|
Thread.new do
|
|
132
143
|
Thread.current.name = "Load Reporter"
|
|
@@ -154,12 +165,12 @@ module Raptor
|
|
|
154
165
|
|
|
155
166
|
# Accepts a connection from the given listener, immediately publishes
|
|
156
167
|
# the pending work to the BPF map, and dispatches the socket into the
|
|
157
|
-
# HTTP/
|
|
168
|
+
# SSL, cleartext HTTP/2, or HTTP/1.1 pipeline.
|
|
158
169
|
#
|
|
159
|
-
# @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
|
|
170
|
+
# @param listener [TCPServer, UNIXServer, Binder::SslListener, Binder::H2cListener] the ready listener
|
|
160
171
|
# @return [Boolean] true if a connection was accepted, false if the listener had nothing to dispatch
|
|
161
172
|
#
|
|
162
|
-
# @rbs (TCPServer | UNIXServer | Binder::SslListener listener) -> bool
|
|
173
|
+
# @rbs (TCPServer | UNIXServer | Binder::SslListener | Binder::H2cListener listener) -> bool
|
|
163
174
|
def accept_connection(listener)
|
|
164
175
|
tcp_client = begin
|
|
165
176
|
listener.is_a?(Binder::SslListener) ? listener.tcp_server.accept_nonblock : listener.accept_nonblock
|
|
@@ -167,13 +178,14 @@ module Raptor
|
|
|
167
178
|
return false
|
|
168
179
|
end
|
|
169
180
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
remote_addr = begin
|
|
182
|
+
tcp_client.is_a?(TCPSocket) ? tcp_client.remote_address.ip_address : DEFAULT_REMOTE_ADDR
|
|
183
|
+
rescue SystemCallError
|
|
184
|
+
tcp_client.close
|
|
185
|
+
return true
|
|
174
186
|
end
|
|
175
187
|
|
|
176
|
-
ReuseportBPF.update_accepted_load(@reactor.backlog + 1) if @
|
|
188
|
+
ReuseportBPF.update_accepted_load(@reactor.backlog + 1) if @worker_index && @running.true?
|
|
177
189
|
|
|
178
190
|
if listener.is_a?(Binder::SslListener)
|
|
179
191
|
@thread_pool << proc do
|
|
@@ -211,7 +223,7 @@ module Raptor
|
|
|
211
223
|
#
|
|
212
224
|
# @param listener [Binder::SslListener] the SSL listener that accepted the connection
|
|
213
225
|
# @param tcp_client [TCPSocket] the accepted TCP socket
|
|
214
|
-
# @param remote_addr [String] the client
|
|
226
|
+
# @param remote_addr [String] the client IP address
|
|
215
227
|
# @return [void]
|
|
216
228
|
#
|
|
217
229
|
# @rbs (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr) -> void
|
|
@@ -268,6 +280,9 @@ module Raptor
|
|
|
268
280
|
Log.rescued_error(error)
|
|
269
281
|
ssl_socket.close rescue nil
|
|
270
282
|
false
|
|
283
|
+
rescue SystemCallError
|
|
284
|
+
ssl_socket.close rescue nil
|
|
285
|
+
false
|
|
271
286
|
end
|
|
272
287
|
end
|
|
273
288
|
|
data/lib/raptor/stats.rb
CHANGED
|
@@ -27,7 +27,8 @@ module Raptor
|
|
|
27
27
|
# @rbs @num_workers: Integer
|
|
28
28
|
# @rbs @mmap: untyped
|
|
29
29
|
|
|
30
|
-
#
|
|
30
|
+
# Creates a new Stats and allocates the shared mmap region for
|
|
31
|
+
# `num_workers` slots.
|
|
31
32
|
#
|
|
32
33
|
# @param num_workers [Integer] number of worker slots to allocate
|
|
33
34
|
# @return [void]
|
|
@@ -80,14 +81,25 @@ module Raptor
|
|
|
80
81
|
|
|
81
82
|
# Reads stats for a worker slot from shared memory.
|
|
82
83
|
#
|
|
83
|
-
# @param slot [Integer] slot
|
|
84
|
+
# @param slot [Integer] slot index to read from
|
|
84
85
|
# @return [Hash] stat hash with :pid, :index, :phase, :requests, :backlog, :busy_threads, :thread_capacity, :started_at, :last_checkin, and :booted
|
|
85
86
|
#
|
|
86
87
|
# @rbs (Integer slot) -> Hash[Symbol, untyped]
|
|
87
88
|
def read(slot)
|
|
88
89
|
data = @mmap[slot * SLOT_SIZE, SLOT_SIZE]
|
|
89
90
|
pid, index, phase, requests, backlog, busy_threads, thread_capacity, started_at, last_checkin, booted = data.unpack(SLOT_FORMAT)
|
|
90
|
-
{
|
|
91
|
+
{
|
|
92
|
+
pid: pid,
|
|
93
|
+
index: index,
|
|
94
|
+
phase: phase,
|
|
95
|
+
requests: requests,
|
|
96
|
+
backlog: backlog,
|
|
97
|
+
busy_threads: busy_threads,
|
|
98
|
+
thread_capacity: thread_capacity,
|
|
99
|
+
started_at: started_at,
|
|
100
|
+
last_checkin: last_checkin,
|
|
101
|
+
booted: booted == 1
|
|
102
|
+
}
|
|
91
103
|
end
|
|
92
104
|
end
|
|
93
105
|
end
|
data/lib/raptor/thread_locals.rb
CHANGED
|
@@ -15,7 +15,8 @@ module Raptor
|
|
|
15
15
|
# Returns a reusable value stored on the current thread.
|
|
16
16
|
#
|
|
17
17
|
# @param key [Symbol] thread-variable key used to cache the value
|
|
18
|
-
# @
|
|
18
|
+
# @yield when the key is unset
|
|
19
|
+
# @yieldreturn [Object] the value to store
|
|
19
20
|
# @return [Object] the existing or newly stored value
|
|
20
21
|
#
|
|
21
22
|
# @rbs (Symbol key) { () -> untyped } -> untyped
|
|
@@ -24,8 +25,8 @@ module Raptor
|
|
|
24
25
|
thread.thread_variable_get(key) || thread.thread_variable_set(key, yield)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
# Clears application
|
|
28
|
-
#
|
|
28
|
+
# Clears application thread locals while retaining Raptor's reusable
|
|
29
|
+
# per-thread values.
|
|
29
30
|
#
|
|
30
31
|
# @return [void]
|
|
31
32
|
#
|
data/lib/raptor/version.rb
CHANGED
|
@@ -11,7 +11,8 @@ module Rackup
|
|
|
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
|
|
14
|
+
# @yield before the cluster starts running
|
|
15
|
+
# @yieldparam cluster [::Raptor::Cluster] the cluster instance
|
|
15
16
|
# @return [void]
|
|
16
17
|
#
|
|
17
18
|
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
|
|
@@ -52,17 +52,17 @@ module Raptor
|
|
|
52
52
|
# Returns the array of bind URIs.
|
|
53
53
|
#
|
|
54
54
|
# @return [Array<String>]
|
|
55
|
-
attr_reader bind_uris:
|
|
55
|
+
attr_reader bind_uris: Array[String]
|
|
56
56
|
|
|
57
57
|
# Returns the kernel `listen()` queue depth for TCP/SSL listeners.
|
|
58
58
|
#
|
|
59
59
|
# @return [Integer]
|
|
60
|
-
attr_reader socket_backlog:
|
|
60
|
+
attr_reader socket_backlog: Integer
|
|
61
61
|
|
|
62
62
|
# Returns the array of listening sockets.
|
|
63
63
|
#
|
|
64
64
|
# @return [Array<TCPServer, UNIXServer, SslListener, H2cListener>]
|
|
65
|
-
attr_reader listeners:
|
|
65
|
+
attr_reader listeners: Array[TCPServer | UNIXServer | SslListener | H2cListener]
|
|
66
66
|
|
|
67
67
|
# Creates a new Binder and binds each URI. `localhost` expands to both
|
|
68
68
|
# IPv4 and IPv6 loopback; when `inherited_fds` supplies file descriptors
|
|
@@ -86,8 +86,8 @@ module Raptor
|
|
|
86
86
|
# @rbs () -> Array[String]
|
|
87
87
|
def addresses: () -> Array[String]
|
|
88
88
|
|
|
89
|
-
# Returns the port of the first TCP
|
|
90
|
-
# is configured.
|
|
89
|
+
# Returns the port of the first TCP, SSL, or cleartext HTTP/2 listener,
|
|
90
|
+
# or 0 when none is configured.
|
|
91
91
|
#
|
|
92
92
|
# @return [Integer]
|
|
93
93
|
#
|
|
@@ -136,7 +136,7 @@ module Raptor
|
|
|
136
136
|
|
|
137
137
|
# Creates fresh listeners for the given URI.
|
|
138
138
|
#
|
|
139
|
-
# @param uri [URI] the parsed bind URI
|
|
139
|
+
# @param uri [URI::Generic] the parsed bind URI
|
|
140
140
|
# @return [Array<TCPServer, UNIXServer, SslListener, H2cListener>]
|
|
141
141
|
# @raise [UnknownBindSchemeError] if the URI scheme is not supported
|
|
142
142
|
#
|
|
@@ -146,7 +146,7 @@ module Raptor
|
|
|
146
146
|
# Reconstructs listeners for the given URI from inherited file
|
|
147
147
|
# descriptors.
|
|
148
148
|
#
|
|
149
|
-
# @param uri [URI] the parsed bind URI the FDs were bound to
|
|
149
|
+
# @param uri [URI::Generic] the parsed bind URI the FDs were bound to
|
|
150
150
|
# @param filenos [Array<Integer>] file descriptors to wrap
|
|
151
151
|
# @return [Array<TCPServer, UNIXServer, SslListener, H2cListener>]
|
|
152
152
|
# @raise [UnknownBindSchemeError] if the URI scheme is not supported
|
|
@@ -188,7 +188,7 @@ module Raptor
|
|
|
188
188
|
#
|
|
189
189
|
# @param host [String, nil] hostname or IP address to bind to
|
|
190
190
|
# @param port [Integer, nil] port number to bind to
|
|
191
|
-
# @param ssl_params [Hash
|
|
191
|
+
# @param ssl_params [Hash{String => String}] SSL options (`cert` and `key` file paths)
|
|
192
192
|
# @return [Array<SslListener>] array containing the created SSL listener(s)
|
|
193
193
|
#
|
|
194
194
|
# @rbs (String? host, Integer? port, Hash[String, String] ssl_params) -> Array[SslListener]
|
|
@@ -197,7 +197,7 @@ module Raptor
|
|
|
197
197
|
# Builds a frozen `OpenSSL::SSL::SSLContext` configured for HTTP/2 and
|
|
198
198
|
# HTTP/1.1 ALPN negotiation.
|
|
199
199
|
#
|
|
200
|
-
# @param ssl_params [Hash
|
|
200
|
+
# @param ssl_params [Hash{String => String}] SSL options (`cert` and `key` file paths)
|
|
201
201
|
# @return [OpenSSL::SSL::SSLContext]
|
|
202
202
|
#
|
|
203
203
|
# @rbs (Hash[String, String] ssl_params) -> OpenSSL::SSL::SSLContext
|
|
@@ -57,17 +57,17 @@ module Raptor
|
|
|
57
57
|
|
|
58
58
|
@parser: OptionParser
|
|
59
59
|
|
|
60
|
-
@options: Hash[Symbol, untyped]
|
|
61
|
-
|
|
62
60
|
@command: Symbol
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
#
|
|
62
|
+
@options: Hash[Symbol, untyped]
|
|
63
|
+
|
|
64
|
+
# Creates a new CLI from `argv`. A rackup file may be given as the
|
|
65
|
+
# first positional argument; every other value is parsed as a flag.
|
|
67
66
|
#
|
|
68
67
|
# @param argv [Array<String>] command-line arguments to parse
|
|
69
68
|
# @return [void]
|
|
70
69
|
# @raise [OptionParser::ParseError] if invalid options are provided
|
|
70
|
+
# @raise [ArgumentError] if the config file or max threads value is invalid
|
|
71
71
|
#
|
|
72
72
|
# @rbs (Array[String] argv) -> void
|
|
73
73
|
def initialize: (Array[String] argv) -> void
|
|
@@ -88,7 +88,7 @@ module Raptor
|
|
|
88
88
|
# @rbs () -> void
|
|
89
89
|
def run_stats: () -> void
|
|
90
90
|
|
|
91
|
-
# Returns the path from a `-c`/`--config` flag in `argv`,
|
|
91
|
+
# Returns the path from a `-c`/`--config` flag in `argv`, recognizing
|
|
92
92
|
# all four OptionParser-accepted forms (`-c PATH`, `-cPATH`,
|
|
93
93
|
# `--config PATH`, `--config=PATH`).
|
|
94
94
|
#
|
|
@@ -62,7 +62,7 @@ module Raptor
|
|
|
62
62
|
|
|
63
63
|
@launch_argv: Array[String]?
|
|
64
64
|
|
|
65
|
-
@on_error: ^(Hash[String, untyped]?, Exception) -> void
|
|
65
|
+
@on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
66
66
|
|
|
67
67
|
@binder: Binder
|
|
68
68
|
|
|
@@ -104,7 +104,7 @@ module Raptor
|
|
|
104
104
|
|
|
105
105
|
@seed_pid: Integer?
|
|
106
106
|
|
|
107
|
-
@
|
|
107
|
+
@refork_threshold_index: Integer
|
|
108
108
|
|
|
109
109
|
@refork_requested: bool
|
|
110
110
|
|
|
@@ -210,19 +210,19 @@ module Raptor
|
|
|
210
210
|
# @rbs () -> Array[Hash[Symbol, untyped]]
|
|
211
211
|
def stats: () -> Array[Hash[Symbol, untyped]]
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
#
|
|
213
|
+
private
|
|
214
|
+
|
|
215
|
+
# Returns cluster statistics for the control server. For an adaptive
|
|
216
|
+
# pool, `max_threads` is the number of threads currently running so
|
|
217
|
+
# utilization reflects the capacity available at that moment.
|
|
216
218
|
#
|
|
217
219
|
# @return [Hash] cluster statistics
|
|
218
220
|
#
|
|
219
221
|
# @rbs () -> Hash[Symbol, untyped]
|
|
220
222
|
def stats_hash: () -> Hash[Symbol, untyped]
|
|
221
223
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# @rbs (Float timestamp) -> String
|
|
225
|
-
def timestamp: (Float timestamp) -> String
|
|
224
|
+
# @rbs (Numeric time) -> String
|
|
225
|
+
def timestamp: (Numeric time) -> String
|
|
226
226
|
|
|
227
227
|
# Returns the inherited-FDs hash for a systemd socket-activation handoff,
|
|
228
228
|
# pairing each bind URI with the FD systemd passed at the same index.
|
|
@@ -15,9 +15,10 @@ module Raptor
|
|
|
15
15
|
|
|
16
16
|
@thread: Thread?
|
|
17
17
|
|
|
18
|
-
# Creates a
|
|
18
|
+
# Creates a new ControlServer for `url` without binding it.
|
|
19
19
|
#
|
|
20
20
|
# @param url [String] `unix://` URL to listen on
|
|
21
|
+
# @yield for each stats request
|
|
21
22
|
# @yieldreturn [Hash] cluster statistics
|
|
22
23
|
# @return [void]
|
|
23
24
|
# @raise [ArgumentError] if the URL is not a Unix socket
|
|
@@ -57,6 +57,7 @@ module Raptor
|
|
|
57
57
|
#
|
|
58
58
|
# @param max_buffer_size [Integer] maximum body bytes waiting to be written
|
|
59
59
|
# @return [void]
|
|
60
|
+
# @raise [ArgumentError] if `max_buffer_size` is not positive
|
|
60
61
|
#
|
|
61
62
|
# @rbs (?max_buffer_size: Integer) -> void
|
|
62
63
|
def initialize: (?max_buffer_size: Integer) -> void
|
|
@@ -64,6 +65,7 @@ module Raptor
|
|
|
64
65
|
# Registers a callback that runs on the request thread once the server
|
|
65
66
|
# accepts the response stream.
|
|
66
67
|
#
|
|
68
|
+
# @yield once the response stream opens
|
|
67
69
|
# @yieldparam stream [DetachedBody] the opened response stream
|
|
68
70
|
# @return [DetachedBody]
|
|
69
71
|
#
|
|
@@ -72,6 +74,7 @@ module Raptor
|
|
|
72
74
|
|
|
73
75
|
# Registers a callback for when the response stream closes.
|
|
74
76
|
#
|
|
77
|
+
# @yield once the response stream closes
|
|
75
78
|
# @yieldparam reason [Symbol] why the stream closed
|
|
76
79
|
# @return [DetachedBody]
|
|
77
80
|
#
|
|
@@ -82,11 +85,12 @@ module Raptor
|
|
|
82
85
|
#
|
|
83
86
|
# @param chunk [String] response body bytes
|
|
84
87
|
# @return [Symbol] `:accepted`, `:full`, or `:closed`
|
|
88
|
+
# @raise [TypeError] if the chunk is not a String
|
|
85
89
|
#
|
|
86
90
|
# @rbs (String chunk) -> Symbol
|
|
87
91
|
def try_write: (String chunk) -> Symbol
|
|
88
92
|
|
|
89
|
-
#
|
|
93
|
+
# Marks the body complete; the stream finishes once buffered bytes are written.
|
|
90
94
|
#
|
|
91
95
|
# @param trailers [Hash] trailing response headers
|
|
92
96
|
# @return [void]
|