raptor 0.11.0 → 0.13.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/binder.rb CHANGED
@@ -5,30 +5,9 @@ require "socket"
5
5
  require "uri"
6
6
 
7
7
  module Raptor
8
- # Manages binding to network addresses and creating listening sockets.
9
- #
10
- # Binder handles parsing URI bind specifications, creating TCP, Unix, and SSL
11
- # server sockets, and managing socket options for optimal performance. It
12
- # supports binding to multiple addresses simultaneously.
13
- #
14
- # @example TCP binding
15
- # binder = Binder.new(["tcp://0.0.0.0:3000", "tcp://[::1]:3000"])
16
- # puts binder.addresses #=> ["0.0.0.0:3000", "[::1]:3000"]
17
- # binder.close
18
- #
19
- # @example Unix socket binding
20
- # binder = Binder.new(["unix:///tmp/raptor.sock"])
21
- # puts binder.addresses #=> ["/tmp/raptor.sock"]
22
- # binder.close
23
- #
24
- # @example SSL binding
25
- # binder = Binder.new(["ssl://0.0.0.0:443?cert=/path/to.crt&key=/path/to.key"])
26
- # puts binder.addresses #=> ["ssl://0.0.0.0:443"]
27
- # binder.close
28
- #
29
- # @example Localhost binding
30
- # binder = Binder.new(["tcp://localhost:8080"])
31
- # # Binds to both IPv4 and IPv6 loopback addresses
8
+ # Binds `tcp://`, `unix://`, and `ssl://` URIs to listening sockets and
9
+ # holds them for the server. Reconstructs listeners from inherited file
10
+ # descriptors when provided (systemd socket activation, hot restart).
32
11
  #
33
12
  class Binder
34
13
  SOCKET_BACKLOG = 1024
@@ -38,11 +17,8 @@ module Raptor
38
17
  def initialize(scheme) = super("unknown scheme: #{scheme.inspect}")
39
18
  end
40
19
 
41
- # Wraps a TCPServer with an SSL context for accepting SSL connections.
42
- #
43
- # Holds both the underlying TCP server and the SSL context together so
44
- # the server thread can accept a TCP connection and then perform the SSL
45
- # handshake in a single coordinated step.
20
+ # Pairs a TCPServer with the SSL context to use for accepted
21
+ # connections.
46
22
  #
47
23
  SslListener = Data.define(:tcp_server, :ssl_context) do
48
24
  # @rbs () -> TCPServer
@@ -61,28 +37,24 @@ module Raptor
61
37
  # @rbs @listeners: Array[TCPServer | UNIXServer | SslListener]
62
38
  # @rbs @uri_listeners: Hash[String, Array[TCPServer | UNIXServer | SslListener]]
63
39
 
64
- # Array of bind URIs.
40
+ # Returns the array of bind URIs.
65
41
  #
66
- # @return [Array<String>] the bind URIs
42
+ # @return [Array<String>]
67
43
  attr_reader :bind_uris
68
44
 
69
- # Kernel listen() queue depth for TCP/SSL listeners.
45
+ # Returns the kernel `listen()` queue depth for TCP/SSL listeners.
70
46
  #
71
- # @return [Integer] the socket backlog
47
+ # @return [Integer]
72
48
  attr_reader :socket_backlog
73
49
 
74
- # Array of listening sockets.
50
+ # Returns the array of listening sockets.
75
51
  #
76
- # @return [Array<TCPServer, UNIXServer, SslListener>] the server sockets
52
+ # @return [Array<TCPServer, UNIXServer, SslListener>]
77
53
  attr_reader :listeners
78
54
 
79
- # Creates a new Binder with the specified bind URIs.
80
- #
81
- # Parses the provided bind URIs and creates listening sockets for each one.
82
- # Supports tcp://, unix://, and ssl:// schemes. Localhost is expanded to
83
- # all available loopback addresses (both IPv4 and IPv6). When `inherited_fds`
84
- # supplies file descriptors for a URI, the listener is reconstructed from
85
- # those FDs instead of binding fresh.
55
+ # Creates a new Binder and binds each URI. `localhost` expands to both
56
+ # IPv4 and IPv6 loopback; when `inherited_fds` supplies file descriptors
57
+ # for a URI the listener is rebuilt from those instead of binding fresh.
86
58
  #
87
59
  # @param bind_uris [Array<String>] array of URI strings to bind to
88
60
  # @param socket_backlog [Integer] kernel listen() queue depth for TCP/SSL listeners
@@ -90,9 +62,6 @@ module Raptor
90
62
  # @return [void]
91
63
  # @raise [UnknownBindSchemeError] if a URI has an unsupported scheme
92
64
  #
93
- # @example
94
- # binder = Binder.new(["tcp://0.0.0.0:3000", "unix:///tmp/raptor.sock"])
95
- #
96
65
  # @rbs (Array[String] bind_uris, ?socket_backlog: Integer, ?inherited_fds: Hash[String, Array[Integer]]) -> void
97
66
  def initialize(bind_uris, socket_backlog: SOCKET_BACKLOG, inherited_fds: {})
98
67
  @bind_uris = bind_uris
@@ -103,15 +72,10 @@ module Raptor
103
72
  parse
104
73
  end
105
74
 
106
- # Returns the bound addresses as strings.
75
+ # Returns the bound addresses as strings: TCP as `host:port`, Unix as
76
+ # the socket path, SSL as `ssl://host:port`.
107
77
  #
108
- # TCP listeners are returned as "host:port", Unix listeners as the socket
109
- # path, and SSL listeners as "ssl://host:port".
110
- #
111
- # @return [Array<String>] address strings for each bound listener
112
- #
113
- # @example
114
- # binder.addresses #=> ["127.0.0.1:3000", "/tmp/raptor.sock", "ssl://0.0.0.0:443"]
78
+ # @return [Array<String>]
115
79
  #
116
80
  # @rbs () -> Array[String]
117
81
  def addresses
@@ -129,12 +93,10 @@ module Raptor
129
93
  end
130
94
  end
131
95
 
132
- # Returns the port number of the first TCP or SSL listener.
96
+ # Returns the port of the first TCP or SSL listener, or 0 when none
97
+ # is configured.
133
98
  #
134
- # Used to populate SERVER_PORT in the Rack environment. Returns 0
135
- # if no TCP or SSL listener is configured (e.g., Unix socket only).
136
- #
137
- # @return [Integer] the port number, or 0 if no TCP listener exists
99
+ # @return [Integer]
138
100
  #
139
101
  # @rbs () -> Integer
140
102
  def server_port
@@ -153,9 +115,7 @@ module Raptor
153
115
  @listeners.each(&:close)
154
116
  end
155
117
 
156
- # Returns the file descriptors of every listener, grouped by the bind URI
157
- # they were created from. The result is the payload to hand to a successor
158
- # process via the `inherited_fds:` constructor argument.
118
+ # Returns the file descriptors of every listener, grouped by bind URI.
159
119
  #
160
120
  # @return [Hash{String => Array<Integer>}]
161
121
  #
@@ -185,24 +145,21 @@ module Raptor
185
145
  # @rbs () -> void
186
146
  def parse
187
147
  @uri_listeners = @bind_uris.to_h do |bind_uri|
188
- if filenos = @inherited_fds[bind_uri]
189
- [bind_uri, restore_listeners(bind_uri, filenos)]
190
- else
191
- [bind_uri, create_listeners(bind_uri)]
192
- end
148
+ uri = URI.parse(bind_uri)
149
+ filenos = @inherited_fds[bind_uri]
150
+ [bind_uri, filenos ? restore_listeners(uri, filenos) : create_listeners(uri)]
193
151
  end
194
152
  @listeners = @uri_listeners.values.flatten
195
153
  end
196
154
 
197
- # Creates fresh listeners for the given bind URI.
155
+ # Creates fresh listeners for the given URI.
198
156
  #
199
- # @param bind_uri [String] the URI to bind
157
+ # @param uri [URI] the parsed bind URI
200
158
  # @return [Array<TCPServer, UNIXServer, SslListener>]
201
159
  # @raise [UnknownBindSchemeError] if the URI scheme is not supported
202
160
  #
203
- # @rbs (String bind_uri) -> Array[TCPServer | UNIXServer | SslListener]
204
- def create_listeners(bind_uri)
205
- uri = URI.parse(bind_uri)
161
+ # @rbs (URI::Generic uri) -> Array[TCPServer | UNIXServer | SslListener]
162
+ def create_listeners(uri)
206
163
  case uri.scheme
207
164
  when "tcp"
208
165
  create_tcp_listeners(uri.host, uri.port)
@@ -215,17 +172,16 @@ module Raptor
215
172
  end
216
173
  end
217
174
 
218
- # Reconstructs listeners for the given bind URI from inherited file
175
+ # Reconstructs listeners for the given URI from inherited file
219
176
  # descriptors.
220
177
  #
221
- # @param bind_uri [String] the URI the FDs were bound to
178
+ # @param uri [URI] the parsed bind URI the FDs were bound to
222
179
  # @param filenos [Array<Integer>] file descriptors to wrap
223
180
  # @return [Array<TCPServer, UNIXServer, SslListener>]
224
181
  # @raise [UnknownBindSchemeError] if the URI scheme is not supported
225
182
  #
226
- # @rbs (String bind_uri, Array[Integer] filenos) -> Array[TCPServer | UNIXServer | SslListener]
227
- def restore_listeners(bind_uri, filenos)
228
- uri = URI.parse(bind_uri)
183
+ # @rbs (URI::Generic uri, Array[Integer] filenos) -> Array[TCPServer | UNIXServer | SslListener]
184
+ def restore_listeners(uri, filenos)
229
185
  case uri.scheme
230
186
  when "tcp"
231
187
  filenos.map { |fileno| TCPServer.for_fd(fileno) }
@@ -267,11 +223,9 @@ module Raptor
267
223
  [tcp_server]
268
224
  end
269
225
 
270
- # Creates a Unix domain server socket at the given path.
271
- #
272
- # Removes stale socket files left by crashed processes (when the socket
273
- # is not currently in use). Registers an at_exit hook to clean up the
274
- # socket file on normal process exit.
226
+ # Creates a Unix domain server socket at the given path. Removes stale
227
+ # socket files left by crashed processes, and cleans the file up on
228
+ # the master's clean exit.
275
229
  #
276
230
  # @param path [String] filesystem path for the Unix socket
277
231
  # @return [Array<UNIXServer>] array containing the created Unix server socket
@@ -306,15 +260,11 @@ module Raptor
306
260
  at_exit { File.delete(path) rescue nil if Process.pid == master_pid }
307
261
  end
308
262
 
309
- # Creates SSL server sockets for the given host, port, and SSL parameters.
310
- #
311
- # Wraps each TCP listener with an SSL context to produce SslListener objects.
312
- # The ssl_params hash must include "cert" and "key" entries pointing to the
313
- # certificate and private key files respectively.
263
+ # Creates SSL server sockets for the given host and port.
314
264
  #
315
265
  # @param host [String, nil] hostname or IP address to bind to
316
266
  # @param port [Integer, nil] port number to bind to
317
- # @param ssl_params [Hash<String, String>] SSL options ("cert" and "key" paths)
267
+ # @param ssl_params [Hash<String, String>] SSL options (`cert` and `key` file paths)
318
268
  # @return [Array<SslListener>] array containing the created SSL listener(s)
319
269
  #
320
270
  # @rbs (String? host, Integer? port, Hash[String, String] ssl_params) -> Array[SslListener]
data/lib/raptor/cli.rb CHANGED
@@ -56,6 +56,11 @@ module Raptor
56
56
  worker_timeout: 60,
57
57
  worker_drain_timeout: 25,
58
58
  worker_shutdown_timeout: 30,
59
+ refork_after: (RUBY_PLATFORM.include?("linux") ? 1000 : nil),
60
+ before_fork: [].freeze,
61
+ before_worker_boot: [].freeze,
62
+ before_worker_shutdown: [].freeze,
63
+ before_refork: [].freeze,
59
64
  stats_file: "tmp/raptor.json",
60
65
  pid_file: nil,
61
66
  stdout_file: nil,
@@ -65,11 +70,9 @@ module Raptor
65
70
 
66
71
  DEFAULT_CONFIG_PATHS = ["raptor.rb", "config/raptor.rb"].freeze
67
72
 
68
- # Loads a configuration file and returns the hash it evaluates to.
69
- #
70
- # The file is evaluated at the top level so constants like `Raptor::*` resolve
71
- # the same as in a regular Ruby script. The final expression must be a Hash
72
- # of cluster options (the same keys accepted by {Raptor::Cluster#initialize}).
73
+ # Loads a Ruby config file and returns the options hash it evaluates
74
+ # to. Evaluated at the top level so `Raptor::*` constants resolve the
75
+ # same as in a regular script.
73
76
  #
74
77
  # @param path [String] path to a Ruby file that evaluates to a Hash
75
78
  # @return [Hash{Symbol => untyped}] cluster options
@@ -86,9 +89,6 @@ module Raptor
86
89
  # Returns the first existing path in {DEFAULT_CONFIG_PATHS} resolved
87
90
  # against `root`, or nil if none exist.
88
91
  #
89
- # Used to pick up a project-local config file when no `-c`/`--config`
90
- # flag was supplied.
91
- #
92
92
  # @param root [String] directory to resolve the default paths against
93
93
  # @return [String, nil] the config path, or nil if no default file exists
94
94
  #
@@ -101,22 +101,14 @@ module Raptor
101
101
  # @rbs @options: Hash[Symbol, untyped]
102
102
  # @rbs @parser: OptionParser
103
103
 
104
- # Creates a new CLI instance and parses command-line arguments.
105
- #
106
- # Parses the provided command-line arguments and configures the server
107
- # options accordingly. A rackup file can be provided as the first
108
- # positional argument (defaults to config.ru).
104
+ # Creates a new CLI instance from `argv`. A rackup file may be given
105
+ # as the first positional argument; every other value is parsed as a
106
+ # flag.
109
107
  #
110
108
  # @param argv [Array<String>] command-line arguments to parse
111
109
  # @return [void]
112
110
  # @raise [OptionParser::ParseError] if invalid options are provided
113
111
  #
114
- # @example With rackup file
115
- # cli = CLI.new(["my_app.ru", "-w", "4"])
116
- #
117
- # @example With options only
118
- # cli = CLI.new(["-t", "8", "-r", "2"])
119
- #
120
112
  # @rbs (Array[String] argv) -> void
121
113
  def initialize(argv)
122
114
  @options = DEFAULT_OPTIONS.dup
@@ -175,12 +167,9 @@ module Raptor
175
167
  end
176
168
  end
177
169
 
178
- # Scans argv for a `-c`/`--config` flag and returns the configured path.
179
- #
180
- # The pre-scan runs before the main OptionParser pass so the config file
181
- # can be applied as a base layer that CLI args then override. All four
182
- # OptionParser-accepted forms (`-c PATH`, `-cPATH`, `--config PATH`,
183
- # `--config=PATH`) are recognized.
170
+ # Returns the path from a `-c`/`--config` flag in `argv`, recognising
171
+ # all four OptionParser-accepted forms (`-c PATH`, `-cPATH`,
172
+ # `--config PATH`, `--config=PATH`).
184
173
  #
185
174
  # @param argv [Array<String>] command-line arguments to scan
186
175
  # @return [String, nil] the config path, or nil if no flag was supplied
@@ -312,6 +301,10 @@ module Raptor
312
301
  @options[:worker_shutdown_timeout] = timeout
313
302
  end
314
303
 
304
+ opts.on("--refork-after NUM", Integer, "Refork workers from a warmed source after any worker crosses NUM requests; 0 disables (default: 1000)") do |num|
305
+ @options[:refork_after] = num
306
+ end
307
+
315
308
  opts.on("--stats-file PATH", String, "Stats file path (default: tmp/raptor.json)") do |path|
316
309
  @options[:stats_file] = path
317
310
  end