raptor 0.1.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 +7 -0
- data/.buildkite/pipeline.yml +36 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +28 -0
- data/exe/raptor +8 -0
- data/ext/raptor_http/extconf.rb +7 -0
- data/ext/raptor_http/raptor_http.c +1248 -0
- data/ext/raptor_http2/extconf.rb +7 -0
- data/ext/raptor_http2/huffman_table.h +4888 -0
- data/ext/raptor_http2/raptor_http2.c +772 -0
- data/lib/raptor/binder.rb +249 -0
- data/lib/raptor/cli.rb +171 -0
- data/lib/raptor/cluster.rb +357 -0
- data/lib/raptor/http2.rb +416 -0
- data/lib/raptor/reactor.rb +411 -0
- data/lib/raptor/request.rb +992 -0
- data/lib/raptor/server.rb +167 -0
- data/lib/raptor/stats.rb +94 -0
- data/lib/raptor/version.rb +6 -0
- data/lib/raptor.rb +13 -0
- data/sig/generated/raptor/binder.rbs +162 -0
- data/sig/generated/raptor/cli.rbs +71 -0
- data/sig/generated/raptor/cluster.rbs +171 -0
- data/sig/generated/raptor/http2.rbs +145 -0
- data/sig/generated/raptor/reactor.rbs +251 -0
- data/sig/generated/raptor/request.rbs +477 -0
- data/sig/generated/raptor/server.rbs +88 -0
- data/sig/generated/raptor/stats.rbs +78 -0
- data/sig/generated/raptor/version.rbs +5 -0
- data/sig/generated/raptor.rbs +9 -0
- metadata +160 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
require "atomic-ruby/atomic_boolean"
|
|
7
|
+
|
|
8
|
+
module Raptor
|
|
9
|
+
# High-performance HTTP server that accepts connections and dispatches them.
|
|
10
|
+
#
|
|
11
|
+
# Server manages the main accept loop, handling incoming client connections from
|
|
12
|
+
# bound sockets. It uses IO.select for efficient polling and implements automatic
|
|
13
|
+
# load balancing by checking reactor backlog before accepting connections,
|
|
14
|
+
# providing natural backpressure based on system capacity.
|
|
15
|
+
#
|
|
16
|
+
# Supports TCP, Unix domain, and SSL listeners transparently. TCP_NODELAY is
|
|
17
|
+
# applied only to TCP sockets, and SSL handshakes are performed synchronously
|
|
18
|
+
# before handing the connection to the reactor.
|
|
19
|
+
#
|
|
20
|
+
# For SSL connections, ALPN negotiation determines the protocol. HTTP/2
|
|
21
|
+
# connections are added to the reactor with initial SETTINGS and processed
|
|
22
|
+
# through the same ractor pool pipeline as HTTP/1.1 connections.
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# binder = Binder.new(["tcp://0.0.0.0:3000"])
|
|
26
|
+
# reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
|
|
27
|
+
# server = Server.new(binder, reactor, thread_pool)
|
|
28
|
+
# server.run
|
|
29
|
+
# # ... later
|
|
30
|
+
# server.shutdown
|
|
31
|
+
#
|
|
32
|
+
class Server
|
|
33
|
+
HTTP_SCHEME = "http"
|
|
34
|
+
HTTPS_SCHEME = "https"
|
|
35
|
+
H2_PROTOCOL = "h2"
|
|
36
|
+
|
|
37
|
+
# @rbs @binder: Binder
|
|
38
|
+
# @rbs @reactor: Reactor
|
|
39
|
+
# @rbs @thread_pool: AtomicThreadPool
|
|
40
|
+
# @rbs @running: AtomicBoolean
|
|
41
|
+
|
|
42
|
+
# Creates a new Server instance.
|
|
43
|
+
#
|
|
44
|
+
# @param binder [Binder] the binder managing listening sockets
|
|
45
|
+
# @param reactor [Reactor] the reactor for handling client connections
|
|
46
|
+
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
47
|
+
# @return [void]
|
|
48
|
+
#
|
|
49
|
+
# @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool) -> void
|
|
50
|
+
def initialize(binder, reactor, thread_pool)
|
|
51
|
+
@binder = binder
|
|
52
|
+
@reactor = reactor
|
|
53
|
+
@thread_pool = thread_pool
|
|
54
|
+
@running = AtomicBoolean.new(true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Starts the server's main accept loop in a new thread.
|
|
58
|
+
#
|
|
59
|
+
# The accept loop polls listening sockets for ready connections and accepts
|
|
60
|
+
# them when system capacity allows. It checks reactor backlog before accepting
|
|
61
|
+
# to prevent overload. This provides natural load balancing across multiple
|
|
62
|
+
# worker processes through backpressure control.
|
|
63
|
+
#
|
|
64
|
+
# @return [Thread] the thread running the accept loop
|
|
65
|
+
#
|
|
66
|
+
# @rbs () -> Thread
|
|
67
|
+
def run
|
|
68
|
+
Thread.new(@binder.listeners, @reactor, @running) do |server_sockets, reactor, running|
|
|
69
|
+
Thread.current.name = self.class.name
|
|
70
|
+
|
|
71
|
+
while running.true?
|
|
72
|
+
begin
|
|
73
|
+
ready_servers, _, _ = IO.select(server_sockets, nil, nil, 1)
|
|
74
|
+
rescue IOError, Errno::EBADF
|
|
75
|
+
break
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
next unless ready_servers
|
|
79
|
+
next if @reactor.backlog >= (@thread_pool.size * 1.2).ceil
|
|
80
|
+
|
|
81
|
+
ready_servers.each do |listener|
|
|
82
|
+
accept_connection(listener, reactor)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Gracefully shuts down the server.
|
|
89
|
+
#
|
|
90
|
+
# Stops accepting new connections and closes all listening sockets.
|
|
91
|
+
# The server thread will exit after handling any in-flight accept operations.
|
|
92
|
+
#
|
|
93
|
+
# @return [void]
|
|
94
|
+
#
|
|
95
|
+
# @rbs () -> void
|
|
96
|
+
def shutdown
|
|
97
|
+
@running.make_false
|
|
98
|
+
@binder.close
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Accepts a connection from the given listener and dispatches it.
|
|
104
|
+
#
|
|
105
|
+
# For SSL connections with h2 negotiated via ALPN, the server sends
|
|
106
|
+
# initial SETTINGS and adds the connection to the reactor as an HTTP/2
|
|
107
|
+
# connection. All other connections follow the HTTP/1.1 path.
|
|
108
|
+
#
|
|
109
|
+
# @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
|
|
110
|
+
# @param reactor [Reactor] the reactor to dispatch connections to
|
|
111
|
+
# @return [void]
|
|
112
|
+
#
|
|
113
|
+
# @rbs (TCPServer | UNIXServer | Binder::SslListener listener, Reactor reactor) -> void
|
|
114
|
+
def accept_connection(listener, reactor)
|
|
115
|
+
tcp_client = begin
|
|
116
|
+
listener.is_a?(Binder::SslListener) ? listener.tcp_server.accept_nonblock : listener.accept_nonblock
|
|
117
|
+
rescue IO::WaitReadable
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if tcp_client.is_a?(TCPSocket)
|
|
122
|
+
tcp_client.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
123
|
+
remote_addr = tcp_client.remote_address.ip_address
|
|
124
|
+
else
|
|
125
|
+
remote_addr = "127.0.0.1"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
url_scheme = HTTP_SCHEME
|
|
129
|
+
client = tcp_client
|
|
130
|
+
|
|
131
|
+
if listener.is_a?(Binder::SslListener)
|
|
132
|
+
url_scheme = HTTPS_SCHEME
|
|
133
|
+
begin
|
|
134
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, listener.ssl_context)
|
|
135
|
+
ssl_socket.sync_close = true
|
|
136
|
+
ssl_socket.accept
|
|
137
|
+
client = ssl_socket
|
|
138
|
+
rescue OpenSSL::SSL::SSLError => error
|
|
139
|
+
warn "SSL handshake failed: #{error.message}"
|
|
140
|
+
tcp_client.close rescue nil
|
|
141
|
+
return
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if ssl_socket.alpn_protocol == H2_PROTOCOL
|
|
145
|
+
ssl_socket.write(Http2.build_server_settings_frame) rescue nil
|
|
146
|
+
|
|
147
|
+
reactor.add(
|
|
148
|
+
id: ssl_socket.object_id,
|
|
149
|
+
socket: ssl_socket,
|
|
150
|
+
remote_addr: remote_addr,
|
|
151
|
+
url_scheme: HTTPS_SCHEME,
|
|
152
|
+
protocol: :http2
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
reactor.add(
|
|
160
|
+
id: client.object_id,
|
|
161
|
+
socket: client,
|
|
162
|
+
remote_addr: remote_addr,
|
|
163
|
+
url_scheme: url_scheme
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
data/lib/raptor/stats.rb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "mmap-ruby"
|
|
5
|
+
|
|
6
|
+
module Raptor
|
|
7
|
+
# Shared memory store for per-worker process statistics.
|
|
8
|
+
#
|
|
9
|
+
# Stats uses an anonymous mmap (MAP_ANON | MAP_SHARED) created before
|
|
10
|
+
# forking so that worker processes can write their stats and the master
|
|
11
|
+
# process can read them without any pipes or signals. Each worker is
|
|
12
|
+
# assigned a fixed-size slot in the shared region.
|
|
13
|
+
#
|
|
14
|
+
# Binary layout per slot (native byte order):
|
|
15
|
+
# pid uint32 4 bytes
|
|
16
|
+
# requests uint64 8 bytes
|
|
17
|
+
# backlog uint32 4 bytes
|
|
18
|
+
# started_at float64 8 bytes
|
|
19
|
+
# last_checkin float64 8 bytes
|
|
20
|
+
# booted uint8 1 byte
|
|
21
|
+
# 33 bytes total
|
|
22
|
+
#
|
|
23
|
+
class Stats
|
|
24
|
+
SLOT_FORMAT = "LQLddC"
|
|
25
|
+
SLOT_SIZE = [0, 0, 0, 0.0, 0.0, 0].pack(SLOT_FORMAT).bytesize
|
|
26
|
+
|
|
27
|
+
# @rbs @num_workers: Integer
|
|
28
|
+
# @rbs @mmap: untyped
|
|
29
|
+
|
|
30
|
+
# Creates a new Stats instance backed by anonymous shared memory.
|
|
31
|
+
#
|
|
32
|
+
# Allocates a MAP_ANON | MAP_SHARED mmap region large enough for
|
|
33
|
+
# num_workers slots. Must be called before forking so that all
|
|
34
|
+
# worker processes share the same backing memory.
|
|
35
|
+
#
|
|
36
|
+
# @param num_workers [Integer] number of worker slots to allocate
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
39
|
+
# @rbs (Integer num_workers) -> void
|
|
40
|
+
def initialize(num_workers)
|
|
41
|
+
@num_workers = num_workers
|
|
42
|
+
@mmap = Mmap.new(nil, length: num_workers * SLOT_SIZE, initialize: "\0")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Writes stats for a worker slot into shared memory.
|
|
46
|
+
#
|
|
47
|
+
# @param index [Integer] slot index to write into
|
|
48
|
+
# @param pid [Integer] worker process ID
|
|
49
|
+
# @param requests [Integer] total requests handled by this worker
|
|
50
|
+
# @param backlog [Integer] current queue depth
|
|
51
|
+
# @param started_at [Float] process start time as a Unix timestamp
|
|
52
|
+
# @param last_checkin [Float] time of last stats write as a Unix timestamp
|
|
53
|
+
# @param booted [Boolean] whether the worker has finished starting
|
|
54
|
+
# @return [void]
|
|
55
|
+
#
|
|
56
|
+
# @rbs (Integer index, pid: Integer, requests: Integer, backlog: Integer, started_at: Float, last_checkin: Float, booted: bool) -> void
|
|
57
|
+
def write(index, pid:, requests:, backlog:, started_at:, last_checkin:, booted:)
|
|
58
|
+
data = [pid, requests, backlog, started_at, last_checkin, booted ? 1 : 0].pack(SLOT_FORMAT)
|
|
59
|
+
@mmap.semlock { @mmap[index * SLOT_SIZE, SLOT_SIZE] = data }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns stats for all worker slots.
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<Hash>] per-worker stat hashes with :pid, :requests, :backlog, :started_at, :last_checkin, and :booted
|
|
65
|
+
#
|
|
66
|
+
# @rbs () -> Array[Hash[Symbol, untyped]]
|
|
67
|
+
def all
|
|
68
|
+
(0...@num_workers).map { |index| read(index) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Releases the shared memory mapping.
|
|
72
|
+
#
|
|
73
|
+
# @return [void]
|
|
74
|
+
#
|
|
75
|
+
# @rbs () -> void
|
|
76
|
+
def unmap
|
|
77
|
+
@mmap.unmap
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Reads stats for a worker slot from shared memory.
|
|
83
|
+
#
|
|
84
|
+
# @param index [Integer] slot index to read from
|
|
85
|
+
# @return [Hash] stat hash with :pid, :requests, :backlog, :started_at, :last_checkin, and :booted
|
|
86
|
+
#
|
|
87
|
+
# @rbs (Integer index) -> Hash[Symbol, untyped]
|
|
88
|
+
def read(index)
|
|
89
|
+
data = @mmap[index * SLOT_SIZE, SLOT_SIZE]
|
|
90
|
+
pid, requests, backlog, started_at, last_checkin, booted = data.unpack(SLOT_FORMAT)
|
|
91
|
+
{ pid:, requests:, backlog:, started_at:, last_checkin:, booted: booted == 1 }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/raptor.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "raptor/version"
|
|
5
|
+
|
|
6
|
+
# Main module for the Raptor web server.
|
|
7
|
+
#
|
|
8
|
+
# Raptor is a high-performance, multi-threaded, multi-process Ruby web server that
|
|
9
|
+
# leverages Ractors for parallel HTTP/1.1 and HTTP/2 request processing, native C
|
|
10
|
+
# extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
|
|
11
|
+
#
|
|
12
|
+
module Raptor
|
|
13
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Generated from lib/raptor/binder.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Raptor
|
|
4
|
+
# Manages binding to network addresses and creating listening sockets.
|
|
5
|
+
#
|
|
6
|
+
# Binder handles parsing URI bind specifications, creating TCP, Unix, and SSL
|
|
7
|
+
# server sockets, and managing socket options for optimal performance. It
|
|
8
|
+
# supports binding to multiple addresses simultaneously.
|
|
9
|
+
#
|
|
10
|
+
# @example TCP binding
|
|
11
|
+
# binder = Binder.new(["tcp://0.0.0.0:3000", "tcp://[::1]:3000"])
|
|
12
|
+
# puts binder.addresses #=> ["0.0.0.0:3000", "[::1]:3000"]
|
|
13
|
+
# binder.close
|
|
14
|
+
#
|
|
15
|
+
# @example Unix socket binding
|
|
16
|
+
# binder = Binder.new(["unix:///tmp/raptor.sock"])
|
|
17
|
+
# puts binder.addresses #=> ["/tmp/raptor.sock"]
|
|
18
|
+
# binder.close
|
|
19
|
+
#
|
|
20
|
+
# @example SSL binding
|
|
21
|
+
# binder = Binder.new(["ssl://0.0.0.0:443?cert=/path/to.crt&key=/path/to.key"])
|
|
22
|
+
# puts binder.addresses #=> ["ssl://0.0.0.0:443"]
|
|
23
|
+
# binder.close
|
|
24
|
+
#
|
|
25
|
+
# @example Localhost binding
|
|
26
|
+
# binder = Binder.new(["tcp://localhost:8080"])
|
|
27
|
+
# # Binds to both IPv4 and IPv6 loopback addresses
|
|
28
|
+
class Binder
|
|
29
|
+
SOCKET_BACKLOG: ::Integer
|
|
30
|
+
|
|
31
|
+
class UnknownBindSchemeError < TypeError
|
|
32
|
+
# @rbs (String scheme) -> void
|
|
33
|
+
def initialize: (String scheme) -> void
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Wraps a TCPServer with an SSL context for accepting SSL connections.
|
|
37
|
+
#
|
|
38
|
+
# Holds both the underlying TCP server and the SSL context together so
|
|
39
|
+
# the server thread can accept a TCP connection and then perform the SSL
|
|
40
|
+
# handshake in a single coordinated step.
|
|
41
|
+
class SslListener < Data
|
|
42
|
+
attr_reader tcp_server(): untyped
|
|
43
|
+
|
|
44
|
+
attr_reader ssl_context(): untyped
|
|
45
|
+
|
|
46
|
+
def self.new: (untyped tcp_server, untyped ssl_context) -> instance
|
|
47
|
+
| (tcp_server: untyped, ssl_context: untyped) -> instance
|
|
48
|
+
|
|
49
|
+
def self.members: () -> [ :tcp_server, :ssl_context ]
|
|
50
|
+
|
|
51
|
+
def members: () -> [ :tcp_server, :ssl_context ]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@bind_uris: Array[String]
|
|
55
|
+
|
|
56
|
+
@listeners: Array[TCPServer | UNIXServer | SslListener]
|
|
57
|
+
|
|
58
|
+
# Array of listening sockets.
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<TCPServer, UNIXServer, SslListener>] the server sockets
|
|
61
|
+
attr_reader listeners: untyped
|
|
62
|
+
|
|
63
|
+
# Creates a new Binder with the specified bind URIs.
|
|
64
|
+
#
|
|
65
|
+
# Parses the provided bind URIs and creates listening sockets for each one.
|
|
66
|
+
# Supports tcp://, unix://, and ssl:// schemes. Localhost is expanded to
|
|
67
|
+
# all available loopback addresses (both IPv4 and IPv6).
|
|
68
|
+
#
|
|
69
|
+
# @param bind_uris [Array<String>] array of URI strings to bind to
|
|
70
|
+
# @return [void]
|
|
71
|
+
# @raise [UnknownBindSchemeError] if a URI has an unsupported scheme
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# binder = Binder.new(["tcp://0.0.0.0:3000", "unix:///tmp/raptor.sock"])
|
|
75
|
+
#
|
|
76
|
+
# @rbs (Array[String] bind_uris) -> void
|
|
77
|
+
def initialize: (Array[String] bind_uris) -> void
|
|
78
|
+
|
|
79
|
+
# Returns the bound addresses as strings.
|
|
80
|
+
#
|
|
81
|
+
# TCP listeners are returned as "host:port", Unix listeners as the socket
|
|
82
|
+
# path, and SSL listeners as "ssl://host:port".
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<String>] address strings for each bound listener
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# binder.addresses #=> ["127.0.0.1:3000", "/tmp/raptor.sock", "ssl://0.0.0.0:443"]
|
|
88
|
+
#
|
|
89
|
+
# @rbs () -> Array[String]
|
|
90
|
+
def addresses: () -> Array[String]
|
|
91
|
+
|
|
92
|
+
# Returns the port number of the first TCP or SSL listener.
|
|
93
|
+
#
|
|
94
|
+
# Used to populate SERVER_PORT in the Rack environment. Returns 0
|
|
95
|
+
# if no TCP or SSL listener is configured (e.g., Unix socket only).
|
|
96
|
+
#
|
|
97
|
+
# @return [Integer] the port number, or 0 if no TCP listener exists
|
|
98
|
+
#
|
|
99
|
+
# @rbs () -> Integer
|
|
100
|
+
def server_port: () -> Integer
|
|
101
|
+
|
|
102
|
+
# Closes all listening sockets.
|
|
103
|
+
#
|
|
104
|
+
# @return [void]
|
|
105
|
+
#
|
|
106
|
+
# @rbs () -> void
|
|
107
|
+
def close: () -> void
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Parses bind URIs and creates listening sockets.
|
|
112
|
+
#
|
|
113
|
+
# @return [void]
|
|
114
|
+
# @raise [UnknownBindSchemeError] if a URI scheme is not supported
|
|
115
|
+
#
|
|
116
|
+
# @rbs () -> void
|
|
117
|
+
def parse: () -> void
|
|
118
|
+
|
|
119
|
+
# Creates TCP server sockets for the given host and port.
|
|
120
|
+
#
|
|
121
|
+
# @param host [String, nil] hostname or IP address to bind to
|
|
122
|
+
# @param port [Integer, nil] port number to bind to
|
|
123
|
+
# @return [Array<TCPServer>] array containing the created TCP server socket(s)
|
|
124
|
+
#
|
|
125
|
+
# @rbs (String? host, Integer? port) -> Array[TCPServer]
|
|
126
|
+
def create_tcp_listeners: (String? host, Integer? port) -> Array[TCPServer]
|
|
127
|
+
|
|
128
|
+
# Creates a Unix domain server socket at the given path.
|
|
129
|
+
#
|
|
130
|
+
# Removes stale socket files left by crashed processes (when the socket
|
|
131
|
+
# is not currently in use). Registers an at_exit hook to clean up the
|
|
132
|
+
# socket file on normal process exit.
|
|
133
|
+
#
|
|
134
|
+
# @param path [String] filesystem path for the Unix socket
|
|
135
|
+
# @return [Array<UNIXServer>] array containing the created Unix server socket
|
|
136
|
+
# @raise [RuntimeError] if the socket path is already in active use
|
|
137
|
+
#
|
|
138
|
+
# @rbs (String path) -> Array[UNIXServer]
|
|
139
|
+
def create_unix_listeners: (String path) -> Array[UNIXServer]
|
|
140
|
+
|
|
141
|
+
# Creates SSL server sockets for the given host, port, and SSL parameters.
|
|
142
|
+
#
|
|
143
|
+
# Wraps each TCP listener with an SSL context to produce SslListener objects.
|
|
144
|
+
# The ssl_params hash must include "cert" and "key" entries pointing to the
|
|
145
|
+
# certificate and private key files respectively.
|
|
146
|
+
#
|
|
147
|
+
# @param host [String, nil] hostname or IP address to bind to
|
|
148
|
+
# @param port [Integer, nil] port number to bind to
|
|
149
|
+
# @param ssl_params [Hash<String, String>] SSL options ("cert" and "key" paths)
|
|
150
|
+
# @return [Array<SslListener>] array containing the created SSL listener(s)
|
|
151
|
+
#
|
|
152
|
+
# @rbs (String? host, Integer? port, Hash[String, String] ssl_params) -> Array[SslListener]
|
|
153
|
+
def create_ssl_listeners: (String? host, Integer? port, Hash[String, String] ssl_params) -> Array[SslListener]
|
|
154
|
+
|
|
155
|
+
# Returns all available loopback IP addresses.
|
|
156
|
+
#
|
|
157
|
+
# @return [Array<String>] unique loopback addresses (IPv4 and IPv6)
|
|
158
|
+
#
|
|
159
|
+
# @rbs () -> Array[String]
|
|
160
|
+
def loopback_addresses: () -> Array[String]
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Generated from lib/raptor/cli.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Raptor
|
|
4
|
+
# Command-line interface for the Raptor web server.
|
|
5
|
+
#
|
|
6
|
+
# CLI parses command-line arguments and starts the server cluster with the
|
|
7
|
+
# specified configuration options. It supports configuring the number of
|
|
8
|
+
# workers, threads, ractors, bind addresses, and various client timeout
|
|
9
|
+
# settings.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# cli = Raptor::CLI.new(["config.ru", "-t", "8", "-w", "4"])
|
|
13
|
+
# cli.run
|
|
14
|
+
#
|
|
15
|
+
# @example With custom timeouts
|
|
16
|
+
# cli = Raptor::CLI.new(["--first-data-timeout", "60", "--threads", "8"])
|
|
17
|
+
# cli.run
|
|
18
|
+
class CLI
|
|
19
|
+
DEFAULT_WORKER_COUNT: untyped
|
|
20
|
+
|
|
21
|
+
DEFAULT_OPTIONS: untyped
|
|
22
|
+
|
|
23
|
+
@command: Symbol
|
|
24
|
+
|
|
25
|
+
@options: Hash[Symbol, untyped]
|
|
26
|
+
|
|
27
|
+
@parser: OptionParser
|
|
28
|
+
|
|
29
|
+
# Creates a new CLI instance and parses command-line arguments.
|
|
30
|
+
#
|
|
31
|
+
# Parses the provided command-line arguments and configures the server
|
|
32
|
+
# options accordingly. A rackup file can be provided as the first
|
|
33
|
+
# positional argument (defaults to config.ru).
|
|
34
|
+
#
|
|
35
|
+
# @param argv [Array<String>] command-line arguments to parse
|
|
36
|
+
# @return [void]
|
|
37
|
+
# @raise [OptionParser::ParseError] if invalid options are provided
|
|
38
|
+
#
|
|
39
|
+
# @example With rackup file
|
|
40
|
+
# cli = CLI.new(["my_app.ru", "-w", "4"])
|
|
41
|
+
#
|
|
42
|
+
# @example With options only
|
|
43
|
+
# cli = CLI.new(["-t", "8", "-r", "2"])
|
|
44
|
+
#
|
|
45
|
+
# @rbs (Array[String] argv) -> void
|
|
46
|
+
def initialize: (Array[String] argv) -> void
|
|
47
|
+
|
|
48
|
+
# Runs the requested command.
|
|
49
|
+
#
|
|
50
|
+
# @return [void]
|
|
51
|
+
#
|
|
52
|
+
# @rbs () -> void
|
|
53
|
+
def run: () -> void
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Reads and prints the stats file.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
#
|
|
61
|
+
# @rbs () -> void
|
|
62
|
+
def run_stats: () -> void
|
|
63
|
+
|
|
64
|
+
# Creates the OptionParser instance with all supported command-line options.
|
|
65
|
+
#
|
|
66
|
+
# @return [OptionParser] configured option parser
|
|
67
|
+
#
|
|
68
|
+
# @rbs () -> OptionParser
|
|
69
|
+
def create_parser: () -> OptionParser
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Generated from lib/raptor/cluster.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Raptor
|
|
4
|
+
# Multi-process web server cluster with advanced concurrency architecture.
|
|
5
|
+
#
|
|
6
|
+
# Cluster manages multiple worker processes, each running a complete server
|
|
7
|
+
# stack including a reactor thread, server thread, ractor pool for HTTP
|
|
8
|
+
# parsing, and thread pool for application processing. It handles process
|
|
9
|
+
# forking, signal management, graceful shutdown, and automatic worker
|
|
10
|
+
# restart when a worker process unexpectedly exits.
|
|
11
|
+
#
|
|
12
|
+
# The architecture provides horizontal scaling through processes while
|
|
13
|
+
# maintaining efficient I/O and CPU utilization within each process through
|
|
14
|
+
# the combination of NIO reactors, ractor-based parsing, and thread pools.
|
|
15
|
+
#
|
|
16
|
+
# Flow per worker process:
|
|
17
|
+
# 1. Server continuously accepts connections but skips acceptance when backlog is high
|
|
18
|
+
# 2. Reactor manages I/O multiplexing and provides backlog metrics for load control
|
|
19
|
+
# 3. Ractor pool handles CPU-intensive HTTP parsing in parallel
|
|
20
|
+
# 4. Thread pool processes Rack applications and handles response writing
|
|
21
|
+
# 5. Natural load balancing occurs through backpressure-based acceptance control
|
|
22
|
+
#
|
|
23
|
+
# @example Basic usage
|
|
24
|
+
# options = {
|
|
25
|
+
# threads: 8, ractors: 2, workers: 4,
|
|
26
|
+
# binds: ["tcp://0.0.0.0:3000"],
|
|
27
|
+
# rackup: "config.ru",
|
|
28
|
+
# client: { first_data_timeout: 30, chunk_data_timeout: 10 }
|
|
29
|
+
# }
|
|
30
|
+
# Cluster.run(options)
|
|
31
|
+
class Cluster
|
|
32
|
+
# Convenience method to create and run a cluster with the given options.
|
|
33
|
+
#
|
|
34
|
+
# @param options [Hash] cluster configuration options
|
|
35
|
+
# @return [void]
|
|
36
|
+
#
|
|
37
|
+
# @rbs (Hash[Symbol, untyped] options) -> void
|
|
38
|
+
def self.run: (Hash[Symbol, untyped] options) -> void
|
|
39
|
+
|
|
40
|
+
@stats_file: String?
|
|
41
|
+
|
|
42
|
+
@stats: Stats
|
|
43
|
+
|
|
44
|
+
@workers: Hash[Integer, Integer]
|
|
45
|
+
|
|
46
|
+
@shutdown: bool
|
|
47
|
+
|
|
48
|
+
@app: untyped
|
|
49
|
+
|
|
50
|
+
@server_port: Integer
|
|
51
|
+
|
|
52
|
+
@binder: Binder
|
|
53
|
+
|
|
54
|
+
@client_options: Hash[Symbol, Integer]
|
|
55
|
+
|
|
56
|
+
@worker_count: Integer
|
|
57
|
+
|
|
58
|
+
@ractor_count: Integer
|
|
59
|
+
|
|
60
|
+
@thread_count: Integer
|
|
61
|
+
|
|
62
|
+
# Creates a new Cluster with the specified configuration.
|
|
63
|
+
#
|
|
64
|
+
# Initializes the cluster with thread, ractor, and worker counts,
|
|
65
|
+
# sets up network binding, loads the Rack application, and prepares
|
|
66
|
+
# for multi-process operation.
|
|
67
|
+
#
|
|
68
|
+
# @param options [Hash] cluster configuration options
|
|
69
|
+
# @option options [Integer] :threads number of threads per worker process
|
|
70
|
+
# @option options [Integer] :ractors number of ractors per worker process
|
|
71
|
+
# @option options [Integer] :workers number of worker processes
|
|
72
|
+
# @option options [Array<String>] :binds array of bind URIs
|
|
73
|
+
# @option options [String] :rackup path to Rack configuration file
|
|
74
|
+
# @option options [Hash] :client client timeout configuration
|
|
75
|
+
# @return [void]
|
|
76
|
+
#
|
|
77
|
+
# @rbs (Hash[Symbol, untyped] options) -> void
|
|
78
|
+
def initialize: (Hash[Symbol, untyped] options) -> void
|
|
79
|
+
|
|
80
|
+
# Starts the multi-process cluster and manages worker processes.
|
|
81
|
+
#
|
|
82
|
+
# Forks the configured number of worker processes and monitors them,
|
|
83
|
+
# automatically restarting any that exit unexpectedly. Handles graceful
|
|
84
|
+
# shutdown via INT or TERM signals, and stats logging via USR1.
|
|
85
|
+
#
|
|
86
|
+
# Each worker process includes:
|
|
87
|
+
# - 1 server thread (continuously accepts connections with backpressure control)
|
|
88
|
+
# - 1 reactor thread (I/O multiplexing, timeout handling, backlog monitoring)
|
|
89
|
+
# - N ractor workers (parallel HTTP parsing)
|
|
90
|
+
# - 1 ractor collector thread (coordinates parsing results)
|
|
91
|
+
# - M worker threads (Rack application processing and response writing)
|
|
92
|
+
# - 1 stats thread (writes per-worker metrics to shared memory every second)
|
|
93
|
+
#
|
|
94
|
+
# @return [void]
|
|
95
|
+
#
|
|
96
|
+
# @rbs () -> void
|
|
97
|
+
def run: () -> void
|
|
98
|
+
|
|
99
|
+
# Returns stats for all worker processes.
|
|
100
|
+
#
|
|
101
|
+
# @return [Array<Hash>] array of per-worker stat hashes, each containing
|
|
102
|
+
# :pid, :requests, :backlog, :started_at, :last_checkin, and :booted
|
|
103
|
+
#
|
|
104
|
+
# @rbs () -> Array[Hash[Symbol, untyped]]
|
|
105
|
+
def stats: () -> Array[Hash[Symbol, untyped]]
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Forks a new worker process and registers it at the given index.
|
|
110
|
+
#
|
|
111
|
+
# @param index [Integer] slot index for this worker in the stats region
|
|
112
|
+
# @return [void]
|
|
113
|
+
#
|
|
114
|
+
# @rbs (Integer index) -> void
|
|
115
|
+
def spawn_worker: (Integer index) -> void
|
|
116
|
+
|
|
117
|
+
# Runs the full server stack inside a worker process.
|
|
118
|
+
#
|
|
119
|
+
# Sets up and coordinates the reactor, server, ractor pool, thread pool,
|
|
120
|
+
# and stats thread, running until a shutdown signal is received or a
|
|
121
|
+
# critical component fails.
|
|
122
|
+
#
|
|
123
|
+
# @param index [Integer] slot index for this worker in the stats region
|
|
124
|
+
# @return [void]
|
|
125
|
+
#
|
|
126
|
+
# @rbs (Integer index) -> void
|
|
127
|
+
def run_worker: (Integer index) -> void
|
|
128
|
+
|
|
129
|
+
# Returns a human-readable description of how a process exited.
|
|
130
|
+
#
|
|
131
|
+
# @param status [Process::Status] the exit status of the process
|
|
132
|
+
# @return [String] a description of the exit reason
|
|
133
|
+
#
|
|
134
|
+
# @rbs (Process::Status status) -> String
|
|
135
|
+
def exit_description: (Process::Status status) -> String
|
|
136
|
+
|
|
137
|
+
# Initiates graceful shutdown of the cluster.
|
|
138
|
+
#
|
|
139
|
+
# @return [void]
|
|
140
|
+
#
|
|
141
|
+
# @rbs () -> void
|
|
142
|
+
def shutdown: () -> void
|
|
143
|
+
|
|
144
|
+
# Logs cluster initialization details including architecture and bind addresses.
|
|
145
|
+
#
|
|
146
|
+
# Outputs a hierarchical view of the cluster configuration showing
|
|
147
|
+
# the master process, worker processes, and per-process thread/ractor
|
|
148
|
+
# allocation along with listening addresses.
|
|
149
|
+
#
|
|
150
|
+
# @return [void]
|
|
151
|
+
#
|
|
152
|
+
# @rbs () -> void
|
|
153
|
+
def log_initialization: () -> void
|
|
154
|
+
|
|
155
|
+
# Logs current stats for all workers to stdout.
|
|
156
|
+
#
|
|
157
|
+
# Triggered by SIGUSR1 in the master process.
|
|
158
|
+
#
|
|
159
|
+
# @return [void]
|
|
160
|
+
#
|
|
161
|
+
# @rbs () -> void
|
|
162
|
+
def log_stats: () -> void
|
|
163
|
+
|
|
164
|
+
# Writes the stats file on a 1-second interval until shutdown.
|
|
165
|
+
#
|
|
166
|
+
# @return [void]
|
|
167
|
+
#
|
|
168
|
+
# @rbs () -> void
|
|
169
|
+
def write_stats_file_loop: () -> void
|
|
170
|
+
end
|
|
171
|
+
end
|