raptor 0.7.0 → 0.9.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 +90 -32
- data/.dockerignore +5 -0
- data/CHANGELOG.md +30 -0
- data/Dockerfile +28 -0
- data/README.md +159 -27
- data/Rakefile +19 -1
- data/docs/raptor-vs-puma.md +557 -0
- data/ext/raptor_bpf/reuseport_select.bpf.c +63 -0
- data/ext/raptor_http/extconf.rb +1 -1
- data/ext/raptor_native/extconf.rb +7 -0
- data/ext/raptor_native/raptor_native.c +99 -0
- data/lib/rackup/handler/raptor.rb +12 -2
- data/lib/raptor/binder.rb +139 -31
- data/lib/raptor/cli.rb +83 -22
- data/lib/raptor/cluster.rb +214 -33
- data/lib/raptor/http.rb +116 -0
- data/lib/raptor/{request.rb → http1.rb} +228 -99
- data/lib/raptor/http2.rb +85 -40
- data/lib/raptor/reactor.rb +22 -15
- data/lib/raptor/reuseport_bpf.rb +108 -0
- data/lib/raptor/server.rb +92 -43
- data/lib/raptor/stats.rb +1 -1
- data/lib/raptor/systemd.rb +69 -0
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/binder.rbs +82 -5
- data/sig/generated/raptor/cli.rbs +2 -3
- data/sig/generated/raptor/cluster.rbs +91 -13
- data/sig/generated/raptor/http.rbs +65 -0
- data/sig/generated/raptor/{request.rbs → http1.rbs} +122 -35
- data/sig/generated/raptor/http2.rbs +46 -13
- data/sig/generated/raptor/reactor.rbs +18 -11
- data/sig/generated/raptor/reuseport_bpf.rbs +56 -0
- data/sig/generated/raptor/server.rbs +49 -21
- data/sig/generated/raptor/systemd.rbs +42 -0
- metadata +30 -3
data/lib/raptor/http2.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "stringio"
|
|
|
6
6
|
require "atomic-ruby/atom"
|
|
7
7
|
require "rack"
|
|
8
8
|
|
|
9
|
-
require_relative "
|
|
9
|
+
require_relative "http"
|
|
10
10
|
require_relative "raptor_http2"
|
|
11
11
|
|
|
12
12
|
module Raptor
|
|
@@ -27,12 +27,17 @@ module Raptor
|
|
|
27
27
|
IDLE = :idle
|
|
28
28
|
|
|
29
29
|
# @rbs @state: Atom
|
|
30
|
+
# @rbs @write_timeout: Integer
|
|
30
31
|
|
|
31
32
|
# Creates a new Writer.
|
|
32
33
|
#
|
|
33
|
-
# @
|
|
34
|
-
|
|
34
|
+
# @param write_timeout [Integer] per-write socket timeout passed through to {Http.socket_write}
|
|
35
|
+
# @return [void]
|
|
36
|
+
#
|
|
37
|
+
# @rbs (write_timeout: Integer) -> void
|
|
38
|
+
def initialize(write_timeout:)
|
|
35
39
|
@state = Atom.new(IDLE)
|
|
40
|
+
@write_timeout = write_timeout
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
# Writes frames to the socket, coordinating with concurrent writers
|
|
@@ -69,7 +74,7 @@ module Raptor
|
|
|
69
74
|
break if pending.empty?
|
|
70
75
|
|
|
71
76
|
pending.each do |frame|
|
|
72
|
-
|
|
77
|
+
Http.socket_write(socket, frame, timeout: @write_timeout) rescue nil
|
|
73
78
|
end
|
|
74
79
|
end
|
|
75
80
|
end
|
|
@@ -78,7 +83,7 @@ module Raptor
|
|
|
78
83
|
# Per-connection outbound flow-control accounting.
|
|
79
84
|
#
|
|
80
85
|
# Tracks the peer's connection-level and per-stream receive windows so
|
|
81
|
-
# outbound DATA frames respect RFC 7540
|
|
86
|
+
# outbound DATA frames respect RFC 7540 section 5.2. Threads dispatching stream
|
|
82
87
|
# responses call `acquire` to reserve send capacity; threads applying
|
|
83
88
|
# inbound WINDOW_UPDATE or SETTINGS frames call the mutating methods to
|
|
84
89
|
# replenish it. The connection window and per-stream windows live in
|
|
@@ -146,9 +151,9 @@ module Raptor
|
|
|
146
151
|
end
|
|
147
152
|
|
|
148
153
|
if granted > 0
|
|
149
|
-
@stream_windows.swap do |
|
|
150
|
-
current =
|
|
151
|
-
|
|
154
|
+
@stream_windows.swap do |windows|
|
|
155
|
+
current = windows[stream_id] || initial
|
|
156
|
+
windows.merge(stream_id => current - granted)
|
|
152
157
|
end
|
|
153
158
|
return granted
|
|
154
159
|
end
|
|
@@ -178,14 +183,14 @@ module Raptor
|
|
|
178
183
|
# @rbs (Integer stream_id, Integer increment) -> void
|
|
179
184
|
def add_stream_window(stream_id, increment)
|
|
180
185
|
initial = @initial_stream_window.value
|
|
181
|
-
@stream_windows.swap do |
|
|
182
|
-
current =
|
|
183
|
-
|
|
186
|
+
@stream_windows.swap do |windows|
|
|
187
|
+
current = windows[stream_id] || initial
|
|
188
|
+
windows.merge(stream_id => current + increment)
|
|
184
189
|
end
|
|
185
190
|
end
|
|
186
191
|
|
|
187
192
|
# Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
|
|
188
|
-
# existing stream window by the delta as required by RFC 7540
|
|
193
|
+
# existing stream window by the delta as required by RFC 7540 section 6.9.2.
|
|
189
194
|
#
|
|
190
195
|
# @param new_size [Integer] the peer's new initial window size
|
|
191
196
|
# @return [void]
|
|
@@ -197,8 +202,8 @@ module Raptor
|
|
|
197
202
|
delta = new_size - old
|
|
198
203
|
return if delta.zero?
|
|
199
204
|
|
|
200
|
-
@stream_windows.swap do |
|
|
201
|
-
|
|
205
|
+
@stream_windows.swap do |windows|
|
|
206
|
+
windows.transform_values { |size| size + delta }
|
|
202
207
|
end
|
|
203
208
|
end
|
|
204
209
|
|
|
@@ -213,12 +218,12 @@ module Raptor
|
|
|
213
218
|
def discard_stream(stream_id)
|
|
214
219
|
return unless @stream_windows.value.key?(stream_id)
|
|
215
220
|
|
|
216
|
-
@stream_windows.swap do |
|
|
217
|
-
next
|
|
221
|
+
@stream_windows.swap do |windows|
|
|
222
|
+
next windows unless windows.key?(stream_id)
|
|
218
223
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
pruned = windows.dup
|
|
225
|
+
pruned.delete(stream_id)
|
|
226
|
+
pruned
|
|
222
227
|
end
|
|
223
228
|
end
|
|
224
229
|
end
|
|
@@ -244,34 +249,51 @@ module Raptor
|
|
|
244
249
|
|
|
245
250
|
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
246
251
|
# @rbs @server_port: Integer
|
|
252
|
+
# @rbs @write_timeout: Integer
|
|
253
|
+
# @rbs @access_log_io: IO?
|
|
247
254
|
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
255
|
+
# @rbs @initial_settings_frame: String
|
|
256
|
+
|
|
257
|
+
# The initial server SETTINGS frame sent on every new HTTP/2 connection.
|
|
258
|
+
#
|
|
259
|
+
# @return [String] the encoded SETTINGS frame
|
|
260
|
+
attr_reader :initial_settings_frame
|
|
248
261
|
|
|
249
262
|
# Creates a new Http2 handler.
|
|
250
263
|
#
|
|
251
264
|
# @param app [#call] the Rack application to dispatch requests to
|
|
252
265
|
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
266
|
+
# @param connection_options [Hash] per-connection settings shared across protocols
|
|
267
|
+
# @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
|
|
268
|
+
# @param http2_options [Hash] HTTP/2-specific settings
|
|
269
|
+
# @option http2_options [Integer] :max_concurrent_streams maximum HTTP/2 concurrent streams per connection
|
|
270
|
+
# @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
|
|
253
271
|
# @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
|
|
254
272
|
# @return [void]
|
|
255
273
|
#
|
|
256
|
-
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
|
|
257
|
-
def initialize(app, server_port, on_error: nil)
|
|
274
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http2_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
|
|
275
|
+
def initialize(app, server_port, connection_options: {}, http2_options: {}, access_log_io: nil, on_error: nil)
|
|
258
276
|
@app = app
|
|
259
277
|
@server_port = server_port
|
|
278
|
+
@write_timeout = connection_options[:write_timeout] || Http::WRITE_TIMEOUT
|
|
279
|
+
@access_log_io = access_log_io
|
|
260
280
|
@on_error = on_error
|
|
261
|
-
end
|
|
262
281
|
|
|
263
|
-
# Builds the initial server SETTINGS frame to send on connection establishment.
|
|
264
|
-
#
|
|
265
|
-
# @return [String] the encoded SETTINGS frame
|
|
266
|
-
#
|
|
267
|
-
# @rbs () -> String
|
|
268
|
-
def self.build_server_settings_frame
|
|
269
282
|
parser = Http2Parser.new
|
|
270
283
|
settings_payload = parser.build_settings(
|
|
271
|
-
max_concurrent_streams:
|
|
284
|
+
max_concurrent_streams: http2_options[:max_concurrent_streams],
|
|
272
285
|
initial_window_size: DEFAULT_WINDOW_SIZE
|
|
273
286
|
)
|
|
274
|
-
parser.build_frame(:settings, 0, 0, settings_payload)
|
|
287
|
+
@initial_settings_frame = parser.build_frame(:settings, 0, 0, settings_payload).freeze
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Creates a per-connection {Writer} configured with the handler's write timeout.
|
|
291
|
+
#
|
|
292
|
+
# @return [Writer] a new per-connection frame writer
|
|
293
|
+
#
|
|
294
|
+
# @rbs () -> Writer
|
|
295
|
+
def create_writer
|
|
296
|
+
Writer.new(write_timeout: @write_timeout)
|
|
275
297
|
end
|
|
276
298
|
|
|
277
299
|
# Processes HTTP/2 frames from the connection buffer.
|
|
@@ -626,7 +648,8 @@ module Raptor
|
|
|
626
648
|
env = build_rack_env(headers, body, remote_addr: remote_addr)
|
|
627
649
|
status, response_headers, response_body = @app.call(env)
|
|
628
650
|
|
|
629
|
-
write_http2_response(socket, writer, flow_control, stream_id, status, response_headers, response_body)
|
|
651
|
+
response_size = write_http2_response(socket, writer, flow_control, stream_id, status, response_headers, response_body)
|
|
652
|
+
write_access_log(env, status, response_size, remote_addr) if @access_log_io
|
|
630
653
|
rescue => error
|
|
631
654
|
write_http2_error_response(socket, writer, stream_id)
|
|
632
655
|
|
|
@@ -652,9 +675,9 @@ module Raptor
|
|
|
652
675
|
# @param status [Integer] HTTP status code
|
|
653
676
|
# @param headers [Hash] response headers from the Rack application
|
|
654
677
|
# @param body [Object] response body responding to each
|
|
655
|
-
# @return [
|
|
678
|
+
# @return [String] the response body size in bytes
|
|
656
679
|
#
|
|
657
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) ->
|
|
680
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> String
|
|
658
681
|
def write_http2_response(socket, writer, flow_control, stream_id, status, headers, body)
|
|
659
682
|
parser = Http2Parser.new
|
|
660
683
|
|
|
@@ -673,11 +696,16 @@ module Raptor
|
|
|
673
696
|
|
|
674
697
|
encoded_headers = parser.encode_headers(header_pairs)
|
|
675
698
|
body_chunks = []
|
|
676
|
-
|
|
699
|
+
body_bytes = 0
|
|
700
|
+
body.each do |chunk|
|
|
701
|
+
next if chunk.empty?
|
|
702
|
+
body_chunks << chunk
|
|
703
|
+
body_bytes += chunk.bytesize
|
|
704
|
+
end
|
|
677
705
|
|
|
678
706
|
if body_chunks.empty?
|
|
679
707
|
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
680
|
-
return
|
|
708
|
+
return "0"
|
|
681
709
|
end
|
|
682
710
|
|
|
683
711
|
frames = [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers)]
|
|
@@ -697,6 +725,7 @@ module Raptor
|
|
|
697
725
|
end
|
|
698
726
|
|
|
699
727
|
writer.write_frames(socket, frames)
|
|
728
|
+
body_bytes.to_s
|
|
700
729
|
end
|
|
701
730
|
|
|
702
731
|
# Writes a 500 error response as HTTP/2 frames.
|
|
@@ -717,6 +746,20 @@ module Raptor
|
|
|
717
746
|
)
|
|
718
747
|
end
|
|
719
748
|
|
|
749
|
+
# Instance-level wrapper around {Http.write_access_log} that routes to
|
|
750
|
+
# the configured `@access_log_io`.
|
|
751
|
+
#
|
|
752
|
+
# @param env [Hash] the Rack environment
|
|
753
|
+
# @param status [Integer] the response status code
|
|
754
|
+
# @param size [String] the response body size in bytes, or `-` if unknown
|
|
755
|
+
# @param remote_addr [String] the client IP address
|
|
756
|
+
# @return [void]
|
|
757
|
+
#
|
|
758
|
+
# @rbs (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
|
|
759
|
+
def write_access_log(env, status, size, remote_addr)
|
|
760
|
+
Http.write_access_log(@access_log_io, env, status, size, remote_addr)
|
|
761
|
+
end
|
|
762
|
+
|
|
720
763
|
# Builds a Rack environment hash from HTTP/2 headers and body.
|
|
721
764
|
#
|
|
722
765
|
# Translates HTTP/2 pseudo-headers into Rack-compatible environment keys
|
|
@@ -743,9 +786,9 @@ module Raptor
|
|
|
743
786
|
when ":authority" then env[Rack::HTTP_HOST] = value
|
|
744
787
|
end
|
|
745
788
|
elsif name == "content-type"
|
|
746
|
-
env[
|
|
789
|
+
env[Http::CONTENT_TYPE] = value
|
|
747
790
|
elsif name == "content-length"
|
|
748
|
-
env[
|
|
791
|
+
env[Http::CONTENT_LENGTH] = value
|
|
749
792
|
else
|
|
750
793
|
rack_key = "HTTP_#{name.upcase.tr("-", "_")}"
|
|
751
794
|
env[rack_key] = value
|
|
@@ -763,11 +806,13 @@ module Raptor
|
|
|
763
806
|
env[Rack::PATH_INFO] = "" unless env.key?(Rack::PATH_INFO)
|
|
764
807
|
env[Rack::QUERY_STRING] = "" unless env.key?(Rack::QUERY_STRING)
|
|
765
808
|
|
|
766
|
-
if body.bytesize.positive? && !env.key?(
|
|
767
|
-
env[
|
|
809
|
+
if body.bytesize.positive? && !env.key?(Http::CONTENT_LENGTH)
|
|
810
|
+
env[Http::CONTENT_LENGTH] = body.bytesize.to_s
|
|
768
811
|
end
|
|
769
812
|
|
|
770
|
-
env[
|
|
813
|
+
env[Http::REMOTE_ADDR] = remote_addr
|
|
814
|
+
env[Http::SERVER_SOFTWARE] = Http::SERVER_SOFTWARE_VALUE
|
|
815
|
+
env[Http::HTTP_VERSION] = SERVER_PROTOCOL
|
|
771
816
|
|
|
772
817
|
populate_server_name_and_port(env)
|
|
773
818
|
|
data/lib/raptor/reactor.rb
CHANGED
|
@@ -14,10 +14,12 @@ module Raptor
|
|
|
14
14
|
# the server uses for backpressure control to prevent overload.
|
|
15
15
|
#
|
|
16
16
|
# @example
|
|
17
|
-
# reactor = Reactor.new(
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
17
|
+
# reactor = Reactor.new(
|
|
18
|
+
# ractor_pool,
|
|
19
|
+
# thread_pool,
|
|
20
|
+
# connection_options: { first_data_timeout: 30, chunk_data_timeout: 10 },
|
|
21
|
+
# http1_options: { persistent_data_timeout: 65 }
|
|
22
|
+
# )
|
|
21
23
|
# reactor.run
|
|
22
24
|
# reactor.add(id: client.object_id, socket: client)
|
|
23
25
|
# # ... later
|
|
@@ -68,7 +70,9 @@ module Raptor
|
|
|
68
70
|
|
|
69
71
|
# @rbs @thread_pool: untyped
|
|
70
72
|
# @rbs @ractor_pool: untyped
|
|
71
|
-
# @rbs @
|
|
73
|
+
# @rbs @first_data_timeout: Integer
|
|
74
|
+
# @rbs @chunk_data_timeout: Integer
|
|
75
|
+
# @rbs @persistent_data_timeout: Integer
|
|
72
76
|
# @rbs @selector: NIO::Selector
|
|
73
77
|
# @rbs @queue: Queue[TCPSocket]
|
|
74
78
|
# @rbs @timeouts: RedBlackTree[TimeoutClient]
|
|
@@ -82,17 +86,20 @@ module Raptor
|
|
|
82
86
|
#
|
|
83
87
|
# @param ractor_pool [RactorPool] ractor pool for HTTP parsing
|
|
84
88
|
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
85
|
-
# @param
|
|
86
|
-
# @option
|
|
87
|
-
# @option
|
|
88
|
-
# @
|
|
89
|
+
# @param connection_options [Hash] per-connection timeout configuration
|
|
90
|
+
# @option connection_options [Integer] :first_data_timeout timeout for initial data
|
|
91
|
+
# @option connection_options [Integer] :chunk_data_timeout timeout for subsequent chunks
|
|
92
|
+
# @param http1_options [Hash] HTTP/1.1-specific configuration
|
|
93
|
+
# @option http1_options [Integer] :persistent_data_timeout timeout for keep-alive idle connections
|
|
89
94
|
# @return [void]
|
|
90
95
|
#
|
|
91
|
-
# @rbs (untyped ractor_pool, untyped thread_pool,
|
|
92
|
-
def initialize(ractor_pool, thread_pool,
|
|
96
|
+
# @rbs (untyped ractor_pool, untyped thread_pool, connection_options: Hash[Symbol, untyped], http1_options: Hash[Symbol, untyped]) -> void
|
|
97
|
+
def initialize(ractor_pool, thread_pool, connection_options:, http1_options:)
|
|
93
98
|
@ractor_pool = ractor_pool
|
|
94
99
|
@thread_pool = thread_pool
|
|
95
|
-
@
|
|
100
|
+
@first_data_timeout = connection_options[:first_data_timeout]
|
|
101
|
+
@chunk_data_timeout = connection_options[:chunk_data_timeout]
|
|
102
|
+
@persistent_data_timeout = http1_options[:persistent_data_timeout]
|
|
96
103
|
|
|
97
104
|
@selector = NIO::Selector.new
|
|
98
105
|
@queue = Queue.new
|
|
@@ -354,11 +361,11 @@ module Raptor
|
|
|
354
361
|
state = @socket_to_state[socket]
|
|
355
362
|
client = TimeoutClient.new(state)
|
|
356
363
|
timeout = if state[:persisted]
|
|
357
|
-
@
|
|
364
|
+
@persistent_data_timeout
|
|
358
365
|
elsif first_data_received?(state)
|
|
359
|
-
@
|
|
366
|
+
@chunk_data_timeout
|
|
360
367
|
else
|
|
361
|
-
@
|
|
368
|
+
@first_data_timeout
|
|
362
369
|
end
|
|
363
370
|
client.timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
364
371
|
@timeouts << client
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "socket"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "libbpf-ruby"
|
|
9
|
+
rescue LoadError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Raptor
|
|
13
|
+
# Routes incoming connections across worker processes to the worker with
|
|
14
|
+
# the lowest reactor backlog.
|
|
15
|
+
#
|
|
16
|
+
# Auto-enabled on Linux when the `libbpf-ruby` gem is installed and the
|
|
17
|
+
# BPF object file has been compiled. Falls back silently to standard
|
|
18
|
+
# `SO_REUSEPORT` when either is missing. Raises when the kernel refuses
|
|
19
|
+
# the BPF program. Wraps `tcp://` bindings only; non-tcp bindings are
|
|
20
|
+
# left to the binder's shared listeners.
|
|
21
|
+
#
|
|
22
|
+
module ReuseportBPF
|
|
23
|
+
LIBBPF_LOADED = !!defined?(LibBPFRuby)
|
|
24
|
+
BPF_OBJECT_PATH = File.expand_path("../../ext/raptor_bpf/reuseport_select.bpf.o", __dir__)
|
|
25
|
+
|
|
26
|
+
# Whether the preconditions for BPF-directed reuseport are met on this
|
|
27
|
+
# platform. Does not imply that the kernel will accept the program.
|
|
28
|
+
#
|
|
29
|
+
# @return [Boolean]
|
|
30
|
+
#
|
|
31
|
+
# @rbs () -> bool
|
|
32
|
+
def self.supported?
|
|
33
|
+
LIBBPF_LOADED && File.exist?(BPF_OBJECT_PATH)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Prepares BPF-directed routing for `worker_count` workers.
|
|
37
|
+
#
|
|
38
|
+
# Returns true when BPF-directed dispatch is active. Returns false
|
|
39
|
+
# when the platform is unsupported. Raises when the kernel refuses
|
|
40
|
+
# the program.
|
|
41
|
+
#
|
|
42
|
+
# @param worker_count [Integer] number of worker processes that will bind sockets
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
#
|
|
45
|
+
# @rbs (Integer worker_count) -> bool
|
|
46
|
+
def self.setup(worker_count)
|
|
47
|
+
return false unless supported?
|
|
48
|
+
|
|
49
|
+
@object = LibBPFRuby::Object.new(BPF_OBJECT_PATH)
|
|
50
|
+
@program_fd = @object.program_fd("select_least_loaded")
|
|
51
|
+
@socks_fd = @object.map_fd("socks")
|
|
52
|
+
@loads_fd = @object.map_fd("loads")
|
|
53
|
+
|
|
54
|
+
LibBPFRuby.map_update(@loads_fd, [0].pack("L"), [worker_count].pack("L"))
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns tcp listening sockets bound and registered for `worker_index`.
|
|
59
|
+
# Skips non-tcp bind URIs.
|
|
60
|
+
#
|
|
61
|
+
# @param bind_uris [Array<String>] the configured bind URIs
|
|
62
|
+
# @param worker_index [Integer] slot index for this worker in the socks map
|
|
63
|
+
# @param socket_backlog [Integer] kernel listen() queue depth
|
|
64
|
+
# @return [Array<TCPServer>] the tcp listening sockets created for this worker
|
|
65
|
+
#
|
|
66
|
+
# @rbs (Array[String] bind_uris, Integer worker_index, Integer socket_backlog) -> Array[TCPServer]
|
|
67
|
+
def self.create_worker_listeners(bind_uris, worker_index, socket_backlog)
|
|
68
|
+
@load_key = [worker_index + 1].pack("L")
|
|
69
|
+
@load_value = String.new("\x00\x00\x00\x00", encoding: Encoding::ASCII_8BIT)
|
|
70
|
+
|
|
71
|
+
bind_uris.filter_map do |bind_uri|
|
|
72
|
+
uri = URI(bind_uri)
|
|
73
|
+
next unless uri.scheme == "tcp"
|
|
74
|
+
|
|
75
|
+
host = uri.host
|
|
76
|
+
host = host[1..-2] if host&.start_with?("[")
|
|
77
|
+
|
|
78
|
+
addrinfo = Addrinfo.tcp(host, uri.port)
|
|
79
|
+
socket = Socket.new(addrinfo.afamily, Socket::SOCK_STREAM, 0)
|
|
80
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
81
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
|
|
82
|
+
socket.bind(addrinfo)
|
|
83
|
+
socket.listen(socket_backlog)
|
|
84
|
+
|
|
85
|
+
LibBPFRuby.sockmap_update(@socks_fd, [worker_index].pack("L"), socket)
|
|
86
|
+
LibBPFRuby.attach_reuseport(socket, @program_fd)
|
|
87
|
+
|
|
88
|
+
tcp_server = TCPServer.for_fd(socket.fileno)
|
|
89
|
+
socket.autoclose = false
|
|
90
|
+
tcp_server
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Publishes this worker's current backlog to the BPF map.
|
|
95
|
+
#
|
|
96
|
+
# @param backlog [Integer] the worker's current reactor backlog
|
|
97
|
+
# @return [void]
|
|
98
|
+
#
|
|
99
|
+
# @rbs (Integer backlog) -> void
|
|
100
|
+
def self.update_load(backlog)
|
|
101
|
+
@load_value.setbyte(0, backlog & 0xff)
|
|
102
|
+
@load_value.setbyte(1, (backlog >> 8) & 0xff)
|
|
103
|
+
@load_value.setbyte(2, (backlog >> 16) & 0xff)
|
|
104
|
+
@load_value.setbyte(3, (backlog >> 24) & 0xff)
|
|
105
|
+
LibBPFRuby.map_update(@loads_fd, @load_key, @load_value)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|