Class: PumaPlus::Hooks
- Inherits:
-
Object
- Object
- PumaPlus::Hooks
- Defined in:
- lib/puma_plus/hooks.rb
Overview
Lifecycle hooks from the config file.
These are the one part of the configuration that cannot be translated into
flags for the Go server, because they are blocks. So the config file is read
twice: once by the puma-plus launcher, which turns settings into flags and
then execs Go, and again here by the Ruby process that actually runs the
application, which is the only process where a hook can meaningfully run.
Reading it twice means any top-level side effect in the config file happens twice. That is worth knowing and is why config files should declare rather than do -- the same advice puma's own docs give, for the same reason.
Semantics follow puma exactly (verified against puma/lib/puma/cluster.rb:438 and cluster/worker.rb:58,151):
before_fork once in the shepherd, before ANY worker is forked
on_worker_boot(idx) in each worker after fork, before it serves
on_worker_shutdown in each worker, before it exits
The timing of on_worker_boot is unusually clean here. A worker becomes visible to Go by dialing in, so running boot hooks before that dial means Go cannot dispatch a request to a worker whose hooks have not finished. In puma the equivalent guarantee needs the master to track a booted state; here it falls out of the connection being the readiness signal.
Constant Summary collapse
- EMPTY =
{}.freeze
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Class Method Summary collapse
Instance Method Summary collapse
- #any?(name) ⇒ Boolean
-
#initialize(hooks, logger: $stderr, path: nil) ⇒ Hooks
constructor
A new instance of Hooks.
- #names ⇒ Object
-
#run(name, *args, fatal: true) ⇒ Object
Run every block registered for
name, in declaration order.
Constructor Details
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
54 55 56 |
# File 'lib/puma_plus/hooks.rb', line 54 def path @path end |
Class Method Details
.load(path, logger: $stderr) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/puma_plus/hooks.rb', line 33 def self.load(path, logger: $stderr) return new(EMPTY, logger: logger) unless path && File.exist?(path) # A throwaway Options: every non-hook directive in the file still runs and # still sets values, and all of them are discarded. The launcher already # turned those into flags, and honouring them here would let a worker # disagree with the server about its own configuration. dsl = ConfigFile.load(path, Options.new({})) new(dsl.hooks, logger: logger, path: path) rescue StandardError => e # A config file that raises when read for hooks would otherwise take down # a worker with a stack trace and no indication that hooks were the cause. raise ConfigError, "loading hooks from #{path}: #{e.class}: #{e.}" end |
Instance Method Details
#any?(name) ⇒ Boolean
56 |
# File 'lib/puma_plus/hooks.rb', line 56 def any?(name) = !(@hooks[name].nil? || @hooks[name].empty?) |
#names ⇒ Object
58 |
# File 'lib/puma_plus/hooks.rb', line 58 def names = @hooks.keys.select { |k| any?(k) } |
#run(name, *args, fatal: true) ⇒ Object
Run every block registered for name, in declaration order.
fatal: is the difference between "this process cannot do its job" and "this process is leaving anyway". A failed on_worker_boot means the worker would serve requests without whatever the hook was supposed to establish -- a database connection, most often -- so it dies loudly instead of serving wrongly. A failed shutdown hook is logged and the shutdown continues, because refusing to exit helps nobody.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/puma_plus/hooks.rb', line 68 def run(name, *args, fatal: true) blocks = @hooks[name] return if blocks.nil? || blocks.empty? blocks.each_with_index do |blk, i| blk.call(*args) rescue StandardError, ScriptError => e where = "#{name} hook #{i + 1}/#{blocks.size}#{@path ? " from #{@path}" : ''}" if fatal @logger.puts "[puma-plus] #{where} failed: #{e.class}: #{e.}" Array(e.backtrace).first(8).each { |l| @logger.puts "[puma-plus] #{l}" } raise HookError, "#{where} failed: #{e.class}: #{e.}" end @logger.puts "[puma-plus] #{where} failed (continuing): #{e.class}: #{e.}" end end |