worker_killer 1.1.0.223443 → 1.2.0.338611

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fba3518e6f518f19b01b87115d33fe0db060fad10c4982eeb66fbdf06700045
4
- data.tar.gz: e2bc9e0d6db5185aeecf1c787bc7655065d701692ef1660cbdc14982b1d90001
3
+ metadata.gz: 5cb4778d7409cc1a260c9db20b53cc61911b7ea26cae741ab7ed86d12e1fcb73
4
+ data.tar.gz: 3fd9bcafb2c7574b1621cc022d6cd51246f9b43b2ae3b2ac7ccbaf617197e90f
5
5
  SHA512:
6
- metadata.gz: 04bb02b8cb52fe1a212bbbcf2a67f413c8cf22ee3808d1b1712e35bf8bc57ff2f4f6cab27006210c2bcc7ee23bd94055f0e7cc454da0f371138c3f5f4b6ced7d
7
- data.tar.gz: b6a80f43dcfcfc15731f594c1aaa93a87305c83ad8a5f8bc8e3cd5bfa623090adb367fb8251e458836e409664310ef9c648e0e97f67e6647e0686e96a7383d88
6
+ metadata.gz: 6d33c26e1cd97761c0a60d9e666d65e205ab19f92477e9f84f9c29f424f69c9da39bde47e88a2cb71907dcef5cf6d677ee76e7d7fade57151968cd22dc1e09cc
7
+ data.tar.gz: 47852e9794dd6ba0823dacde4b7a4a9020f8250d046a8155fd3e52f93621767d2c346c0ddfa7ef573aa67fc466b1b697dcba82ddbda184d84b91141a3bab3259
@@ -1,27 +1,37 @@
1
- require 'socket'
2
-
3
1
  module ::WorkerKiller
4
2
  module Killer
5
3
  class Puma < ::WorkerKiller::Killer::Base
6
4
 
7
- attr_accessor :ipc_path, :worker_num
5
+ attr_accessor :worker_num
8
6
 
9
- def initialize(ipc_path:, worker_num: nil, **kwargs)
7
+ def initialize(puma_plugin:, worker_num: nil, **kwargs)
10
8
  super(**kwargs)
11
- @ipc_path = ipc_path
9
+ @puma_plugin = puma_plugin
12
10
  @worker_num = worker_num
13
11
  end
14
12
 
15
13
  def do_kill(sig, pid, alive_sec, **_params)
14
+ return if @worker_num.nil? # May be nil if Puma not in Cluster mode
16
15
  return if @already_sended == sig
17
16
 
18
- logger.warn "#{self} send #{worker_num} to Puma Plugin (pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts}) triggered by #{sig}"
17
+ logger.warn "#{self.class} send [W#{worker_num}] to Puma Plugin (from pid: #{pid}) alive: #{alive_sec} sec (trial #{kill_attempts}) triggered by #{sig}"
19
18
 
20
19
  @already_sended = sig
20
+ @puma_plugin.set_logger!(logger)
21
+
22
+ @puma_plugin.request_restart_server(worker_num)
23
+ end
24
+
25
+ def do_inhibit(_path_info)
26
+ @puma_plugin.set_logger!(logger)
27
+
28
+ @puma_plugin.inhibit_restart(worker_num)
29
+ end
30
+
31
+ def do_release
32
+ @puma_plugin.set_logger!(logger)
21
33
 
22
- Socket.unix(ipc_path) do |sock|
23
- sock.puts Integer(worker_num).to_s
24
- end
34
+ @puma_plugin.release_restart(worker_num)
25
35
  end
26
36
 
27
37
  end
@@ -29,7 +29,7 @@ module WorkerKiller
29
29
  # :nocov:
30
30
 
31
31
  def logger
32
- @logger || WorkerKiller.configuration.logger
32
+ @logger ||= WorkerKiller.configuration.logger
33
33
  end
34
34
 
35
35
  end
@@ -4,23 +4,80 @@ require 'worker_killer/count_limiter'
4
4
  module WorkerKiller
5
5
  class Middleware
6
6
 
7
- attr_reader :limiter, :killer, :reaction
7
+ attr_reader :limiter, :killer, :reaction, :inhibitions
8
8
 
9
- def initialize(app, killer:, klass:, reaction: nil, **opts)
9
+ # inhibitions - список адресов, которые будут времнно блокировать перезапуск воркеров:
10
+ # Rails.application.config.middleware.insert_before(
11
+ # Rack::Sendfile,
12
+ # WorkerKiller::Middleware::RequestsLimiter, killer:, min: 2, max: 3, inhibitions: ['/test']
13
+ # )
14
+ #
15
+ def initialize(app, killer:, klass:, inhibitions: [], reaction: nil, **opts)
10
16
  @app = app
11
17
  @killer = killer
12
18
 
13
- @reaction = reaction || proc do |l, k|
14
- k.kill(l.started_at)
19
+ @inhibitions = if @killer.respond_to?(:do_inhibit)
20
+ inhibitions.dup.freeze
21
+ else
22
+ [].freeze
15
23
  end
16
24
 
25
+ @reaction = reaction || method(:default_kill)
26
+
17
27
  @limiter = klass.new(**opts)
18
28
  end
19
29
 
20
30
  def call(env)
21
- @app.call(env)
31
+ inhibited = false
32
+ if (path_info = env['PATH_INFO']) && inhibitions.any?{|i| path_info[i] }
33
+ inihibit(path_info)
34
+ inhibited = true
35
+ end
36
+
37
+ tuple = @app.call(env)
38
+ tuple = with_inhibition(tuple) if inhibited
39
+
40
+ tuple
22
41
  ensure
23
- reaction.call(limiter, killer) if limiter.check
42
+ reaction.call(limiter, killer) if killer && limiter.check
43
+ end
44
+
45
+ def with_inhibition(tuple)
46
+ # Почему именно each описано в спецификации в разделе The Response
47
+ # https://github.com/rack/rack/blob/main/SPEC.rdoc
48
+ if tuple[2].respond_to?(:each)
49
+ old = tuple[2]
50
+ tuple[2] = Enumerator.new do |y|
51
+ old.each do |chunk|
52
+ y << chunk
53
+ end
54
+ old.close if old.respond_to?(:close)
55
+ release
56
+ end
57
+ else
58
+ release
59
+ end
60
+ tuple
61
+ end
62
+
63
+ def default_kill(l, k)
64
+ k.kill(l.started_at)
65
+ end
66
+
67
+ def inihibit(path_info)
68
+ killer.do_inhibit(path_info)
69
+ rescue StandardError => e
70
+ logger.error "#{self.class}::inhibit error: #{e.inspect}"
71
+ end
72
+
73
+ def release
74
+ killer.do_release
75
+ rescue StandardError => e
76
+ logger.error "#{self.class}::release error: #{e.inspect}"
77
+ end
78
+
79
+ def logger
80
+ @logger ||= WorkerKiller.configuration.logger
24
81
  end
25
82
 
26
83
  class RequestsLimiter < ::WorkerKiller::Middleware
@@ -0,0 +1,13 @@
1
+ require 'worker_killer'
2
+ require 'worker_killer/puma_plugin_ng'
3
+
4
+ Puma::Plugin.create do
5
+ def config(cfg)
6
+ ::WorkerKiller::PumaPluginNg.instance.config(cfg)
7
+ end
8
+
9
+ def start(launcher)
10
+ ::WorkerKiller::PumaPluginNg.instance.start(launcher)
11
+ end
12
+ end
13
+
@@ -1,30 +1,68 @@
1
+ require 'delegate'
1
2
  require 'singleton'
3
+ require 'socket'
2
4
 
3
5
  require 'worker_killer/memory_limiter'
4
6
  require 'worker_killer/count_limiter'
5
7
 
6
-
7
8
  module WorkerKiller
8
9
  class PumaPlugin
9
10
 
10
11
  include Singleton
11
12
 
13
+ class PumaLogWrapper < SimpleDelegator
14
+
15
+ def info(msg)
16
+ __getobj__.log(msg)
17
+ end
18
+
19
+ def warn(msg)
20
+ __getobj__.log(msg)
21
+ end
22
+
23
+ def debug(msg)
24
+ __getobj__.log("(DEBUG) #{msg}")
25
+ end
26
+
27
+ end
28
+
12
29
  attr_accessor :ipc_path, :killer, :thread
13
30
 
14
31
  def initialize
32
+ @killer = ::WorkerKiller::Killer::Puma.new(worker_num: nil, puma_plugin: self)
33
+ @worker_num = nil
34
+ @debug = false
35
+
15
36
  @ipc_path = File.join('tmp', "puma_worker_killer_#{Process.pid}.socket")
16
- @killer = ::WorkerKiller::Killer::Puma.new(worker_num: nil, ipc_path: ipc_path)
17
- log "Initializing IPC: #{@ipc_path}"
18
37
  end
19
38
 
20
- def config(puma)
21
- puma.on_worker_boot do |num|
22
- log "Set worker_num: #{num}"
39
+ # Этот метод зовётся при ИНИЦИАЛИЗАЦИИ плагина внути master-процесса, в самомо начале
40
+ # тут можно выполнить конфигурацию чегоднибудь нужного
41
+ def config(dsl)
42
+ if %w[t 1].include?(ENV.fetch('WORKER_KILLER_DEBUG', 'false').to_s.downcase[0].to_s)
43
+ @debug = true
44
+ end
45
+
46
+ cb = if dsl.respond_to?(:before_worker_boot)
47
+ :before_worker_boot
48
+ else
49
+ :on_worker_boot
50
+ end
51
+
52
+ dsl.send(cb) do |num|
23
53
  @killer.worker_num = num
54
+ @worker_num = num
55
+ @tag = nil
56
+ log "Set worker_num: #{num}"
24
57
  end
25
58
  end
26
59
 
60
+ # Этот метод зовётся при ИНИЦИАЛИЗАЦИИ плагина внути master-процесса, контролирующего кластер Puma
61
+ # псле форка данные сохранённые тут также доступны (например logger)
27
62
  def start(launcher)
63
+ set_logger!(PumaLogWrapper.new(launcher.log_writer))
64
+
65
+ log "Initializing IPC: #{@ipc_path}"
28
66
  @runner = launcher.instance_variable_get('@runner')
29
67
 
30
68
  launcher.events.on_booted do
@@ -32,10 +70,32 @@ module WorkerKiller
32
70
  end
33
71
  end
34
72
 
73
+ def set_logger!(logger)
74
+ @logger = logger
75
+ end
76
+
77
+ # Этот метод зовётся из Middleware внтури воркера
78
+ def request_restart_server(worker_num)
79
+ log("Equeue worker #{worker_num} for restarting...")
80
+ Socket.unix(ipc_path) do |sock|
81
+ sock.puts Integer(worker_num).to_s
82
+ end
83
+ end
84
+
85
+ # Этот метод зовётся из Middleware внтури воркера
86
+ def inhibit_restart(_worker_num)
87
+ nil
88
+ end
89
+
90
+ # Этот метод зовётся из Middleware внтури воркера
91
+ def release_restart(_worker_num)
92
+ nil
93
+ end
94
+
35
95
  def start_ipc_listener
36
- log 'Start IPC listener'
96
+ log "Start IPC listener on #{@ipc_path}"
37
97
  Thread.new do
38
- Socket.unix_server_loop(ipc_path) do |sock, *args|
98
+ Socket.unix_server_loop(ipc_path) do |sock, *_args|
39
99
  if (line = sock.gets)
40
100
  worker_num = Integer(line.strip)
41
101
  if (worker = find_worker(worker_num))
@@ -72,7 +132,25 @@ module WorkerKiller
72
132
  end
73
133
 
74
134
  def log(msg)
75
- warn("#{self.class}[#{Process.pid}]: #{msg}")
135
+ if @logger
136
+ @logger.warn("#{tag} #{msg}")
137
+ else
138
+ warn("[#{Process.pid}] #{tag} #{msg}")
139
+ end
140
+ end
141
+
142
+ def tag
143
+ @tag ||= "[#{self.class}] #{@worker_num.nil? ? '[M]' : "[W#{@worker_num}]"}"
144
+ end
145
+
146
+ def debug(msg)
147
+ return unless @debug
148
+
149
+ if @logger
150
+ @logger.debug("#{tag} #{msg}")
151
+ else
152
+ warn("[#{Process.pid}] #{tag} (DEBUG) #{msg}")
153
+ end
76
154
  end
77
155
 
78
156
  end
@@ -0,0 +1,140 @@
1
+ require 'delegate'
2
+ require 'singleton'
3
+
4
+ require 'worker_killer/memory_limiter'
5
+ require 'worker_killer/count_limiter'
6
+
7
+
8
+ module WorkerKiller
9
+ class PumaPluginNg
10
+
11
+ include Singleton
12
+
13
+ class PumaLogWrapper < SimpleDelegator
14
+
15
+ def info(msg)
16
+ __getobj__.log(msg)
17
+ end
18
+
19
+ def warn(msg)
20
+ __getobj__.log(msg)
21
+ end
22
+
23
+ def debug(msg)
24
+ __getobj__.log("(DEBUG) #{msg}")
25
+ end
26
+
27
+ end
28
+
29
+ attr_accessor :killer, :puma_server, :inhibited, :kill_queue
30
+
31
+ def initialize
32
+ @killer = ::WorkerKiller::Killer::Puma.new(worker_num: nil, puma_plugin: self)
33
+ @worker_num = nil
34
+ @debug = false
35
+
36
+ @puma_server = nil
37
+
38
+ @force_restart = false
39
+ @inhibited ||= Hash.new {|h, k| h[k] = 0 }
40
+ @kill_queue ||= Set.new
41
+ end
42
+
43
+ # Этот метод зовётся при ИНИЦИАЛИЗАЦИИ плагина внути master-процесса, в самомо начале
44
+ # тут можно выполнить конфигурацию чегоднибудь нужного
45
+ def config(dsl)
46
+ if %w[t 1].include?(ENV.fetch('WORKER_KILLER_DEBUG', 'false').to_s.downcase[0].to_s)
47
+ @debug = true
48
+ end
49
+
50
+ dsl.before_worker_boot do |num|
51
+ @killer.worker_num = num
52
+ @worker_num = num
53
+ @tag = nil
54
+ log "Set worker_num: #{num}"
55
+ end
56
+
57
+ dsl.out_of_band do
58
+ do_kill('OOB') unless @force_restart
59
+ end
60
+ end
61
+
62
+ # Этот метод зовётся при ИНИЦИАЛИЗАЦИИ плагина внути master-процесса, контролирующего кластер Puma
63
+ # псле форка данные сохранённые тут также доступны (например logger)
64
+ def start(launcher)
65
+ set_logger!(PumaLogWrapper.new(launcher.log_writer))
66
+ end
67
+
68
+ def set_logger!(logger)
69
+ @logger = logger
70
+ end
71
+
72
+ # Завершать процесс сразу после окончания inhibited метода. Иначе завершение будет происзодть в Out Of Band методе
73
+ def force_restart!(force = true)
74
+ @force_restart = force
75
+ end
76
+
77
+ # Этот метод зовётся из Middleware внтури воркера
78
+ def request_restart_server(worker_num)
79
+ return if @worker_num != worker_num
80
+
81
+ log("Equeue worker #{worker_num} for restarting...")
82
+ kill_queue << worker_num
83
+ end
84
+
85
+ # Этот метод зовётся из Middleware внтури воркера
86
+ def inhibit_restart(worker_num)
87
+ return if @worker_num != worker_num
88
+
89
+ cnt = inhibited[worker_num] += 1 # just increase inhibit counter
90
+ log("Worker inhibition increased: #{cnt}")
91
+ end
92
+
93
+ # Этот метод зовётся из Middleware внтури воркера
94
+ def release_restart(worker_num)
95
+ return if @worker_num != worker_num
96
+
97
+ cnt = inhibited[worker_num] -= 1 # just decrease inhibit counter
98
+ log("Worker inhibition decreased: #{cnt}")
99
+ return unless cnt <= 0
100
+
101
+ inhibited.delete(worker_num)
102
+ log('Worker released')
103
+ do_kill('RELEASE') if @force_restart
104
+ end
105
+
106
+ def do_kill(name)
107
+ return if kill_queue.empty?
108
+
109
+ log "Killing workers by #{name}: #{kill_queue}"
110
+ (kill_queue - inhibited.keys).each do |worker_num|
111
+ kill_queue.delete(worker_num)
112
+ Thread.current.puma_server.begin_restart
113
+ end
114
+ end
115
+
116
+ def log(msg)
117
+ if @logger
118
+ @logger.warn("#{tag} #{msg}")
119
+ else
120
+ warn("[#{Process.pid}] #{tag} #{msg}")
121
+ end
122
+ end
123
+
124
+ def tag
125
+ @tag ||= "[#{self.class}] #{@worker_num.nil? ? '[M]' : "[W#{@worker_num}]"}"
126
+ end
127
+
128
+ def debug(msg)
129
+ return unless @debug
130
+
131
+ if @logger
132
+ @logger.debug("#{tag} #{msg}")
133
+ else
134
+ warn("[#{Process.pid}] #{tag} (DEBUG) #{msg}")
135
+ end
136
+ end
137
+
138
+ end
139
+ end
140
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  module WorkerKiller
4
4
 
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
 
7
7
  end
8
8
 
@@ -1,34 +1,29 @@
1
1
  RSpec.describe WorkerKiller::Killer::Puma do
2
- let(:config) do
3
- WorkerKiller::Configuration.new.tap do |c|
4
- c.quit_attempts = 2
5
- c.term_attempts = 2
2
+ let(:puma_plugin) { double(set_logger!: nil) }
3
+ let(:killer){ described_class.new(puma_plugin: puma_plugin, worker_num: 99) }
4
+
5
+ describe '#kill' do
6
+ it do
7
+ expect(puma_plugin).to receive(:request_restart_server)
8
+
9
+ killer.kill(Time.now)
6
10
  end
7
11
  end
8
12
 
9
- let(:ipc_path) { '/tmp/test_ipx.sock' }
10
- let(:killer){ described_class.new(ipc_path: ipc_path, worker_num: 99) }
11
- let(:buffer) { StringIO.new }
13
+ describe '#do_inhibit' do
14
+ it do
15
+ expect(puma_plugin).to receive(:inhibit_restart)
12
16
 
13
- describe '#kill' do
14
- around do |example|
15
- prev = WorkerKiller.configuration
16
- WorkerKiller.configuration = config
17
- example.run
18
- ensure
19
- WorkerKiller.configuration = prev
17
+ killer.do_inhibit('something')
20
18
  end
19
+ end
21
20
 
22
- it 'expect right signal order' do
23
- expect(Socket).to receive(:unix).with(ipc_path).and_yield(buffer).exactly(3).times
24
- expect(Process).not_to receive(:kill)
21
+ describe '#do_release' do
22
+ it do
23
+ expect(puma_plugin).to receive(:release_restart)
25
24
 
26
- killer.kill(Time.now)
27
- expect(buffer.string.strip).to eq(99.to_s)
28
25
 
29
- 1.times { killer.kill(Time.now) } # 1 QUIT
30
- 2.times { killer.kill(Time.now) } # 1 TERM
31
- 5.times { killer.kill(Time.now) } # 5 KILL
26
+ killer.do_release
32
27
  end
33
28
  end
34
29
  end
@@ -2,13 +2,17 @@ require 'securerandom'
2
2
 
3
3
  RSpec.describe WorkerKiller::Middleware do
4
4
  let(:app){ double(call: {}) }
5
- let(:killer) { double }
5
+ let(:killer) { double(WorkerKiller::Killer::Base, do_inhibit: true) }
6
6
  let(:reaction){ double }
7
7
  let(:anykey){ SecureRandom.hex(8) }
8
+ let(:inhibitions) { [%r{/attachments}] }
9
+ let(:env) { { 'PATH_INFO' => '/something' } }
8
10
 
9
11
  describe 'Custom class' do
10
12
  let(:klass){ double }
11
- let(:options){ { killer: killer, klass: klass, reaction: reaction, anykey: anykey } }
13
+ let(:options) do
14
+ { killer: killer, klass: klass, reaction: reaction, anykey: anykey, inhibitions: inhibitions }
15
+ end
12
16
  subject{ described_class.new(app, **options) }
13
17
 
14
18
  it 'is expected to be initialized' do
@@ -19,7 +23,7 @@ RSpec.describe WorkerKiller::Middleware do
19
23
  end
20
24
 
21
25
  describe WorkerKiller::Middleware::RequestsLimiter do
22
- let(:options){ { killer: killer, min: 3, max: 3 } }
26
+ let(:options){ { killer: killer, min: 3, max: 3, inhibitions: inhibitions } }
23
27
  subject{ described_class.new(app, **options) }
24
28
 
25
29
  it 'is expected to be initialized without reaction' do
@@ -30,7 +34,25 @@ RSpec.describe WorkerKiller::Middleware do
30
34
  expect(killer).to receive(:kill).with(Time).twice
31
35
 
32
36
  4.times do
33
- subject.call({})
37
+ subject.call(env)
38
+ end
39
+ end
40
+
41
+
42
+ describe 'inhibitions' do
43
+ let(:env) { { 'PATH_INFO' => '/attachments' } }
44
+
45
+ it 'is expected to use inhibitions' do
46
+ expect(WorkerKiller::CountLimiter).to receive(:new).with(min: 3, max: 3).and_call_original
47
+ expect(subject.limiter).to be_an(WorkerKiller::CountLimiter)
48
+ expect(subject.limiter.min).to eq(3)
49
+ expect(subject.limiter.max).to eq(3)
50
+ expect(killer).to receive(:kill).with(Time).twice
51
+ expect(killer).to receive(:do_release).exactly(4).times
52
+
53
+ 4.times do
54
+ subject.call(env)
55
+ end
34
56
  end
35
57
  end
36
58
 
@@ -44,36 +66,38 @@ RSpec.describe WorkerKiller::Middleware do
44
66
  expect(reaction).to receive(:call).with(subject.limiter, killer).twice
45
67
 
46
68
  4.times do
47
- subject.call({})
69
+ subject.call(env)
48
70
  end
49
71
  end
50
72
  end
51
73
 
52
74
  describe WorkerKiller::Middleware::OOMLimiter do
53
- let(:options){ { killer: killer, min: 2222, max: 2223 } }
75
+ let(:options){ { killer: killer, min: 2222, max: 2223, inhibitions: inhibitions } }
54
76
  subject{ described_class.new(app, **options) }
55
77
 
56
78
  it 'is expected to be initialized without reaction' do
57
- expect(WorkerKiller::MemoryLimiter).to receive(:new).with(min: 2222, max: 2223).and_call_original
79
+ expect(WorkerKiller::MemoryLimiter).to receive(:new).with(min: 2222,
80
+ max: 2223).and_call_original
58
81
  expect(subject.limiter).to be_an(WorkerKiller::MemoryLimiter)
59
82
  expect(subject.limiter.min).to eq(2222)
60
83
  expect(subject.limiter.max).to eq(2223)
61
84
  expect(killer).to receive(:kill).with(subject.limiter.started_at).once
62
85
  expect(subject.limiter).to receive(:check).and_return(true)
63
86
 
64
- subject.call({})
87
+ subject.call(env)
65
88
  end
66
89
 
67
90
  it 'is expected to be initialized with reaction' do
68
91
  options[:reaction] = reaction
69
- expect(WorkerKiller::MemoryLimiter).to receive(:new).with(min: 2222, max: 2223).and_call_original
92
+ expect(WorkerKiller::MemoryLimiter).to receive(:new).with(min: 2222,
93
+ max: 2223).and_call_original
70
94
  expect(subject.limiter).to be_an(WorkerKiller::MemoryLimiter)
71
95
  expect(subject.limiter.min).to eq(2222)
72
96
  expect(subject.limiter.max).to eq(2223)
73
97
  expect(reaction).to receive(:call).with(subject.limiter, killer)
74
98
  expect(subject.limiter).to receive(:check).and_return(true)
75
99
 
76
- subject.call({})
100
+ subject.call(env)
77
101
  end
78
102
  end
79
103
  end
@@ -0,0 +1,65 @@
1
+ RSpec.describe WorkerKiller::PumaPluginNg do
2
+ subject(:plugin){ described_class.send(:new) }
3
+ let(:dsl) { double }
4
+ let(:runner){ double }
5
+ let(:events) { double }
6
+ let(:launcher){ double('@runner' => runner, 'events' => events, log_writer: double(log: nil)) }
7
+ let(:worker){ double('booted?' => true, 'term?' => false) }
8
+
9
+ it {
10
+ is_expected.to have_attributes(killer: ::WorkerKiller::Killer::Puma,
11
+ inhibited: Hash,
12
+ kill_queue: Set)
13
+ }
14
+
15
+ context 'Puma initialization' do
16
+ it 'expected to register CB' do
17
+ expect(dsl).to receive(:before_worker_boot).and_yield(123)
18
+ expect(dsl).to receive(:out_of_band).and_yield
19
+ plugin.config(dsl)
20
+ end
21
+
22
+ it do
23
+ expect(plugin).to receive(:set_logger!).and_call_original
24
+ plugin.start(launcher)
25
+ end
26
+ end
27
+
28
+ context '#request_restart_server' do
29
+ it do
30
+ plugin.instance_variable_set('@worker_num', 99)
31
+ expect { plugin.request_restart_server(99) }.to change { plugin.kill_queue }.to([99])
32
+ end
33
+ end
34
+
35
+ context '#inhibit_restart' do
36
+ it do
37
+ plugin.instance_variable_set('@worker_num', 99)
38
+ expect { plugin.inhibit_restart(99) }.to change { plugin.inhibited }.from({}).to({ 99 => 1 })
39
+ end
40
+ end
41
+
42
+ context '#release_restart' do
43
+ it do
44
+ plugin.instance_variable_set('@worker_num', 99)
45
+ plugin.inhibit_restart(99)
46
+
47
+ expect { plugin.release_restart(99) }.to change { plugin.inhibited }.from({ 99 => 1 }).to({})
48
+ end
49
+ end
50
+
51
+ context '#do_kill' do
52
+ it do
53
+ Thread.attr_accessor(:puma_server)
54
+
55
+ plugin.instance_variable_set('@worker_num', 99)
56
+ plugin.request_restart_server(99)
57
+ puma_server = double()
58
+ Thread.current.puma_server = puma_server
59
+ expect(puma_server).to receive(:begin_restart)
60
+
61
+ plugin.do_kill('test')
62
+ end
63
+ end
64
+ end
65
+
@@ -1,9 +1,9 @@
1
1
  RSpec.describe WorkerKiller::PumaPlugin do
2
2
  subject(:plugin){ described_class.instance }
3
- let(:puma) { double }
3
+ let(:dsl) { double }
4
4
  let(:runner){ double }
5
5
  let(:events) { double }
6
- let(:launcher){ double('@runner' => runner, 'events' => events) }
6
+ let(:launcher){ double('@runner' => runner, 'events' => events, log_writer: double(log: nil)) }
7
7
  let(:worker){ double('booted?' => true, 'term?' => false) }
8
8
 
9
9
  it {
@@ -11,10 +11,17 @@ RSpec.describe WorkerKiller::PumaPlugin do
11
11
  killer: ::WorkerKiller::Killer::Puma)
12
12
  }
13
13
 
14
+
14
15
  context 'Puma initialization' do
16
+ it 'expected to register CB' do
17
+ expect(dsl).to receive(:before_worker_boot).and_yield(123)
18
+ plugin.config(dsl)
19
+ end
20
+
15
21
  it do
16
- expect(puma).to receive(:on_worker_boot)
17
- plugin.config(puma)
22
+ expect(plugin).to receive(:set_logger!).and_call_original
23
+ expect(events).to receive(:on_booted)
24
+ plugin.start(launcher)
18
25
  end
19
26
 
20
27
  it do
@@ -30,5 +37,40 @@ RSpec.describe WorkerKiller::PumaPlugin do
30
37
  plugin.thread.join
31
38
  end
32
39
  end
40
+
41
+ context 'expect right signal order' do
42
+ let(:config) do
43
+ WorkerKiller::Configuration.new.tap do |c|
44
+ c.quit_attempts = 2
45
+ c.term_attempts = 2
46
+ end
47
+ end
48
+
49
+ let(:killer){ ::WorkerKiller::Killer::Puma.new(puma_plugin: plugin, worker_num: 99) }
50
+ let(:buffer) { StringIO.new }
51
+
52
+ around do |example|
53
+ prev = WorkerKiller.configuration
54
+ WorkerKiller.configuration = config
55
+ example.run
56
+ ensure
57
+ WorkerKiller.configuration = prev
58
+ end
59
+
60
+
61
+ it 'expect right signal order' do
62
+ expect(Socket).to receive(:unix).with(/puma_worker_.*socket/).and_yield(buffer).exactly(3).times
63
+ expect(Process).not_to receive(:kill)
64
+
65
+ killer.kill(Time.now)
66
+ expect(buffer.string.strip).to eq(99.to_s)
67
+
68
+ 1.times { killer.kill(Time.now) } # 1 QUIT
69
+ 2.times { killer.kill(Time.now) } # 1 TERM
70
+ 5.times { killer.kill(Time.now) } # 5 KILL
71
+ end
72
+ end
73
+
74
+
33
75
  end
34
76
 
data/spec/spec_helper.rb CHANGED
@@ -31,6 +31,7 @@ SimpleCov.start
31
31
  require 'worker_killer'
32
32
  require 'worker_killer/delayed_job_plugin'
33
33
  require 'worker_killer/puma_plugin'
34
+ require 'worker_killer/puma_plugin_ng'
34
35
 
35
36
  $root = File.join(File.dirname(__dir__), 'spec')
36
37
  Dir[File.join(__dir__, 'support', '**', '*.rb')].sort.each {|f| require f }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: worker_killer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.223443
4
+ version: 1.2.0.338611
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samoilenko Yuri
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-02 00:00:00.000000000 Z
11
+ date: 2025-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: get_process_mem
@@ -136,7 +136,9 @@ files:
136
136
  - lib/worker_killer/memory_limiter.rb
137
137
  - lib/worker_killer/middleware.rb
138
138
  - lib/worker_killer/puma/plugin/worker_killer.rb
139
+ - lib/worker_killer/puma/plugin/worker_killer_ng.rb
139
140
  - lib/worker_killer/puma_plugin.rb
141
+ - lib/worker_killer/puma_plugin_ng.rb
140
142
  - lib/worker_killer/version.rb
141
143
  - spec/count_limiter_spec.rb
142
144
  - spec/delayed_job_plugin/jobs_limiter_spec.rb
@@ -148,6 +150,7 @@ files:
148
150
  - spec/killer_spec.rb
149
151
  - spec/memory_limiter_spec.rb
150
152
  - spec/middleware_spec.rb
153
+ - spec/puma_plugin_ng_spec.rb
151
154
  - spec/puma_plugin_spec.rb
152
155
  - spec/spec_helper.rb
153
156
  - spec/support/logger.rb
@@ -156,7 +159,7 @@ homepage: https://github.com/RnD-Soft/worker_killer
156
159
  licenses:
157
160
  - MIT
158
161
  metadata: {}
159
- post_install_message:
162
+ post_install_message:
160
163
  rdoc_options: []
161
164
  require_paths:
162
165
  - lib
@@ -171,22 +174,23 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
174
  - !ruby/object:Gem::Version
172
175
  version: '0'
173
176
  requirements: []
174
- rubygems_version: 3.0.3
175
- signing_key:
177
+ rubygems_version: 3.2.33
178
+ signing_key:
176
179
  specification_version: 4
177
180
  summary: Kill any workers by memory and request counts or take custom reaction
178
181
  test_files:
179
- - spec/killer/signal_spec.rb
180
- - spec/killer/puma_spec.rb
182
+ - spec/count_limiter_spec.rb
183
+ - spec/delayed_job_plugin/jobs_limiter_spec.rb
184
+ - spec/delayed_job_plugin/oom_limiter_spec.rb
181
185
  - spec/killer/delayed_job_spec.rb
182
186
  - spec/killer/passenger_spec.rb
183
- - spec/puma_plugin_spec.rb
187
+ - spec/killer/puma_spec.rb
188
+ - spec/killer/signal_spec.rb
184
189
  - spec/killer_spec.rb
185
- - spec/count_limiter_spec.rb
186
- - spec/worker_killer_spec.rb
187
- - spec/support/logger.rb
190
+ - spec/memory_limiter_spec.rb
188
191
  - spec/middleware_spec.rb
192
+ - spec/puma_plugin_ng_spec.rb
193
+ - spec/puma_plugin_spec.rb
189
194
  - spec/spec_helper.rb
190
- - spec/delayed_job_plugin/oom_limiter_spec.rb
191
- - spec/delayed_job_plugin/jobs_limiter_spec.rb
192
- - spec/memory_limiter_spec.rb
195
+ - spec/support/logger.rb
196
+ - spec/worker_killer_spec.rb