Class: PumaPlus::ConfigFile

Inherits:
Object
  • Object
show all
Defined in:
lib/puma_plus/config_file.rb

Overview

The config-file DSL, evaluated against a plain Ruby file the way puma evaluates config/puma.rb.

A Ruby file rather than YAML because the interesting things are not values. Lifecycle hooks are blocks, worker counts are routinely computed from Etc.nprocessors or an env var, and a config that can branch on environment is the reason anyone tolerates a config file at all.

Names follow puma wherever the concept exists, so a config can be ported by deleting the lines that no longer apply rather than rewritten. Where puma has no equivalent -- autoscaling, Ractors, HTTP/2 and /3 -- the names are ours.

Constant Summary collapse

IGNORED =

Options a puma config sets that puma-plus has no equivalent for, mapped to why. Accepted and ignored with a warning rather than raising: a config ported from puma should boot, and then tell you what it dropped.

{
  preload_app!: "puma-plus always preloads; the shepherd loads the app once and forks",
  prune_bundler: "the Go server is not a gem, so there is no bundler to prune",
  queue_requests: "requests are always queued, in Go -- that is the whole design",
  wait_for_less_busy_worker: "Go dispatches to an idle worker by construction",
  fork_worker: "not implemented",
  nakayoshi_fork: "not implemented",
  set_remote_address: "Go builds REMOTE_ADDR when it constructs the env"
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options, path: nil) ⇒ ConfigFile

Returns a new instance of ConfigFile.



71
72
73
74
75
76
# File 'lib/puma_plus/config_file.rb', line 71

def initialize(options, path: nil)
  @options = options
  @path = path
  @hooks = Hash.new { |h, k| h[k] = [] }
  @warnings = []
end

Instance Attribute Details

#hooksObject (readonly)

Returns the value of attribute hooks.



78
79
80
# File 'lib/puma_plus/config_file.rb', line 78

def hooks
  @hooks
end

#warningsObject (readonly)

Returns the value of attribute warnings.



78
79
80
# File 'lib/puma_plus/config_file.rb', line 78

def warnings
  @warnings
end

Class Method Details

.load(path, options) ⇒ Object



80
81
82
83
84
# File 'lib/puma_plus/config_file.rb', line 80

def self.load(path, options)
  dsl = new(options, path: path)
  dsl.instance_eval(File.read(path), path, 1)
  dsl
end

Instance Method Details

#acquire_timeout(d) ⇒ Object



177
# File 'lib/puma_plus/config_file.rb', line 177

def acquire_timeout(d) = set(:acquire_timeout, duration(d, "acquire_timeout"))

#activate_control_app(url = "tcp://127.0.0.1:9293", _opts = {}) ⇒ Object

Where /stats, /metrics and /health are served.



188
189
190
# File 'lib/puma_plus/config_file.rb', line 188

def activate_control_app(url = "tcp://127.0.0.1:9293", _opts = {})
  set :control, Address.host_port(url, 9293, "activate_control_app")
end

#autoscale(min: nil, max: nil, target_queue_p95: nil, enabled: true) ⇒ Object

autoscale min: 1, max: 8, target_queue_p95: "25ms"



168
169
170
171
172
173
# File 'lib/puma_plus/config_file.rb', line 168

def autoscale(min: nil, max: nil, target_queue_p95: nil, enabled: true)
  set :autoscale, !!enabled
  set :min_workers, Integer(min) if min
  set :max_workers, Integer(max) if max
  set :target_queue_p95, duration(target_queue_p95, "target_queue_p95") if target_queue_p95
end

#before_fork(&blk) ⇒ Object

--- lifecycle hooks ------------------------------------------------

Blocks, so they cannot be translated into flags for the Go process. The worker re-reads this same config file and runs them itself; the launcher only records that they exist so it can pass the path along.



202
# File 'lib/puma_plus/config_file.rb', line 202

def before_fork(&blk) = @hooks[:before_fork] << blk

#bind(url) ⇒ Object

bind "tcp://0.0.0.0:9292" bind "ssl://0.0.0.0:9443"

Unix sockets are deliberately unsupported for the client listener: the unix socket in this design is how workers dial in, and letting a config point a public listener at one invites confusing the two.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/puma_plus/config_file.rb', line 94

def bind(url)
  uri = begin
    URI.parse(url)
  rescue URI::Error => e
    raise ConfigError, "bind #{url.inspect}: #{e.message}"
  end
  case uri.scheme
  when "tcp", "http"
    set :listen, "#{uri.host}:#{uri.port || 9292}"
  when "ssl", "https"
    set :listen_tls, "#{uri.host}:#{uri.port || 9443}"
    apply_ssl_params(URI.decode_www_form(uri.query.to_s).to_h, url)
  when "unix"
    raise ConfigError, "bind #{url.inspect}: unix sockets are not supported for " \
                       "the HTTP listener (the unix socket is how workers dial in)"
  else
    raise ConfigError, "bind #{url.inspect}: unknown scheme #{uri.scheme.inspect}"
  end
end

#debug_headers(enabled = true) ⇒ Object



194
# File 'lib/puma_plus/config_file.rb', line 194

def debug_headers(enabled = true) = set(:debug_headers, !!enabled)

#decision_log(path) ⇒ Object



176
# File 'lib/puma_plus/config_file.rb', line 176

def decision_log(path) = set(:decision_log, path.to_s)

#directory(dir) ⇒ Object



183
# File 'lib/puma_plus/config_file.rb', line 183

def directory(dir) = set(:directory, File.expand_path(dir.to_s))

#environment(env) ⇒ Object



182
# File 'lib/puma_plus/config_file.rb', line 182

def environment(env) = set(:environment, env.to_s)

#h2c(enabled = true) ⇒ Object

HTTP/2 cleartext on the main listener. Nothing in puma to mirror.



119
# File 'lib/puma_plus/config_file.rb', line 119

def h2c(enabled = true) = set(:h2c, !!enabled)

#http3(addr) ⇒ Object

HTTP/3 needs its own address because QUIC is UDP.



122
# File 'lib/puma_plus/config_file.rb', line 122

def http3(addr) = set(:listen_h3, addr.to_s)

#max_connections(n) ⇒ Object



163
# File 'lib/puma_plus/config_file.rb', line 163

def max_connections(n) = set(:max_conns, Integer(n))

#mem_limit(mb) ⇒ Object



175
# File 'lib/puma_plus/config_file.rb', line 175

def mem_limit(mb) = set(:mem_limit_mb, Integer(mb))

#on_worker_boot(&blk) ⇒ Object



203
# File 'lib/puma_plus/config_file.rb', line 203

def on_worker_boot(&blk) = @hooks[:on_worker_boot] << blk

#on_worker_shutdown(&blk) ⇒ Object



204
# File 'lib/puma_plus/config_file.rb', line 204

def on_worker_shutdown(&blk) = @hooks[:on_worker_shutdown] << blk

#pidfile(path) ⇒ Object



185
# File 'lib/puma_plus/config_file.rb', line 185

def pidfile(path) = set(:pidfile, path.to_s)

#port(port, host = nil) ⇒ Object



114
115
116
# File 'lib/puma_plus/config_file.rb', line 114

def port(port, host = nil)
  set :listen, "#{host || '0.0.0.0'}:#{Integer(port)}"
end

#rackup(path) ⇒ Object

--- app ------------------------------------------------------------



181
# File 'lib/puma_plus/config_file.rb', line 181

def rackup(path) = set(:app, path.to_s)

#ractors(count) ⇒ Object

Serve from N Ractors in one process instead of threads across forks.



161
# File 'lib/puma_plus/config_file.rb', line 161

def ractors(count) = set(:ractors, Integer(count))

#ssl_bind(host, port, opts = {}) ⇒ Object

ssl_bind "0.0.0.0", 9443, cert: "...", key: "..."

puma's signature, so an existing ssl_bind line ports unchanged. The equivalent query-string form on bind works too, since both end up here.



130
131
132
133
# File 'lib/puma_plus/config_file.rb', line 130

def ssl_bind(host, port, opts = {})
  set :listen_tls, "#{host}:#{Integer(port)}"
  apply_ssl_params(opts.transform_keys(&:to_s), "ssl_bind")
end

#tag(name) ⇒ Object



184
# File 'lib/puma_plus/config_file.rb', line 184

def tag(name) = set(:tag, name.to_s)

#threads(min, max = min) ⇒ Object

threads min, max

puma-plus has no thread pool to grow and shrink: a thread is a connection to Go, dialed at boot and held. So only the max is meaningful, and a config that asks for a range gets told which number was used rather than silently having one picked.

Raises:



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/puma_plus/config_file.rb', line 148

def threads(min, max = min)
  min = Integer(min)
  max = Integer(max)
  raise ConfigError, "threads: min (#{min}) must be <= max (#{max})" if min > max

  if min != max
    @warnings << "threads #{min},#{max}: puma-plus holds a fixed number of " \
                 "worker connections, so #{max} is used and #{min} ignored"
  end
  set :threads, max
end

#tls_cert(cert, key) ⇒ Object

Certificate and key paths for the TLS and HTTP/3 listeners.



136
# File 'lib/puma_plus/config_file.rb', line 136

def tls_cert(cert, key) = (set(:tls_cert, expand(cert)); set(:tls_key, expand(key)))

#tls_hosts(*names) ⇒ Object



124
# File 'lib/puma_plus/config_file.rb', line 124

def tls_hosts(*names) = set(:tls_hosts, names.flatten.join(","))

#websockets(enabled = true) ⇒ Object



192
# File 'lib/puma_plus/config_file.rb', line 192

def websockets(enabled = true) = set(:websockets, !!enabled)

#webtransport(enabled = true) ⇒ Object



193
# File 'lib/puma_plus/config_file.rb', line 193

def webtransport(enabled = true) = set(:webtransport, !!enabled)

#workers(count) ⇒ Object

--- concurrency ----------------------------------------------------



140
# File 'lib/puma_plus/config_file.rb', line 140

def workers(count) = set(:workers, Integer(count))