process_bot 0.1.21 → 0.1.22
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 +4 -4
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +16 -0
- data/lib/process_bot/capistrano/sidekiq.rake +22 -2
- data/lib/process_bot/capistrano/sidekiq_helpers.rb +1 -0
- data/lib/process_bot/control_socket.rb +1 -1
- data/lib/process_bot/process/handlers/sidekiq.rb +4 -4
- data/lib/process_bot/process/runner_instance.rb +28 -0
- data/lib/process_bot/process.rb +118 -16
- data/lib/process_bot/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 393279b999e338c87046703fca4e6e1c22adc46ffe085a95bef758df831d3a47
|
|
4
|
+
data.tar.gz: 852bd1ad1529685294bcc54be632c444f8748490519d41fd32bd963217098ec8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e166f7a08badb085df25e770028432f4a40b5e024f7c4b363426388145e2fbce4c5b91a67e348d504c9c040b24c674ce03e1ba2c5d097d3fffa54a949549e3f
|
|
7
|
+
data.tar.gz: 1fed86f6ca3b6c6dcc579f9cbe44f2d9796b6cdc18b08227a9d03f4a3814b6b806dfa0e154a5f63e17c533496a0720ffb32b336c3e98b3f9cd84958027e97563
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -75,6 +75,22 @@ bundle exec process_bot --command start --log true --log-file-path /var/log/proc
|
|
|
75
75
|
Use `process_bot:sidekiq:graceful` to wait for running jobs, and
|
|
76
76
|
`process_bot:sidekiq:graceful_no_wait` to return immediately while Sidekiq drains.
|
|
77
77
|
|
|
78
|
+
### Overlapping restarts
|
|
79
|
+
|
|
80
|
+
You can restart Sidekiq while the old process drains by enabling overlap on the ProcessBot instance:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
set :sidekiq_restart_overlap, true
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
When enabled, `process_bot:sidekiq:restart` will use the overlap behavior.
|
|
87
|
+
|
|
88
|
+
Or when running ProcessBot directly:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bundle exec process_bot --command restart --sidekiq-restart-overlap true
|
|
92
|
+
```
|
|
93
|
+
|
|
78
94
|
### Capistrano logging
|
|
79
95
|
|
|
80
96
|
ProcessBot logging is enabled by default in the Capistrano integration.
|
|
@@ -13,6 +13,7 @@ namespace :load do
|
|
|
13
13
|
set :sidekiq_processes, 1
|
|
14
14
|
set :sidekiq_options_per_process, nil
|
|
15
15
|
set :sidekiq_user, nil
|
|
16
|
+
set :sidekiq_restart_overlap, nil
|
|
16
17
|
set :process_bot_log, true
|
|
17
18
|
# Rbenv, Chruby, and RVM integration
|
|
18
19
|
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
@@ -118,8 +119,27 @@ namespace :process_bot do
|
|
|
118
119
|
|
|
119
120
|
desc "Restart Sidekiq and ProcessBot"
|
|
120
121
|
task :restart do
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
if fetch(:sidekiq_restart_overlap, nil)
|
|
123
|
+
on roles fetch(:sidekiq_roles) do |role|
|
|
124
|
+
git_plugin.switch_user(role) do
|
|
125
|
+
running_processes = git_plugin.running_process_bot_processes
|
|
126
|
+
|
|
127
|
+
if running_processes.any?
|
|
128
|
+
running_processes.each do |process_bot_process|
|
|
129
|
+
git_plugin.process_bot_command(process_bot_process, :restart)
|
|
130
|
+
end
|
|
131
|
+
else
|
|
132
|
+
fetch(:sidekiq_processes).times do |idx|
|
|
133
|
+
puts "Starting Sidekiq with ProcessBot #{idx}"
|
|
134
|
+
git_plugin.start_sidekiq(idx)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
invoke! "process_bot:sidekiq:stop"
|
|
141
|
+
invoke! "process_bot:sidekiq:start"
|
|
142
|
+
end
|
|
123
143
|
end
|
|
124
144
|
end
|
|
125
145
|
end
|
|
@@ -167,6 +167,7 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
167
167
|
args += ["--sidekiq-queues", Array(fetch(:sidekiq_queue)).join(",")] if fetch(:sidekiq_queue)
|
|
168
168
|
args += ["--sidekiq-config", fetch(:sidekiq_config)] if fetch(:sidekiq_config)
|
|
169
169
|
args += ["--sidekiq-concurrency", fetch(:sidekiq_concurrency)] if fetch(:sidekiq_concurrency)
|
|
170
|
+
args += ["--sidekiq-restart-overlap", fetch(:sidekiq_restart_overlap)] unless fetch(:sidekiq_restart_overlap, nil).nil?
|
|
170
171
|
if (process_options = fetch(:sidekiq_options_per_process))
|
|
171
172
|
args += process_options[idx]
|
|
172
173
|
end
|
|
@@ -84,7 +84,7 @@ class ProcessBot::ControlSocket
|
|
|
84
84
|
command = JSON.parse(data)
|
|
85
85
|
command_type = command.fetch("command")
|
|
86
86
|
|
|
87
|
-
if command_type == "graceful" || command_type == "graceful_no_wait" || command_type == "stop"
|
|
87
|
+
if command_type == "graceful" || command_type == "graceful_no_wait" || command_type == "restart" || command_type == "stop"
|
|
88
88
|
begin
|
|
89
89
|
unless process.accept_control_commands?
|
|
90
90
|
client.puts(JSON.generate(type: "error", message: "ProcessBot is shutting down", backtrace: Thread.current.backtrace))
|
|
@@ -141,8 +141,8 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
141
141
|
command
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
def graceful(**_args)
|
|
145
|
-
process.set_stopped
|
|
144
|
+
def graceful(stop_process_bot: true, **_args)
|
|
145
|
+
process.set_stopped if stop_process_bot
|
|
146
146
|
|
|
147
147
|
return unless ensure_current_pid?
|
|
148
148
|
|
|
@@ -152,8 +152,8 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
152
152
|
wait_for_no_jobs_and_stop_sidekiq
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
-
def graceful_no_wait(**_args)
|
|
156
|
-
process.set_stopped
|
|
155
|
+
def graceful_no_wait(stop_process_bot: true, **_args)
|
|
156
|
+
process.set_stopped if stop_process_bot
|
|
157
157
|
|
|
158
158
|
return unless ensure_current_pid?
|
|
159
159
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class ProcessBot::Process::RunnerInstance
|
|
2
|
+
attr_reader :runner, :thread
|
|
3
|
+
|
|
4
|
+
def initialize(runner:, event_queue:, logger:)
|
|
5
|
+
@runner = runner
|
|
6
|
+
@event_queue = event_queue
|
|
7
|
+
@logger = logger
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def start
|
|
11
|
+
@thread = Thread.new do
|
|
12
|
+
runner.run
|
|
13
|
+
event_queue << {type: :stopped, runner_instance: self}
|
|
14
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
|
15
|
+
logger.error e.message
|
|
16
|
+
logger.error e.backtrace
|
|
17
|
+
event_queue << {type: :error, runner_instance: self, error: e}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def running?
|
|
22
|
+
runner.running?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :event_queue, :logger
|
|
28
|
+
end
|
data/lib/process_bot/process.rb
CHANGED
|
@@ -12,6 +12,7 @@ class ProcessBot::Process
|
|
|
12
12
|
|
|
13
13
|
autoload :Handlers, "#{__dir__}/process/handlers"
|
|
14
14
|
autoload :Runner, "#{__dir__}/process/runner"
|
|
15
|
+
autoload :RunnerInstance, "#{__dir__}/process/runner_instance"
|
|
15
16
|
|
|
16
17
|
attr_reader :control_command_monitor, :current_pid, :current_process_title, :options, :port, :stopped
|
|
17
18
|
|
|
@@ -21,6 +22,9 @@ class ProcessBot::Process
|
|
|
21
22
|
@accept_control_commands = true
|
|
22
23
|
@control_command_monitor = Monitor.new
|
|
23
24
|
@control_commands_in_flight = 0
|
|
25
|
+
@runner_events = Queue.new
|
|
26
|
+
@runner_instances = []
|
|
27
|
+
@runner_monitor = Monitor.new
|
|
24
28
|
|
|
25
29
|
options.events.connect(:on_process_started, &method(:on_process_started)) # rubocop:disable Performance/MethodObjectAsBlock
|
|
26
30
|
options.events.connect(:on_socket_opened, &method(:on_socket_opened)) # rubocop:disable Performance/MethodObjectAsBlock
|
|
@@ -34,7 +38,7 @@ class ProcessBot::Process
|
|
|
34
38
|
if command == "start"
|
|
35
39
|
logger.logs "Starting process"
|
|
36
40
|
start
|
|
37
|
-
elsif command == "graceful" || command == "graceful_no_wait" || command == "stop"
|
|
41
|
+
elsif command == "graceful" || command == "graceful_no_wait" || command == "restart" || command == "stop"
|
|
38
42
|
send_control_command(command)
|
|
39
43
|
else
|
|
40
44
|
raise "Unknown command: #{command}"
|
|
@@ -85,18 +89,32 @@ class ProcessBot::Process
|
|
|
85
89
|
|
|
86
90
|
def start
|
|
87
91
|
start_control_socket
|
|
92
|
+
start_runner_instance
|
|
88
93
|
|
|
89
94
|
loop do
|
|
90
|
-
|
|
95
|
+
runner_event = runner_events.pop
|
|
96
|
+
handle_runner_event(runner_event)
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
next unless stopped && runner_instances.empty?
|
|
99
|
+
|
|
100
|
+
stop_accepting_control_commands
|
|
101
|
+
wait_for_control_commands
|
|
102
|
+
break
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def restart(**args)
|
|
107
|
+
logger.logs "Restart process"
|
|
108
|
+
|
|
109
|
+
if handler_name == "sidekiq"
|
|
110
|
+
if restart_overlap?(args)
|
|
111
|
+
handler_instance.graceful_no_wait(stop_process_bot: false)
|
|
112
|
+
start_runner_instance
|
|
96
113
|
else
|
|
97
|
-
|
|
98
|
-
sleep 1
|
|
114
|
+
handler_instance.graceful(stop_process_bot: false)
|
|
99
115
|
end
|
|
116
|
+
else
|
|
117
|
+
handler_instance.stop
|
|
100
118
|
end
|
|
101
119
|
end
|
|
102
120
|
|
|
@@ -107,7 +125,7 @@ class ProcessBot::Process
|
|
|
107
125
|
end
|
|
108
126
|
|
|
109
127
|
def run
|
|
110
|
-
|
|
128
|
+
start_runner_instance
|
|
111
129
|
end
|
|
112
130
|
|
|
113
131
|
def send_control_command(command, **command_options)
|
|
@@ -119,13 +137,7 @@ class ProcessBot::Process
|
|
|
119
137
|
end
|
|
120
138
|
|
|
121
139
|
def runner
|
|
122
|
-
@runner ||=
|
|
123
|
-
command: handler_instance.start_command,
|
|
124
|
-
handler_name: handler_name,
|
|
125
|
-
handler_instance: handler_instance,
|
|
126
|
-
logger: logger,
|
|
127
|
-
options: options
|
|
128
|
-
)
|
|
140
|
+
current_runner_instance&.runner || @runner ||= build_runner
|
|
129
141
|
end
|
|
130
142
|
|
|
131
143
|
def update_process_title
|
|
@@ -164,4 +176,94 @@ class ProcessBot::Process
|
|
|
164
176
|
@control_commands_in_flight
|
|
165
177
|
end
|
|
166
178
|
end
|
|
179
|
+
|
|
180
|
+
def build_runner
|
|
181
|
+
ProcessBot::Process::Runner.new(
|
|
182
|
+
command: handler_instance.start_command,
|
|
183
|
+
handler_name: handler_name,
|
|
184
|
+
handler_instance: handler_instance,
|
|
185
|
+
logger: logger,
|
|
186
|
+
options: options
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def start_runner_instance
|
|
191
|
+
runner_instance = ProcessBot::Process::RunnerInstance.new(
|
|
192
|
+
runner: build_runner,
|
|
193
|
+
event_queue: runner_events,
|
|
194
|
+
logger: logger
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
track_runner_instance(runner_instance)
|
|
198
|
+
@current_runner_instance = runner_instance
|
|
199
|
+
@runner = runner_instance.runner
|
|
200
|
+
runner_instance.start
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def handle_runner_event(runner_event)
|
|
204
|
+
runner_instance = runner_event.fetch(:runner_instance)
|
|
205
|
+
remove_runner_instance(runner_instance)
|
|
206
|
+
log_runner_event_error(runner_event)
|
|
207
|
+
clear_current_runner(runner_instance)
|
|
208
|
+
restart_runner_if_needed(runner_instance)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def runner_instances
|
|
212
|
+
runner_monitor.synchronize do
|
|
213
|
+
@runner_instances.dup
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def track_runner_instance(runner_instance)
|
|
218
|
+
runner_monitor.synchronize do
|
|
219
|
+
@runner_instances << runner_instance
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def remove_runner_instance(runner_instance)
|
|
224
|
+
runner_monitor.synchronize do
|
|
225
|
+
@runner_instances.delete(runner_instance)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def restart_overlap?(command_options = {})
|
|
230
|
+
value = if command_options.key?(:sidekiq_restart_overlap)
|
|
231
|
+
command_options[:sidekiq_restart_overlap]
|
|
232
|
+
else
|
|
233
|
+
options[:sidekiq_restart_overlap]
|
|
234
|
+
end
|
|
235
|
+
return false if value.nil?
|
|
236
|
+
return value if value == true || value == false
|
|
237
|
+
|
|
238
|
+
normalized = value.to_s.strip.downcase
|
|
239
|
+
return false if normalized == "false" || normalized == "0" || normalized == ""
|
|
240
|
+
|
|
241
|
+
true
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def log_runner_event_error(runner_event)
|
|
245
|
+
return unless runner_event.fetch(:type) == :error
|
|
246
|
+
|
|
247
|
+
logger.error "Process runner crashed: #{runner_event.fetch(:error)}"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def clear_current_runner(runner_instance)
|
|
251
|
+
return unless runner_instance == current_runner_instance
|
|
252
|
+
|
|
253
|
+
@current_runner_instance = nil
|
|
254
|
+
@runner = nil
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def restart_runner_if_needed(runner_instance)
|
|
258
|
+
return if stopped
|
|
259
|
+
return unless runner_instance == current_runner_instance || current_runner_instance.nil?
|
|
260
|
+
|
|
261
|
+
logger.logs "Process stopped - starting again after 1 sec"
|
|
262
|
+
sleep 1
|
|
263
|
+
start_runner_instance
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
attr_reader :current_runner_instance, :runner_events, :runner_monitor
|
|
167
269
|
end
|
data/lib/process_bot/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: process_bot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kaspernj
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: knjrbfw
|
|
@@ -173,6 +173,7 @@ files:
|
|
|
173
173
|
- lib/process_bot/process/handlers/custom.rb
|
|
174
174
|
- lib/process_bot/process/handlers/sidekiq.rb
|
|
175
175
|
- lib/process_bot/process/runner.rb
|
|
176
|
+
- lib/process_bot/process/runner_instance.rb
|
|
176
177
|
- lib/process_bot/version.rb
|
|
177
178
|
- peak_flow.yml
|
|
178
179
|
- process_bot.gemspec
|