process_bot 0.1.14 → 0.1.16
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/Gemfile.lock +1 -1
- data/README.md +2 -1
- data/lib/process_bot/capistrano/sidekiq.rake +49 -0
- data/lib/process_bot/capistrano/sidekiq_helpers.rb +25 -11
- data/lib/process_bot/control_socket.rb +39 -15
- data/lib/process_bot/process/handlers/sidekiq.rb +27 -21
- data/lib/process_bot/process.rb +4 -4
- data/lib/process_bot/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d79a43f8a6bdf408f637e66fff579115ab45f57f0c8c0314d98c1e84a8f4430
|
|
4
|
+
data.tar.gz: 716c9b8e69f54f1dc60a322d1ddad7f4336b464e338964ffa9f736d7cac11477
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2739cfa0148b5085b45f0ca273d583f609413346e7d7ebed3693fb3715ebeb2ad4c14475b212c82a86e3bc1081585f2edc606c0d2f7ae2159a61722cfc1bc0c1
|
|
7
|
+
data.tar.gz: f59153145d7e36da05d9d9dc98d6e7d2f0a3b8f03fcfb51606b800453685f2fcb45e07eca46395cbdeac095f041749eb2064b9f98bc6dfe1c5e7d3fad5611e35
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -44,6 +44,7 @@ ProcessBot provides these Sidekiq tasks:
|
|
|
44
44
|
- `process_bot:sidekiq:stop`
|
|
45
45
|
- `process_bot:sidekiq:graceful` (stops fetching new jobs and waits for running jobs by default)
|
|
46
46
|
- `process_bot:sidekiq:graceful_no_wait` (stops fetching new jobs and returns immediately)
|
|
47
|
+
- `process_bot:sidekiq:ensure_running` (starts missing processes, ignoring ones in graceful shutdown)
|
|
47
48
|
- `process_bot:sidekiq:restart`
|
|
48
49
|
|
|
49
50
|
You can also skip waiting for graceful completion:
|
|
@@ -88,7 +89,7 @@ set :process_bot_log, false
|
|
|
88
89
|
When running ProcessBot directly, you can control graceful waiting and log file output:
|
|
89
90
|
|
|
90
91
|
```bash
|
|
91
|
-
bundle exec process_bot --command
|
|
92
|
+
bundle exec process_bot --command graceful_no_wait
|
|
92
93
|
bundle exec process_bot --command start --log-file-path /var/log/process_bot.log
|
|
93
94
|
```
|
|
94
95
|
|
|
@@ -70,6 +70,55 @@ namespace :process_bot do
|
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
desc "Ensure the configured number of Sidekiq ProcessBots are running (excluding graceful shutdowns)"
|
|
74
|
+
task :ensure_running do
|
|
75
|
+
on roles fetch(:sidekiq_roles) do |role|
|
|
76
|
+
git_plugin.switch_user(role) do
|
|
77
|
+
desired_processes = fetch(:sidekiq_processes).to_i
|
|
78
|
+
running_processes = git_plugin.running_process_bot_processes
|
|
79
|
+
|
|
80
|
+
graceful_processes = running_processes.select do |process_bot_data|
|
|
81
|
+
git_plugin.sidekiq_process_graceful?(process_bot_data)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
active_processes = running_processes - graceful_processes
|
|
85
|
+
|
|
86
|
+
active_indexes = active_processes.filter_map do |process_bot_data|
|
|
87
|
+
git_plugin.process_bot_sidekiq_index(process_bot_data)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
graceful_indexes = graceful_processes.filter_map do |process_bot_data|
|
|
91
|
+
git_plugin.process_bot_sidekiq_index(process_bot_data)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
puts "ProcessBot Sidekiq in graceful shutdown: #{graceful_indexes.join(", ")}" if graceful_indexes.any?
|
|
95
|
+
|
|
96
|
+
desired_indexes = (0...desired_processes).to_a
|
|
97
|
+
missing_indexes = desired_indexes - active_indexes - graceful_indexes
|
|
98
|
+
missing_count = desired_processes - active_indexes.count
|
|
99
|
+
|
|
100
|
+
if missing_count.negative?
|
|
101
|
+
puts "Found #{active_indexes.count} running ProcessBot Sidekiq processes; desired is #{desired_processes}"
|
|
102
|
+
missing_count = 0
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if missing_indexes.any?
|
|
106
|
+
missing_indexes.each do |idx|
|
|
107
|
+
puts "Starting Sidekiq with ProcessBot #{idx} (missing)"
|
|
108
|
+
git_plugin.start_sidekiq(idx)
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
puts "All ProcessBot Sidekiq processes are running (#{active_indexes.count}/#{desired_processes})"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
return unless missing_count > missing_indexes.length
|
|
115
|
+
|
|
116
|
+
puts "Skipped starting #{missing_count - missing_indexes.length} processes because " \
|
|
117
|
+
"they are still in graceful shutdown"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
73
122
|
desc "Restart Sidekiq and ProcessBot"
|
|
74
123
|
task :restart do
|
|
75
124
|
invoke! "process_bot:sidekiq:stop"
|
|
@@ -36,14 +36,11 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
36
36
|
raise "No port in process bot data? #{process_bot_data}" unless process_bot_data["port"]
|
|
37
37
|
|
|
38
38
|
mode = "exec"
|
|
39
|
-
wait_for_gracefully_stopped = process_bot_wait_setting(command)
|
|
40
39
|
|
|
41
40
|
if mode == "runner"
|
|
42
41
|
args = {command: command, port: process_bot_data.fetch("port")}
|
|
43
42
|
args["log"] = fetch(:process_bot_log) unless fetch(:process_bot_log).nil?
|
|
44
43
|
|
|
45
|
-
args["wait_for_gracefully_stopped"] = wait_for_gracefully_stopped unless wait_for_gracefully_stopped.nil?
|
|
46
|
-
|
|
47
44
|
escaped_args = JSON.generate(args).gsub("\"", "\\\"")
|
|
48
45
|
rails_runner_command = "require 'process_bot'; ProcessBot::Process.new(ProcessBot::Options.from_args(#{escaped_args})).execute!"
|
|
49
46
|
|
|
@@ -61,7 +58,6 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
61
58
|
backend_command << "#{key} #{value}"
|
|
62
59
|
end
|
|
63
60
|
|
|
64
|
-
backend_command << " --wait-for-gracefully-stopped #{wait_for_gracefully_stopped}" unless wait_for_gracefully_stopped.nil?
|
|
65
61
|
else
|
|
66
62
|
raise "Unknown mode: #{mode}"
|
|
67
63
|
end
|
|
@@ -69,13 +65,6 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
69
65
|
backend.execute backend_command
|
|
70
66
|
end
|
|
71
67
|
|
|
72
|
-
def process_bot_wait_setting(command)
|
|
73
|
-
return true if command == :graceful
|
|
74
|
-
return false if command == :graceful_no_wait
|
|
75
|
-
|
|
76
|
-
nil
|
|
77
|
-
end
|
|
78
|
-
|
|
79
68
|
def running_process_bot_processes
|
|
80
69
|
sidekiq_app_name = fetch(:sidekiq_app_name, fetch(:application))
|
|
81
70
|
raise "No :sidekiq_app_name was set" unless sidekiq_app_name
|
|
@@ -104,6 +93,31 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
104
93
|
processes
|
|
105
94
|
end
|
|
106
95
|
|
|
96
|
+
def process_bot_sidekiq_index(process_bot_data)
|
|
97
|
+
process_bot_id = process_bot_data["id"].to_s
|
|
98
|
+
match = process_bot_id.match(/-(\d+)\z/)
|
|
99
|
+
return nil unless match
|
|
100
|
+
|
|
101
|
+
match[1].to_i
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def sidekiq_command_graceful?(command)
|
|
105
|
+
return false unless command
|
|
106
|
+
|
|
107
|
+
normalized_command = command.to_s.downcase
|
|
108
|
+
normalized_command.include?("stopping") || normalized_command.include?("quiet")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def sidekiq_process_graceful?(process_bot_data)
|
|
112
|
+
sidekiq_pid = process_bot_data["pid"]
|
|
113
|
+
return false unless sidekiq_pid
|
|
114
|
+
|
|
115
|
+
command = backend.capture(:ps, "-o", "command=", "-p", sidekiq_pid.to_s).strip
|
|
116
|
+
sidekiq_command_graceful?(command)
|
|
117
|
+
rescue SSHKit::Command::Failed
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
107
121
|
def sidekiq_user(role = nil)
|
|
108
122
|
if role.nil?
|
|
109
123
|
fetch(:sidekiq_user)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require "socket"
|
|
2
|
+
require "json"
|
|
3
|
+
require "knjrbfw"
|
|
2
4
|
|
|
3
5
|
class ProcessBot::ControlSocket
|
|
4
6
|
attr_reader :options, :port, :process, :server
|
|
@@ -21,15 +23,25 @@ class ProcessBot::ControlSocket
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def start_tcp_server
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
used_ports = used_process_bot_ports
|
|
27
|
+
attempts = 0
|
|
28
|
+
|
|
29
|
+
loop do
|
|
30
|
+
if used_ports.include?(@port)
|
|
31
|
+
@port += 1
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attempts += 1
|
|
36
|
+
@server = actually_start_tcp_server("localhost", @port)
|
|
37
|
+
break
|
|
38
|
+
rescue Errno::EADDRINUSE, Errno::EADDRNOTAVAIL => e
|
|
39
|
+
if attempts <= 100
|
|
40
|
+
@port += 1
|
|
41
|
+
next
|
|
42
|
+
else
|
|
43
|
+
raise e
|
|
44
|
+
end
|
|
33
45
|
end
|
|
34
46
|
end
|
|
35
47
|
|
|
@@ -100,18 +112,30 @@ class ProcessBot::ControlSocket
|
|
|
100
112
|
end
|
|
101
113
|
|
|
102
114
|
def run_command(command_type, command_options, client)
|
|
103
|
-
command_type, command_options = normalize_command(command_type, command_options)
|
|
104
115
|
logger.logs "Command #{command_type} with options #{command_options}"
|
|
105
116
|
|
|
106
117
|
process.__send__(command_type, **command_options)
|
|
107
118
|
client.puts(JSON.generate(type: "success"))
|
|
108
119
|
end
|
|
109
120
|
|
|
110
|
-
def
|
|
111
|
-
|
|
121
|
+
def used_process_bot_ports
|
|
122
|
+
ports = []
|
|
123
|
+
|
|
124
|
+
Knj::Unix_proc.list("grep" => "ProcessBot") do |process|
|
|
125
|
+
process_command = process.data.fetch("cmd")
|
|
126
|
+
match = process_command.match(/ProcessBot (\{.+\})/)
|
|
127
|
+
next unless match
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
process_data = JSON.parse(match[1])
|
|
131
|
+
rescue JSON::ParserError
|
|
132
|
+
next
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
port = process_data["port"]
|
|
136
|
+
ports << port.to_i if port
|
|
137
|
+
end
|
|
112
138
|
|
|
113
|
-
|
|
114
|
-
command_options[:wait_for_gracefully_stopped] = false
|
|
115
|
-
[command_type, command_options]
|
|
139
|
+
ports.uniq
|
|
116
140
|
end
|
|
117
141
|
end
|
|
@@ -21,10 +21,6 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
21
21
|
Process.detach(pid) if pid
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def false_value?(value)
|
|
25
|
-
!value || value == "false"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
24
|
def process_running?(pid)
|
|
29
25
|
return false unless pid
|
|
30
26
|
|
|
@@ -121,20 +117,6 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
121
117
|
end
|
|
122
118
|
end
|
|
123
119
|
|
|
124
|
-
def handle_graceful_wait(wait_for_gracefully_stopped)
|
|
125
|
-
if false_value?(wait_for_gracefully_stopped)
|
|
126
|
-
logger.logs "Dont wait for gracefully stopped - doing that in fork..."
|
|
127
|
-
|
|
128
|
-
daemonize do
|
|
129
|
-
wait_for_no_jobs_and_stop_sidekiq
|
|
130
|
-
exit
|
|
131
|
-
end
|
|
132
|
-
else
|
|
133
|
-
logger.logs "Wait for gracefully stopped..."
|
|
134
|
-
wait_for_no_jobs_and_stop_sidekiq
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
120
|
def start_command # rubocop:disable Metrics/AbcSize
|
|
139
121
|
args = []
|
|
140
122
|
|
|
@@ -159,15 +141,29 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
159
141
|
command
|
|
160
142
|
end
|
|
161
143
|
|
|
162
|
-
def graceful(**
|
|
163
|
-
|
|
144
|
+
def graceful(**_args)
|
|
145
|
+
process.set_stopped
|
|
146
|
+
|
|
147
|
+
return unless ensure_current_pid?
|
|
148
|
+
|
|
149
|
+
return unless send_tstp_or_return
|
|
150
|
+
|
|
151
|
+
logger.logs "Wait for gracefully stopped..."
|
|
152
|
+
wait_for_no_jobs_and_stop_sidekiq
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def graceful_no_wait(**_args)
|
|
164
156
|
process.set_stopped
|
|
165
157
|
|
|
166
158
|
return unless ensure_current_pid?
|
|
167
159
|
|
|
168
160
|
return unless send_tstp_or_return
|
|
169
161
|
|
|
170
|
-
|
|
162
|
+
logger.logs "Dont wait for gracefully stopped - doing that in fork..."
|
|
163
|
+
daemonize do
|
|
164
|
+
wait_for_no_jobs_and_stop_sidekiq
|
|
165
|
+
exit
|
|
166
|
+
end
|
|
171
167
|
end
|
|
172
168
|
|
|
173
169
|
def stop(**_args)
|
|
@@ -219,5 +215,15 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
219
215
|
logger.logs "Wait for no jobs and Stop sidekiq"
|
|
220
216
|
wait_for_no_jobs
|
|
221
217
|
stop
|
|
218
|
+
wait_for_sidekiq_exit
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def wait_for_sidekiq_exit
|
|
222
|
+
return unless current_pid
|
|
223
|
+
|
|
224
|
+
while process_running?(current_pid)
|
|
225
|
+
logger.logs "Waiting for Sidekiq PID #{current_pid} to stop"
|
|
226
|
+
sleep 1
|
|
227
|
+
end
|
|
222
228
|
end
|
|
223
229
|
end
|
data/lib/process_bot/process.rb
CHANGED
|
@@ -6,6 +6,7 @@ class ProcessBot::Process
|
|
|
6
6
|
extend Forwardable
|
|
7
7
|
|
|
8
8
|
def_delegator :handler_instance, :graceful
|
|
9
|
+
def_delegator :handler_instance, :graceful_no_wait
|
|
9
10
|
def_delegator :handler_instance, :stop
|
|
10
11
|
|
|
11
12
|
autoload :Handlers, "#{__dir__}/process/handlers"
|
|
@@ -29,10 +30,8 @@ class ProcessBot::Process
|
|
|
29
30
|
if command == "start"
|
|
30
31
|
logger.logs "Starting process"
|
|
31
32
|
start
|
|
32
|
-
elsif command == "graceful" || command == "stop"
|
|
33
|
+
elsif command == "graceful" || command == "graceful_no_wait" || command == "stop"
|
|
33
34
|
send_control_command(command)
|
|
34
|
-
elsif command == "graceful_no_wait"
|
|
35
|
-
send_control_command(command, wait_for_gracefully_stopped: false)
|
|
36
35
|
else
|
|
37
36
|
raise "Unknown command: #{command}"
|
|
38
37
|
end
|
|
@@ -107,7 +106,8 @@ class ProcessBot::Process
|
|
|
107
106
|
|
|
108
107
|
def send_control_command(command, **command_options)
|
|
109
108
|
logger.logs "Sending #{command} command"
|
|
110
|
-
client.send_command(command: command, options: options.options.merge(command_options))
|
|
109
|
+
response = client.send_command(command: command, options: options.options.merge(command_options))
|
|
110
|
+
raise "No response from ProcessBot while sending #{command}" if response == :nil
|
|
111
111
|
rescue Errno::ECONNREFUSED => e
|
|
112
112
|
raise e unless options[:ignore_no_process_bot]
|
|
113
113
|
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.16
|
|
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-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: knjrbfw
|