Class: Rack::NackMode

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/nack_mode.rb

Overview

Middleware that communicates impending shutdown to a load balancer via NACKing (negative acking) health checks. Your app needs to inform the middleware when it wants to shut down, and the middleware will call back when it’s safe to do so.

Responds to health checks on /admin (configurable via :path option).

Basic usage:

class MyApp < Sinatra::Base
  use Rack::NackMode do |health_check|
    # store the middleware instance for calling #shutdown below
    @health_check = health_check
  end

  class << self
    def shutdown
      if @health_check
        @health_check.shutdown { exit 0 }
      else
        exit 0
      end
    end
  end
end

N.B. because Rack waits to initialise middleware until it receives an HTTP request, it’s possible to shut down before the middleware is initialised. That’s unlikely to be a problem, because having not received any HTTP requests, we’ve obviously not received any *health check* requests either, meaning the load balancer should already believe we’re down: so it should be safe to shutdown immediately, as in the above example.

Defined Under Namespace

Modules: Timer

Constant Summary collapse

DEFAULT_NACKS_BEFORE_SHUTDOWN =

Default number of health checks we NACK before shutting down. This matches e.g. haproxy’s default for how many failed checks it needs before marking a backend as down.

3
DEFAULT_HEALTHCHECK_TIMEOUT =

Default time (in seconds) during shutdown to wait for the first healthcheck request before concluding that the healthcheck is missing or misconfigured, and shutting down anyway.

15

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) {|_self| ... } ⇒ NackMode

Returns a new instance of NackMode.

Yields:

  • (_self)

Yield Parameters:

Raises:

  • (ArgumentError)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rack/nack_mode.rb', line 46

def initialize(app, options = {})
  @app = app

  options.assert_valid_keys :path,
                            :healthy_if,
                            :sick_if,
                            :nacks_before_shutdown,
                            :healthcheck_timeout,
                            :logger
  @path = options[:path] || '/admin'
  @health_callback = if options[:healthy_if] && options[:sick_if]
    raise ArgumentError, 'Please specify either :healthy_if or :sick_if, not both'
  elsif healthy_if = options[:healthy_if]
    healthy_if
  elsif sick_if = options[:sick_if]
    lambda { !sick_if.call }
  else
    lambda { true }
  end
  @nacks_before_shutdown = options[:nacks_before_shutdown] || DEFAULT_NACKS_BEFORE_SHUTDOWN
  raise ArgumentError, ":nacks_before_shutdown must be at least 1" unless @nacks_before_shutdown >= 1
  @healthcheck_timeout = options[:healthcheck_timeout] || DEFAULT_HEALTHCHECK_TIMEOUT
  @logger = options[:logger]

  yield self if block_given?
end

Instance Method Details

#call(env) ⇒ Object



73
74
75
76
77
78
79
80
# File 'lib/rack/nack_mode.rb', line 73

def call(env)
  if health_check?(env)
    clear_healthcheck_timeout
    health_check_response(env)
  else
    @app.call(env)
  end
end

#shutdown(&block) ⇒ Object



82
83
84
85
86
87
88
89
# File 'lib/rack/nack_mode.rb', line 82

def shutdown(&block)
  info "Shutting down after NACKing #@nacks_before_shutdown health checks"
  @shutdown_callback = block

  install_healthcheck_timeout { do_shutdown }

  nil
end