Class: WorkerKiller::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/worker_killer/middleware.rb

Direct Known Subclasses

OOMLimiter, RequestsLimiter

Defined Under Namespace

Classes: OOMLimiter, RequestsLimiter

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, killer:, klass:, interval: 10, inhibitions: [], reaction: nil, **opts) ⇒ Middleware

inhibitions - список адресов, которые будут времнно блокировать перезапуск воркеров: Rails.application.config.middleware.insert_before(

Rack::Sendfile,
WorkerKiller::Middleware::RequestsLimiter, killer:, min: 2, max: 3, inhibitions: ['/test']

)



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/worker_killer/middleware.rb', line 15

def initialize(app, killer:, klass:, interval: 10, inhibitions: [], reaction: nil, **opts)
  @app = app
  @killer = killer
  @interval = interval

  @inhibitions = inhibitions.dup.freeze

  @reaction = reaction || method(:default_kill)

  @limiter = klass.new(**opts)
  @last_reacted_at = 0.0
  @inhibited = 0
  @delayed_reaction = nil
end

Instance Attribute Details

#inhibitionsObject (readonly)

Returns the value of attribute inhibitions.



7
8
9
# File 'lib/worker_killer/middleware.rb', line 7

def inhibitions
  @inhibitions
end

#intervalObject (readonly)

Returns the value of attribute interval.



7
8
9
# File 'lib/worker_killer/middleware.rb', line 7

def interval
  @interval
end

#killerObject (readonly)

Returns the value of attribute killer.



7
8
9
# File 'lib/worker_killer/middleware.rb', line 7

def killer
  @killer
end

#limiterObject (readonly)

Returns the value of attribute limiter.



7
8
9
# File 'lib/worker_killer/middleware.rb', line 7

def limiter
  @limiter
end

#reactionObject (readonly)

Returns the value of attribute reaction.



7
8
9
# File 'lib/worker_killer/middleware.rb', line 7

def reaction
  @reaction
end

Instance Method Details

#call(env) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/worker_killer/middleware.rb', line 30

def call(env)
  if (path_info = env['PATH_INFO']) && inhibitions.any?{|i| path_info[i] }
    call_with_inhibition(env, path_info) do
      # реакция будет вызвана после реального окончания обработки
      react if limiter.check
    end
  else
    @app.call(env).tap do
      # реакция будет вызвана сейчас (как обычно)
      react if limiter.check
    end
  end
end

#call_with_inhibition(env, path_info, &after) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/worker_killer/middleware.rb', line 44

def call_with_inhibition(env, path_info, &after)
  inihibit(path_info)

  rack_response = nil
  begin
    rack_response = @app.call(env)
  rescue Exception
    # в случае ошибоки во время @app.call возращаем как было
    release
    raise
  end

  # Почему именно each описано в спецификации в разделе The Response
  # https://github.com/rack/rack/blob/main/SPEC.rdoc
  if rack_response[2].respond_to?(:each)
    rack_response[2] = wrap_rack_response_body(rack_response[2], &after)
  else
    release # освобождаем сразу
    after.call
  end

  rack_response
end

#default_kill(l, k) ⇒ Object



92
93
94
# File 'lib/worker_killer/middleware.rb', line 92

def default_kill(l, k)
  k.kill(l.started_at)
end

#inihibit(_path_info) ⇒ Object



96
97
98
# File 'lib/worker_killer/middleware.rb', line 96

def inihibit(_path_info)
  @inhibited += 1
end

#loggerObject



109
110
111
# File 'lib/worker_killer/middleware.rb', line 109

def logger
  @logger ||= WorkerKiller.configuration.logger
end

#reactObject



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/worker_killer/middleware.rb', line 80

def react
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  return if now - @last_reacted_at < @interval

  @last_reacted_at = now
  if @inhibited > 0
    @delayed_reaction = -> { reaction.call(limiter, killer) }
  else
    reaction.call(limiter, killer)
  end
end

#releaseObject



100
101
102
103
104
105
106
107
# File 'lib/worker_killer/middleware.rb', line 100

def release
  return unless ((@inhibited -= 1) == 0) && @delayed_reaction

  @delayed_reaction.tap do |cb|
    @delayed_reaction = nil
    cb.call
  end
end

#wrap_rack_response_body(rack_response_body, &after) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/worker_killer/middleware.rb', line 68

def wrap_rack_response_body(rack_response_body, &after)
  Enumerator.new do |y|
    rack_response_body.each {|chunk| y << chunk }
    # Почему именно close описано в спецификации в разделе The Response
    # https://github.com/rack/rack/blob/main/SPEC.rdoc
    rack_response_body.close if rack_response_body.respond_to?(:close)

    release # освобождаем после отправки всего тела
    after.call
  end
end