raptor 0.3.0 → 0.5.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.
data/lib/raptor/server.rb CHANGED
@@ -6,28 +6,22 @@ require "socket"
6
6
  require "atomic-ruby/atomic_boolean"
7
7
 
8
8
  module Raptor
9
- # High-performance HTTP server that accepts connections and dispatches them.
9
+ # Accepts client connections and dispatches them into the request
10
+ # pipeline. Skips acceptance when the reactor backlog is high so an
11
+ # overloaded process leaves connections for peers that can absorb
12
+ # them (via shared `SO_REUSEPORT` listeners).
10
13
  #
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 the connection is dispatched.
19
- #
20
- # For HTTP/1.1 connections the first request is parsed inline on the server
21
- # thread and dispatched directly to the thread pool, falling back to the
22
- # reactor only when more data is needed. For HTTP/2 connections (negotiated
23
- # via ALPN) the server sends initial SETTINGS and registers the connection
24
- # with the reactor for frame processing through the ractor pool.
14
+ # Supports TCP, Unix, and SSL listeners. SSL handshakes are offloaded
15
+ # to the thread pool so a slow client can't pin the server thread.
16
+ # For HTTP/1.1 the first request is parsed inline and dispatched
17
+ # straight to the thread pool; HTTP/2 (negotiated via ALPN) is
18
+ # registered with the reactor for frame processing.
25
19
  #
26
20
  # @example
27
21
  # binder = Binder.new(["tcp://0.0.0.0:3000"])
28
- # reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
22
+ # reactor = Reactor.new(ractor_pool, thread_pool, client_options: {})
29
23
  # request = Request.new(app, 3000)
30
- # server = Server.new(binder, reactor, thread_pool, request)
24
+ # server = Server.new(binder, reactor, thread_pool, request, client_options: { first_data_timeout: 30 })
31
25
  # server.run
32
26
  # # ... later
33
27
  # server.shutdown
@@ -35,12 +29,17 @@ module Raptor
35
29
  class Server
36
30
  HTTP_SCHEME = "http"
37
31
  HTTPS_SCHEME = "https"
32
+
38
33
  H2_PROTOCOL = "h2"
39
34
 
35
+ DEFAULT_REMOTE_ADDR = "127.0.0.1"
36
+ DEFAULT_SERVER_NAME = "localhost"
37
+
40
38
  # @rbs @binder: Binder
41
39
  # @rbs @reactor: Reactor
42
40
  # @rbs @thread_pool: AtomicThreadPool
43
41
  # @rbs @request: Request
42
+ # @rbs @client_options: Hash[Symbol, untyped]
44
43
  # @rbs @running: AtomicBoolean
45
44
 
46
45
  # Creates a new Server instance.
@@ -49,14 +48,16 @@ module Raptor
49
48
  # @param reactor [Reactor] the reactor for handling client connections
50
49
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
51
50
  # @param request [Request] the HTTP/1.1 request handler
51
+ # @param client_options [Hash] client timeout configuration, used to bound TLS handshakes
52
52
  # @return [void]
53
53
  #
54
- # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request) -> void
55
- def initialize(binder, reactor, thread_pool, request)
54
+ # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request, client_options: Hash[Symbol, untyped]) -> void
55
+ def initialize(binder, reactor, thread_pool, request, client_options:)
56
56
  @binder = binder
57
57
  @reactor = reactor
58
58
  @thread_pool = thread_pool
59
59
  @request = request
60
+ @client_options = client_options
60
61
  @running = AtomicBoolean.new(true)
61
62
  end
62
63
 
@@ -108,9 +109,11 @@ module Raptor
108
109
 
109
110
  # Accepts a connection from the given listener and dispatches it.
110
111
  #
111
- # For SSL connections with h2 negotiated via ALPN, the server sends
112
- # initial SETTINGS and adds the connection to the reactor as an HTTP/2
113
- # connection. All other connections follow the HTTP/1.1 path.
112
+ # For SSL listeners the TLS handshake is offloaded to the thread pool so
113
+ # a slow client cannot block the server thread. For SSL connections with
114
+ # h2 negotiated via ALPN, the server sends initial SETTINGS and adds the
115
+ # connection to the reactor as an HTTP/2 connection. All other connections
116
+ # follow the HTTP/1.1 path.
114
117
  #
115
118
  # @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
116
119
  # @param reactor [Reactor] the reactor to dispatch connections to
@@ -128,49 +131,120 @@ module Raptor
128
131
  tcp_client.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
129
132
  remote_addr = tcp_client.remote_address.ip_address
130
133
  else
131
- remote_addr = "127.0.0.1"
134
+ remote_addr = DEFAULT_REMOTE_ADDR
132
135
  end
133
136
 
134
- url_scheme = HTTP_SCHEME
135
- client = tcp_client
136
-
137
137
  if listener.is_a?(Binder::SslListener)
138
- url_scheme = HTTPS_SCHEME
139
- begin
140
- ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, listener.ssl_context)
141
- ssl_socket.sync_close = true
142
- ssl_socket.accept
143
- client = ssl_socket
144
- rescue OpenSSL::SSL::SSLError => error
145
- warn "SSL handshake failed: #{error.message}"
146
- tcp_client.close rescue nil
147
- return
138
+ @thread_pool << proc do
139
+ dispatch_ssl_connection(listener, tcp_client, remote_addr, reactor)
148
140
  end
141
+ return
142
+ end
149
143
 
150
- if ssl_socket.alpn_protocol == H2_PROTOCOL
151
- ssl_socket.write(Http2.build_server_settings_frame) rescue nil
144
+ @request.eager_accept(
145
+ tcp_client,
146
+ tcp_client.object_id,
147
+ reactor,
148
+ @thread_pool,
149
+ remote_addr,
150
+ HTTP_SCHEME
151
+ )
152
+ end
152
153
 
153
- reactor.add(
154
- id: ssl_socket.object_id,
155
- socket: ssl_socket,
156
- remote_addr: remote_addr,
157
- url_scheme: HTTPS_SCHEME,
158
- protocol: :http2,
159
- writer: Http2::Writer.new
160
- )
154
+ # Performs the TLS handshake for an accepted SSL connection and dispatches
155
+ # it through the HTTP/2 or HTTP/1.1 path. The handshake is bounded by
156
+ # `:first_data_timeout` so a slow client cannot pin a worker thread.
157
+ #
158
+ # @param listener [Binder::SslListener] the SSL listener that accepted the connection
159
+ # @param tcp_client [TCPSocket] the accepted TCP socket
160
+ # @param remote_addr [String] the client's IP address
161
+ # @param reactor [Reactor] the reactor to dispatch the connection to
162
+ # @return [void]
163
+ #
164
+ # @rbs (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr, Reactor reactor) -> void
165
+ def dispatch_ssl_connection(listener, tcp_client, remote_addr, reactor)
166
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, listener.ssl_context)
167
+ ssl_socket.sync_close = true
168
+ return unless perform_ssl_handshake(ssl_socket)
169
+
170
+ if ssl_socket.alpn_protocol == H2_PROTOCOL
171
+ ssl_socket.write(Http2.build_server_settings_frame) rescue nil
172
+
173
+ reactor.add(
174
+ id: ssl_socket.object_id,
175
+ socket: ssl_socket,
176
+ remote_addr: remote_addr,
177
+ url_scheme: HTTPS_SCHEME,
178
+ protocol: :http2,
179
+ writer: Http2::Writer.new,
180
+ flow_control: Http2::FlowControl.new
181
+ )
161
182
 
162
- return
163
- end
183
+ return
164
184
  end
165
185
 
166
186
  @request.eager_accept(
167
- client,
168
- client.object_id,
187
+ ssl_socket,
188
+ ssl_socket.object_id,
169
189
  reactor,
170
190
  @thread_pool,
171
191
  remote_addr,
172
- url_scheme
192
+ HTTPS_SCHEME
173
193
  )
174
194
  end
195
+
196
+ # Drives a non-blocking SSL handshake to completion, bounded by the
197
+ # configured first-data timeout. Returns true on success, false on
198
+ # timeout or SSL error.
199
+ #
200
+ # @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket to hand-shake
201
+ # @return [Boolean] true if the handshake completed
202
+ #
203
+ # @rbs (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
204
+ def perform_ssl_handshake(ssl_socket)
205
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @client_options[:first_data_timeout]
206
+
207
+ begin
208
+ ssl_socket.accept_nonblock
209
+ true
210
+ rescue IO::WaitReadable
211
+ return false unless wait_for_handshake(ssl_socket, deadline, :read)
212
+
213
+ retry
214
+ rescue IO::WaitWritable
215
+ return false unless wait_for_handshake(ssl_socket, deadline, :write)
216
+
217
+ retry
218
+ rescue OpenSSL::SSL::SSLError => error
219
+ Log.rescued_error(error)
220
+ ssl_socket.close rescue nil
221
+ false
222
+ end
223
+ end
224
+
225
+ # Waits up to `deadline` for the socket to become ready for the next step
226
+ # of the SSL handshake. Closes the socket and returns false on timeout.
227
+ #
228
+ # @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket
229
+ # @param deadline [Float] absolute monotonic deadline
230
+ # @param direction [Symbol] either `:read` or `:write`
231
+ # @return [Boolean] true if the socket became ready before the deadline
232
+ #
233
+ # @rbs (OpenSSL::SSL::SSLSocket ssl_socket, Float deadline, Symbol direction) -> bool
234
+ def wait_for_handshake(ssl_socket, deadline, direction)
235
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
236
+ ready = if remaining <= 0
237
+ false
238
+ elsif direction == :read
239
+ ssl_socket.wait_readable(remaining)
240
+ else
241
+ ssl_socket.wait_writable(remaining)
242
+ end
243
+ return true if ready
244
+
245
+ Log.warn "SSL handshake timed out"
246
+ ssl_socket.close rescue nil
247
+ false
248
+ end
175
249
  end
176
250
  end
data/lib/raptor/stats.rb CHANGED
@@ -12,26 +12,27 @@ module Raptor
12
12
  # assigned a fixed-size slot in the shared region.
13
13
  #
14
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
15
+ # pid uint32 4 bytes
16
+ # index uint32 4 bytes
17
+ # phase uint32 4 bytes
18
+ # requests uint64 8 bytes
19
+ # backlog uint32 4 bytes
20
+ # busy_threads uint32 4 bytes
21
+ # thread_capacity uint32 4 bytes
22
+ # started_at float64 8 bytes
23
+ # last_checkin float64 8 bytes
24
+ # booted uint8 1 byte
25
+ # 49 bytes total
22
26
  #
23
27
  class Stats
24
- SLOT_FORMAT = "LQLddC"
25
- SLOT_SIZE = [0, 0, 0, 0.0, 0.0, 0].pack(SLOT_FORMAT).bytesize
28
+ SLOT_FORMAT = "LLLQLLLddC"
29
+ SLOT_SIZE = [0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, 0].pack(SLOT_FORMAT).bytesize
26
30
 
27
31
  # @rbs @num_workers: Integer
28
32
  # @rbs @mmap: untyped
29
33
 
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.
34
+ # Allocates the shared mmap region. Must be called before forking
35
+ # workers so the mapping is inherited by every child process.
35
36
  #
36
37
  # @param num_workers [Integer] number of worker slots to allocate
37
38
  # @return [void]
@@ -44,24 +45,27 @@ module Raptor
44
45
 
45
46
  # Writes stats for a worker slot into shared memory.
46
47
  #
47
- # @param index [Integer] slot index to write into
48
+ # @param index [Integer] slot index to write into; also written into the slot itself
48
49
  # @param pid [Integer] worker process ID
50
+ # @param phase [Integer] cluster phase this worker was forked at
49
51
  # @param requests [Integer] total requests handled by this worker
50
52
  # @param backlog [Integer] current queue depth
53
+ # @param busy_threads [Integer] worker threads currently processing requests
54
+ # @param thread_capacity [Integer] worker threads configured for this worker
51
55
  # @param started_at [Float] process start time as a Unix timestamp
52
56
  # @param last_checkin [Float] time of last stats write as a Unix timestamp
53
57
  # @param booted [Boolean] whether the worker has finished starting
54
58
  # @return [void]
55
59
  #
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)
60
+ # @rbs (Integer index, pid: Integer, phase: Integer, requests: Integer, backlog: Integer, busy_threads: Integer, thread_capacity: Integer, started_at: Float, last_checkin: Float, booted: bool) -> void
61
+ def write(index, pid:, phase:, requests:, backlog:, busy_threads:, thread_capacity:, started_at:, last_checkin:, booted:)
62
+ data = [pid, index, phase, requests, backlog, busy_threads, thread_capacity, started_at, last_checkin, booted ? 1 : 0].pack(SLOT_FORMAT)
59
63
  @mmap.semlock { @mmap[index * SLOT_SIZE, SLOT_SIZE] = data }
60
64
  end
61
65
 
62
66
  # Returns stats for all worker slots.
63
67
  #
64
- # @return [Array<Hash>] per-worker stat hashes with :pid, :requests, :backlog, :started_at, :last_checkin, and :booted
68
+ # @return [Array<Hash>] per-worker stat hashes with :pid, :index, :phase, :requests, :backlog, :busy_threads, :thread_capacity, :started_at, :last_checkin, and :booted
65
69
  #
66
70
  # @rbs () -> Array[Hash[Symbol, untyped]]
67
71
  def all
@@ -81,14 +85,14 @@ module Raptor
81
85
 
82
86
  # Reads stats for a worker slot from shared memory.
83
87
  #
84
- # @param index [Integer] slot index to read from
85
- # @return [Hash] stat hash with :pid, :requests, :backlog, :started_at, :last_checkin, and :booted
88
+ # @param slot [Integer] slot offset to read from
89
+ # @return [Hash] stat hash with :pid, :index, :phase, :requests, :backlog, :busy_threads, :thread_capacity, :started_at, :last_checkin, and :booted
86
90
  #
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 }
91
+ # @rbs (Integer slot) -> Hash[Symbol, untyped]
92
+ def read(slot)
93
+ data = @mmap[slot * SLOT_SIZE, SLOT_SIZE]
94
+ pid, index, phase, requests, backlog, busy_threads, thread_capacity, started_at, last_checkin, booted = data.unpack(SLOT_FORMAT)
95
+ { pid:, index:, phase:, requests:, backlog:, busy_threads:, thread_capacity:, started_at:, last_checkin:, booted: booted == 1 }
92
96
  end
93
97
  end
94
98
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Raptor
5
- VERSION = "0.3.0"
5
+ VERSION = "0.5.0"
6
6
  end
@@ -5,7 +5,7 @@ module Raptor
5
5
  #
6
6
  # CLI parses command-line arguments and starts the server cluster with the
7
7
  # specified configuration options. It supports configuring the number of
8
- # workers, threads, ractors, bind addresses, and various client timeout
8
+ # workers, ractors, threads, bind addresses, and various client timeout
9
9
  # settings.
10
10
  #
11
11
  # @example Basic usage
@@ -20,6 +20,8 @@ module Raptor
20
20
 
21
21
  DEFAULT_OPTIONS: untyped
22
22
 
23
+ DEFAULT_CONFIG_PATHS: untyped
24
+
23
25
  # Loads a configuration file and returns the hash it evaluates to.
24
26
  #
25
27
  # The file is evaluated at the top level so constants like `Raptor::*` resolve
@@ -33,6 +35,18 @@ module Raptor
33
35
  # @rbs (String path) -> Hash[Symbol, untyped]
34
36
  def self.load_config_file: (String path) -> Hash[Symbol, untyped]
35
37
 
38
+ # Returns the first existing path in {DEFAULT_CONFIG_PATHS} resolved
39
+ # against `root`, or nil if none exist.
40
+ #
41
+ # Used to pick up a project-local config file when no `-c`/`--config`
42
+ # flag was supplied.
43
+ #
44
+ # @param root [String] directory to resolve the default paths against
45
+ # @return [String, nil] the config path, or nil if no default file exists
46
+ #
47
+ # @rbs (?String root) -> String?
48
+ def self.default_config_path: (?String root) -> String?
49
+
36
50
  @command: Symbol
37
51
 
38
52
  @options: Hash[Symbol, untyped]
@@ -4,14 +4,15 @@ module Raptor
4
4
  # Multi-process web server cluster with advanced concurrency architecture.
5
5
  #
6
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.
7
+ # stack including a ractor pool for HTTP parsing, a thread pool for
8
+ # application processing, plus dedicated reactor and server threads. It
9
+ # handles process forking, signal management, graceful shutdown, and
10
+ # automatic worker restart when a worker process unexpectedly exits.
11
11
  #
12
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.
13
+ # maintaining efficient I/O and CPU utilization within each process
14
+ # through the combination of ractor-based parsing and thread pools on
15
+ # top of NIO reactors.
15
16
  #
16
17
  # Flow per worker process:
17
18
  # 1. Server continuously accepts connections but skips acceptance when backlog is high
@@ -22,7 +23,7 @@ module Raptor
22
23
  #
23
24
  # @example Basic usage
24
25
  # options = {
25
- # threads: 8, ractors: 2, workers: 4,
26
+ # workers: 4, ractors: 2, threads: 8,
26
27
  # binds: ["tcp://0.0.0.0:3000"],
27
28
  # rackup: "config.ru",
28
29
  # client: { first_data_timeout: 30, chunk_data_timeout: 10 }
@@ -37,53 +38,66 @@ module Raptor
37
38
  # @rbs (Hash[Symbol, untyped] options) -> void
38
39
  def self.run: (Hash[Symbol, untyped] options) -> void
39
40
 
40
- @phased_restarting: bool
41
+ @thread_count: Integer
41
42
 
42
- @phased_restart_requested: bool
43
+ @client_options: Hash[Symbol, Integer]
43
44
 
44
- @stats: Stats
45
+ @worker_timeout: Integer
45
46
 
46
- @workers: Hash[Integer, Integer]
47
+ @worker_boot_timeout: Integer
47
48
 
48
- @shutdown: bool
49
+ @worker_shutdown_timeout: Integer
49
50
 
50
- @app: untyped
51
+ @stats_file: String?
51
52
 
52
- @server_port: Integer
53
+ @pid_file: String?
54
+
55
+ @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
53
56
 
54
57
  @binder: Binder
55
58
 
56
- @pidfile: String?
59
+ @server_port: Integer
57
60
 
58
- @stats_file: String?
61
+ @app: untyped
59
62
 
60
- @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
63
+ @shutdown: bool
61
64
 
62
- @client_options: Hash[Symbol, Integer]
65
+ @workers: Hash[Integer, Integer]
63
66
 
64
- @worker_count: Integer
67
+ @timed_out: Set[Integer]
68
+
69
+ @stats: Stats
70
+
71
+ @phase: Integer
72
+
73
+ @phased_restart_requested: bool
74
+
75
+ @phased_restarting: bool
65
76
 
66
77
  @ractor_count: Integer
67
78
 
68
- @thread_count: Integer
79
+ @worker_count: Integer
69
80
 
70
81
  # Creates a new Cluster with the specified configuration.
71
82
  #
72
- # Initializes the cluster with thread, ractor, and worker counts,
83
+ # Initializes the cluster with worker, ractor, and thread counts,
73
84
  # sets up network binding, loads the Rack application, and prepares
74
85
  # for multi-process operation.
75
86
  #
76
87
  # @param options [Hash] cluster configuration options
77
- # @option options [Integer] :threads number of threads per worker process
78
- # @option options [Integer] :ractors number of ractors per worker process
79
- # @option options [Integer] :workers number of worker processes
80
88
  # @option options [Array<String>] :binds array of bind URIs
89
+ # @option options [Integer] :workers number of worker processes
90
+ # @option options [Integer] :ractors number of ractors per worker process
91
+ # @option options [Integer] :threads number of threads per worker process
81
92
  # @option options [#call] :app pre-built Rack application
82
93
  # @option options [String] :rackup path to Rack configuration file
83
94
  # @option options [Hash] :client client configuration
84
- # @option options [#call] :on_error callback invoked with (env, exception) when the Rack app raises
95
+ # @option options [Integer] :worker_timeout seconds to wait for a booted worker to check in before killing it
96
+ # @option options [Integer] :worker_boot_timeout seconds to wait for a worker to finish booting before killing it
97
+ # @option options [Integer] :worker_shutdown_timeout seconds to wait for graceful worker exit before force-killing
85
98
  # @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
86
- # @option options [String, nil] :pidfile path to write the master PID to, or nil to disable
99
+ # @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
100
+ # @option options [#call] :on_error callback invoked with (env, exception) when the Rack app raises
87
101
  # @return [void]
88
102
  #
89
103
  # @rbs (Hash[Symbol, untyped] options) -> void
@@ -92,15 +106,15 @@ module Raptor
92
106
  # Starts the multi-process cluster and manages worker processes.
93
107
  #
94
108
  # Forks the configured number of worker processes and monitors them,
95
- # automatically restarting any that exit unexpectedly. Handles graceful
96
- # shutdown via INT or TERM signals, stats logging via USR1, and phased
97
- # restart via USR2.
109
+ # restarting any that exit unexpectedly or stop checking in. Handles
110
+ # graceful shutdown via INT or TERM signals, stats logging via USR1,
111
+ # and phased restart via USR2.
98
112
  #
99
113
  # Each worker process includes:
100
114
  # - 1 server thread (continuously accepts connections with backpressure control)
101
115
  # - 1 reactor thread (I/O multiplexing, timeout handling, backlog monitoring)
102
- # - N ractor workers (parallel HTTP parsing)
103
- # - 1 ractor collector thread (coordinates parsing results)
116
+ # - N pipeline ractors (parallel HTTP parsing)
117
+ # - 1 pipeline collector thread (coordinates parsing results)
104
118
  # - M worker threads (Rack application processing and response writing)
105
119
  # - 1 stats thread (writes per-worker metrics to shared memory every second)
106
120
  #
@@ -120,6 +134,7 @@ module Raptor
120
134
  private
121
135
 
122
136
  # Forks a new worker process and registers it at the given index.
137
+ # The worker inherits the cluster's current phase.
123
138
  #
124
139
  # @param index [Integer] slot index for this worker in the stats region
125
140
  # @return [void]
@@ -135,6 +150,25 @@ module Raptor
135
150
  # @rbs () -> Symbol
136
151
  def reap_workers: () -> Symbol
137
152
 
153
+ # Stops every worker, escalating from TERM to KILL if any fail to
154
+ # exit within `worker_shutdown_timeout`.
155
+ #
156
+ # @return [void]
157
+ #
158
+ # @rbs () -> void
159
+ def stop_workers: () -> void
160
+
161
+ # Kills workers that have stopped checking in. A booted worker that
162
+ # fails to update its stats slot within `worker_timeout` seconds is
163
+ # assumed to be hung (deadlocked app, runaway loop, blocked syscall);
164
+ # a worker still in startup is held to `worker_boot_timeout`. Killed
165
+ # workers are then restarted by `reap_workers`.
166
+ #
167
+ # @return [void]
168
+ #
169
+ # @rbs () -> void
170
+ def timeout_hung_workers: () -> void
171
+
138
172
  # Replaces each worker process one at a time, waiting for the new
139
173
  # worker to boot before moving on to the next. Triggered by SIGUSR2.
140
174
  #
@@ -150,10 +184,11 @@ module Raptor
150
184
  # critical component fails.
151
185
  #
152
186
  # @param index [Integer] slot index for this worker in the stats region
187
+ # @param phase [Integer] the cluster phase this worker was forked at
153
188
  # @return [void]
154
189
  #
155
- # @rbs (Integer index) -> void
156
- def run_worker: (Integer index) -> void
190
+ # @rbs (Integer index, Integer phase) -> void
191
+ def run_worker: (Integer index, Integer phase) -> void
157
192
 
158
193
  # Returns a human-readable description of how a process exited.
159
194
  #
@@ -170,11 +205,8 @@ module Raptor
170
205
  # @rbs () -> void
171
206
  def shutdown: () -> void
172
207
 
173
- # Logs cluster initialization details including architecture and bind addresses.
174
- #
175
- # Outputs a hierarchical view of the cluster configuration showing
176
- # the master process, worker processes, and per-process thread/ractor
177
- # allocation along with listening addresses.
208
+ # Prints the cluster's startup banner showing process structure
209
+ # and bind addresses.
178
210
  #
179
211
  # @return [void]
180
212
  #