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.
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Raptor
5
+ VERSION = "0.1.0"
6
+ 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