process_bot 0.1.2 → 0.1.4
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/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/Gemfile +7 -2
- data/Gemfile.lock +63 -30
- data/README.md +19 -9
- data/exe/process_bot +6 -2
- data/lib/process_bot/capistrano/sidekiq.rake +14 -25
- data/lib/process_bot/capistrano/sidekiq.rb +0 -2
- data/lib/process_bot/capistrano/sidekiq_helpers.rb +59 -25
- data/lib/process_bot/capistrano.rb +1 -0
- data/lib/process_bot/client_socket.rb +41 -0
- data/lib/process_bot/control_socket.rb +56 -9
- data/lib/process_bot/logger.rb +23 -7
- data/lib/process_bot/options.rb +67 -2
- data/lib/process_bot/process/handlers/sidekiq.rb +106 -24
- data/lib/process_bot/process/runner.rb +58 -6
- data/lib/process_bot/process.rb +68 -15
- data/lib/process_bot/version.rb +1 -1
- data/lib/process_bot.rb +1 -0
- data/peak_flow.yml +19 -3
- data/process_bot.gemspec +2 -10
- metadata +10 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24914d42c043171014a4dacebea43987204236a2dadb14d2fcc4f8bda936e628
|
|
4
|
+
data.tar.gz: b9248c4189e0d52826728401e940bed7ba46e330fb7f391d587ba00f23b160e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a92b6568aaf9e799f5d318cd63c900185c4f6cacb4578c7f6fc3769933e571b27b7f48f27d7c33a93ce700d5e3990ea53c0f50a5b39c31ae9153130502c770bc
|
|
7
|
+
data.tar.gz: 3eb45c1dd31b202ac80b0c1b7925e60ed682fd5f42210a768459c6b456d84b9a76cc7618b67ead9f576482ded5b9d47501039d51d158a5125bfab258097c43ff
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.7.8
|
data/Gemfile
CHANGED
|
@@ -5,9 +5,14 @@ source "https://rubygems.org"
|
|
|
5
5
|
# Specify your gem's dependencies in process_bot.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
+
gem "pry"
|
|
8
9
|
gem "rake"
|
|
9
10
|
gem "rspec"
|
|
10
|
-
gem "rubocop"
|
|
11
11
|
gem "string-cases"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
group :development do
|
|
14
|
+
gem "rubocop"
|
|
15
|
+
gem "rubocop-performance"
|
|
16
|
+
gem "rubocop-rake"
|
|
17
|
+
gem "rubocop-rspec"
|
|
18
|
+
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,63 +1,96 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
process_bot (0.1.
|
|
4
|
+
process_bot (0.1.4)
|
|
5
|
+
knjrbfw (>= 0.0.116)
|
|
5
6
|
|
|
6
7
|
GEM
|
|
7
8
|
remote: https://rubygems.org/
|
|
8
9
|
specs:
|
|
9
10
|
ast (2.4.2)
|
|
10
11
|
coderay (1.1.3)
|
|
12
|
+
datet (0.0.25)
|
|
11
13
|
diff-lcs (1.5.0)
|
|
12
|
-
|
|
14
|
+
http2 (0.0.36)
|
|
15
|
+
string-cases (~> 0)
|
|
16
|
+
json (2.7.0)
|
|
17
|
+
knjrbfw (0.0.116)
|
|
18
|
+
datet
|
|
19
|
+
http2
|
|
20
|
+
php4r
|
|
21
|
+
ruby_process
|
|
22
|
+
tsafe
|
|
23
|
+
wref (>= 0.0.8)
|
|
24
|
+
language_server-protocol (3.17.0.3)
|
|
13
25
|
method_source (1.0.0)
|
|
14
|
-
parallel (1.
|
|
15
|
-
parser (3.
|
|
26
|
+
parallel (1.23.0)
|
|
27
|
+
parser (3.2.2.4)
|
|
16
28
|
ast (~> 2.4.1)
|
|
17
|
-
|
|
29
|
+
racc
|
|
30
|
+
php4r (0.0.4)
|
|
31
|
+
datet
|
|
32
|
+
http2
|
|
33
|
+
string-strtr
|
|
34
|
+
pry (0.14.2)
|
|
18
35
|
coderay (~> 1.1)
|
|
19
36
|
method_source (~> 1.0)
|
|
37
|
+
racc (1.7.3)
|
|
20
38
|
rainbow (3.1.1)
|
|
21
|
-
rake (13.0
|
|
22
|
-
regexp_parser (2.
|
|
23
|
-
rexml (3.2.
|
|
24
|
-
rspec (3.
|
|
25
|
-
rspec-core (~> 3.
|
|
26
|
-
rspec-expectations (~> 3.
|
|
27
|
-
rspec-mocks (~> 3.
|
|
28
|
-
rspec-core (3.
|
|
29
|
-
rspec-support (~> 3.
|
|
30
|
-
rspec-expectations (3.
|
|
39
|
+
rake (13.1.0)
|
|
40
|
+
regexp_parser (2.8.2)
|
|
41
|
+
rexml (3.2.6)
|
|
42
|
+
rspec (3.12.0)
|
|
43
|
+
rspec-core (~> 3.12.0)
|
|
44
|
+
rspec-expectations (~> 3.12.0)
|
|
45
|
+
rspec-mocks (~> 3.12.0)
|
|
46
|
+
rspec-core (3.12.0)
|
|
47
|
+
rspec-support (~> 3.12.0)
|
|
48
|
+
rspec-expectations (3.12.0)
|
|
31
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
32
|
-
rspec-support (~> 3.
|
|
33
|
-
rspec-mocks (3.
|
|
50
|
+
rspec-support (~> 3.12.0)
|
|
51
|
+
rspec-mocks (3.12.0)
|
|
34
52
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
35
|
-
rspec-support (~> 3.
|
|
36
|
-
rspec-support (3.
|
|
37
|
-
rubocop (1.
|
|
53
|
+
rspec-support (~> 3.12.0)
|
|
54
|
+
rspec-support (3.12.0)
|
|
55
|
+
rubocop (1.58.0)
|
|
38
56
|
json (~> 2.3)
|
|
57
|
+
language_server-protocol (>= 3.17.0)
|
|
39
58
|
parallel (~> 1.10)
|
|
40
|
-
parser (>= 3.
|
|
59
|
+
parser (>= 3.2.2.4)
|
|
41
60
|
rainbow (>= 2.2.2, < 4.0)
|
|
42
61
|
regexp_parser (>= 1.8, < 3.0)
|
|
43
62
|
rexml (>= 3.2.5, < 4.0)
|
|
44
|
-
rubocop-ast (>= 1.
|
|
63
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
|
45
64
|
ruby-progressbar (~> 1.7)
|
|
46
|
-
unicode-display_width (>=
|
|
47
|
-
rubocop-ast (1.
|
|
48
|
-
parser (>= 3.
|
|
49
|
-
rubocop-
|
|
65
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
66
|
+
rubocop-ast (1.30.0)
|
|
67
|
+
parser (>= 3.2.1.0)
|
|
68
|
+
rubocop-capybara (2.19.0)
|
|
69
|
+
rubocop (~> 1.41)
|
|
70
|
+
rubocop-factory_bot (2.24.0)
|
|
71
|
+
rubocop (~> 1.33)
|
|
72
|
+
rubocop-performance (1.19.1)
|
|
50
73
|
rubocop (>= 1.7.0, < 2.0)
|
|
51
74
|
rubocop-ast (>= 0.4.0)
|
|
52
75
|
rubocop-rake (0.6.0)
|
|
53
76
|
rubocop (~> 1.0)
|
|
54
|
-
rubocop-rspec (2.
|
|
55
|
-
rubocop (~> 1.
|
|
56
|
-
|
|
77
|
+
rubocop-rspec (2.25.0)
|
|
78
|
+
rubocop (~> 1.40)
|
|
79
|
+
rubocop-capybara (~> 2.17)
|
|
80
|
+
rubocop-factory_bot (~> 2.22)
|
|
81
|
+
ruby-progressbar (1.13.0)
|
|
82
|
+
ruby_process (0.0.13)
|
|
83
|
+
string-cases
|
|
84
|
+
tsafe
|
|
85
|
+
wref
|
|
57
86
|
string-cases (0.0.4)
|
|
58
|
-
|
|
87
|
+
string-strtr (0.0.3)
|
|
88
|
+
tsafe (0.0.12)
|
|
89
|
+
unicode-display_width (2.5.0)
|
|
90
|
+
wref (0.0.8)
|
|
59
91
|
|
|
60
92
|
PLATFORMS
|
|
93
|
+
ruby
|
|
61
94
|
x86_64-linux
|
|
62
95
|
|
|
63
96
|
DEPENDENCIES
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ProcessBot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run your app through ProcessBot for automatic restart if crashing, but still support normal deployment through Capistrano.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
In the future ProcessBot will also watch memory usage and restart processes if leaking memory automatically and gracefully.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -12,17 +12,27 @@ Add this line to your application's Gemfile:
|
|
|
12
12
|
gem 'process_bot'
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
Add to your `Capfile`:
|
|
16
|
+
```ruby
|
|
17
|
+
require "process_bot"
|
|
18
|
+
install_plugin ProcessBot::Capistrano::Sidekiq
|
|
19
|
+
install_plugin ProcessBot::Capistrano::Puma
|
|
20
|
+
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
Add to your `deploy.rb`:
|
|
23
|
+
```ruby
|
|
24
|
+
after "deploy:starting", "process_bot:sidekiq:graceful"
|
|
25
|
+
after "deploy:published", "process_bot:sidekiq:start"
|
|
26
|
+
after "deploy:failed", "process_bot:sidekiq:start"
|
|
27
|
+
```
|
|
22
28
|
|
|
23
29
|
## Usage
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
Run commands in the command line like this:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cap production process_bot:sidekiq:graceful
|
|
35
|
+
```
|
|
26
36
|
|
|
27
37
|
## Development
|
|
28
38
|
|
data/exe/process_bot
CHANGED
|
@@ -18,6 +18,12 @@ while argv_i < ARGV.length
|
|
|
18
18
|
|
|
19
19
|
if (match = arg.match(/\A--(.+)\Z/))
|
|
20
20
|
key = match[1].tr("-", "_").to_sym
|
|
21
|
+
|
|
22
|
+
if key == :path
|
|
23
|
+
puts "Path: #{File.realpath("#{__dir__}/..")}"
|
|
24
|
+
exit
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
argv_i += 1
|
|
22
28
|
value = ARGV.fetch(argv_i)
|
|
23
29
|
|
|
@@ -29,8 +35,6 @@ while argv_i < ARGV.length
|
|
|
29
35
|
argv_i += 1
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
pp options.options
|
|
33
|
-
|
|
34
38
|
ProcessBot::Process
|
|
35
39
|
.new(options)
|
|
36
40
|
.execute!
|
|
@@ -14,62 +14,51 @@ namespace :load do
|
|
|
14
14
|
set :sidekiq_options_per_process, nil
|
|
15
15
|
set :sidekiq_user, nil
|
|
16
16
|
# Rbenv, Chruby, and RVM integration
|
|
17
|
-
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a
|
|
18
|
-
set :rvm_map_bins, fetch(:rvm_map_bins).to_a
|
|
19
|
-
set :chruby_map_bins, fetch(:chruby_map_bins).to_a
|
|
17
|
+
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
18
|
+
set :rvm_map_bins, fetch(:rvm_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
19
|
+
set :chruby_map_bins, fetch(:chruby_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
20
20
|
# Bundler integration
|
|
21
|
-
set :bundle_bins, fetch(:bundle_bins).to_a
|
|
21
|
+
set :bundle_bins, fetch(:bundle_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
namespace :process_bot do
|
|
26
26
|
namespace :sidekiq do
|
|
27
|
-
desc "
|
|
28
|
-
task :
|
|
27
|
+
desc "Stop Sidekiq and ProcessBot gracefully (stop fetching new tasks from Redis and then quit when nothing is running)"
|
|
28
|
+
task :graceful do
|
|
29
29
|
on roles fetch(:sidekiq_roles) do |role|
|
|
30
30
|
git_plugin.switch_user(role) do
|
|
31
|
-
git_plugin.
|
|
32
|
-
git_plugin.
|
|
31
|
+
git_plugin.running_process_bot_processes.each do |process_bot_process|
|
|
32
|
+
git_plugin.process_bot_command(process_bot_process, :graceful)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
desc "Stop Sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)"
|
|
38
|
+
desc "Stop Sidekiq and ProcessBot (graceful shutdown within timeout, put unfinished tasks back to Redis)"
|
|
39
39
|
task :stop do
|
|
40
40
|
on roles fetch(:sidekiq_roles) do |role|
|
|
41
41
|
git_plugin.switch_user(role) do
|
|
42
|
-
git_plugin.
|
|
43
|
-
git_plugin.
|
|
42
|
+
git_plugin.running_process_bot_processes.each do |process_bot_data|
|
|
43
|
+
git_plugin.process_bot_command(process_bot_data, :stop)
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
desc "
|
|
50
|
-
task :stop_after_time do
|
|
51
|
-
on roles fetch(:sidekiq_roles) do |role|
|
|
52
|
-
git_plugin.switch_user(role) do
|
|
53
|
-
git_plugin.running_sidekiq_processes.each do |sidekiq_process|
|
|
54
|
-
git_plugin.stop_sidekiq_after_time(pid: sidekiq_process.fetch(:pid), signal: "TERM")
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
desc "Start sidekiq"
|
|
49
|
+
desc "Start Sidekiq and ProcessBot"
|
|
61
50
|
task :start do
|
|
62
51
|
on roles fetch(:sidekiq_roles) do |role|
|
|
63
52
|
git_plugin.switch_user(role) do
|
|
64
53
|
fetch(:sidekiq_processes).times do |idx|
|
|
65
|
-
puts "Starting Sidekiq #{idx}"
|
|
54
|
+
puts "Starting Sidekiq with ProcessBot #{idx}"
|
|
66
55
|
git_plugin.start_sidekiq(idx)
|
|
67
56
|
end
|
|
68
57
|
end
|
|
69
58
|
end
|
|
70
59
|
end
|
|
71
60
|
|
|
72
|
-
desc "Restart
|
|
61
|
+
desc "Restart Sidekiq and ProcessBot"
|
|
73
62
|
task :restart do
|
|
74
63
|
invoke! "process_bot:sidekiq:stop"
|
|
75
64
|
invoke! "process_bot:sidekiq:start"
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLength
|
|
2
4
|
def sidekiq_require
|
|
3
5
|
"--require #{fetch(:sidekiq_require)}" if fetch(:sidekiq_require)
|
|
4
6
|
end
|
|
@@ -30,41 +32,62 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
raise "Invalid PID: #{pid}" unless pid.to_s.match?(/\A\d+\Z/)
|
|
36
|
-
raise "Invalid signal: #{signal}" unless VALID_SIGNALS.include?(signal)
|
|
35
|
+
def process_bot_command(process_bot_data, command) # rubocop:disable Metrics/AbcSize
|
|
36
|
+
raise "No port in process bot data? #{process_bot_data}" unless process_bot_data["port"]
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
mode = "exec"
|
|
39
|
+
|
|
40
|
+
if mode == "runner"
|
|
41
|
+
args = {command: command, port: process_bot_data.fetch("port")}
|
|
42
|
+
|
|
43
|
+
if command == :graceful && !fetch(:process_bot_wait_for_gracefully_stopped).nil?
|
|
44
|
+
args["wait_for_gracefully_stopped"] = fetch(:process_bot_wait_for_gracefully_stopped)
|
|
45
|
+
end
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
raise "Invalid signal: #{signal}" unless VALID_SIGNALS.include?(signal)
|
|
47
|
+
escaped_args = JSON.generate(args).gsub("\"", "\\\"")
|
|
48
|
+
rails_runner_command = "require 'process_bot'; ProcessBot::Process.new(ProcessBot::Options.from_args(#{escaped_args})).execute!"
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
backend_command = "cd #{release_path} && " \
|
|
51
|
+
"#{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec rails runner \"#{rails_runner_command}\""
|
|
52
|
+
elsif mode == "exec"
|
|
53
|
+
backend_command = "cd #{release_path} && " \
|
|
54
|
+
"#{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot " \
|
|
55
|
+
"--command #{command} " \
|
|
56
|
+
"--port #{process_bot_data.fetch("port")}"
|
|
57
|
+
|
|
58
|
+
if command == :graceful && !fetch(:process_bot_wait_for_gracefully_stopped).nil?
|
|
59
|
+
backend_command << " --wait-for-gracefully-stopped #{fetch(:process_bot_wait_for_gracefully_stopped)}"
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
raise "Unknown mode: #{mode}"
|
|
63
|
+
end
|
|
47
64
|
|
|
48
|
-
backend.execute
|
|
65
|
+
backend.execute backend_command
|
|
49
66
|
end
|
|
50
67
|
|
|
51
|
-
def
|
|
68
|
+
def running_process_bot_processes
|
|
52
69
|
sidekiq_app_name = fetch(:sidekiq_app_name, fetch(:application))
|
|
53
70
|
raise "No :sidekiq_app_name was set" unless sidekiq_app_name
|
|
54
71
|
|
|
55
72
|
begin
|
|
56
|
-
processes_output = backend.capture("ps a |
|
|
73
|
+
processes_output = backend.capture("ps a | grep ProcessBot | grep sidekiq | grep -v '/usr/bin/SCREEN' | grep '#{Regexp.escape(sidekiq_app_name)}'")
|
|
57
74
|
rescue SSHKit::Command::Failed
|
|
58
75
|
# Fails when output is empty (when no processes found through grep)
|
|
59
|
-
puts "No Sidekiq processes found"
|
|
76
|
+
puts "No ProcessBot Sidekiq processes found"
|
|
60
77
|
return []
|
|
61
78
|
end
|
|
62
79
|
|
|
80
|
+
parse_process_bot_process_from_ps(processes_output)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_process_bot_process_from_ps(processes_output)
|
|
63
84
|
processes = []
|
|
64
|
-
processes_output.scan(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(
|
|
65
|
-
|
|
85
|
+
processes_output.scan(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+ProcessBot (\{([^\n]+?)\})$/).each do |process_output|
|
|
86
|
+
process_bot_data = JSON.parse(process_output[4])
|
|
87
|
+
process_bot_pid = process_output[0]
|
|
88
|
+
process_bot_data["process_bot_pid"] = process_bot_pid
|
|
66
89
|
|
|
67
|
-
processes <<
|
|
90
|
+
processes << process_bot_data
|
|
68
91
|
end
|
|
69
92
|
|
|
70
93
|
processes
|
|
@@ -86,7 +109,7 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
|
86
109
|
backend.capture(:echo, SSHKit.config.command_map[:bundle]).strip
|
|
87
110
|
end
|
|
88
111
|
|
|
89
|
-
def start_sidekiq(idx = 0) # rubocop:disable Metrics/AbcSize
|
|
112
|
+
def start_sidekiq(idx = 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
90
113
|
releases = backend.capture(:ls, "-x", releases_path).split
|
|
91
114
|
releases << release_timestamp.to_s if release_timestamp
|
|
92
115
|
releases.uniq
|
|
@@ -95,13 +118,19 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
|
95
118
|
raise "Invalid release timestamp: #{release_timestamp}" unless latest_release_version
|
|
96
119
|
|
|
97
120
|
args = [
|
|
121
|
+
"--command", "start",
|
|
98
122
|
"--id", "sidekiq-#{latest_release_version}-#{idx}",
|
|
123
|
+
"--application", fetch(:sidekiq_app_name, fetch(:application)),
|
|
99
124
|
"--handler", "sidekiq",
|
|
100
125
|
"--bundle-prefix", SSHKit.config.command_map.prefix[:bundle].join(" "),
|
|
101
126
|
"--sidekiq-environment", fetch(:sidekiq_env),
|
|
102
|
-
"--port",
|
|
127
|
+
"--port", idx + 7050,
|
|
128
|
+
"--release-path", release_path
|
|
103
129
|
]
|
|
104
|
-
|
|
130
|
+
|
|
131
|
+
# Use screen for logging everything which is why this is disabled
|
|
132
|
+
# args += ["--log-file-path", fetch(:sidekiq_log)] if fetch(:sidekiq_log)
|
|
133
|
+
|
|
105
134
|
args += ["--sidekiq-require", fetch(:sidekiq_require)] if fetch(:sidekiq_require)
|
|
106
135
|
args += ["--sidekiq-tag", fetch(:sidekiq_tag)] if fetch(:sidekiq_tag)
|
|
107
136
|
args += ["--sidekiq-queues", Array(fetch(:sidekiq_queue)).join(",")] if fetch(:sidekiq_queue)
|
|
@@ -112,13 +141,18 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
|
112
141
|
end
|
|
113
142
|
args += fetch(:sidekiq_options) if fetch(:sidekiq_options)
|
|
114
143
|
|
|
115
|
-
screen_args = ["-dmS sidekiq
|
|
116
|
-
|
|
144
|
+
screen_args = ["-dmS process-bot--sidekiq--#{idx}-#{latest_release_version}"]
|
|
145
|
+
|
|
146
|
+
if (process_bot_sidekiq_log = fetch(:process_bot_sidekig_log))
|
|
147
|
+
screen_args << "-L -Logfile #{process_bot_sidekiq_log}_#{latest_release_version}_#{idx}.log"
|
|
148
|
+
elsif fetch(:sidekiq_log)
|
|
149
|
+
screen_args << "-L -Logfile #{fetch(:sidekiq_log)}"
|
|
150
|
+
end
|
|
117
151
|
|
|
118
152
|
process_bot_args = args.compact.map { |arg| "\"#{arg}\"" }
|
|
119
153
|
|
|
120
154
|
command = "/usr/bin/screen #{screen_args.join(" ")} " \
|
|
121
|
-
"bash -c 'cd #{release_path} && #{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot #{process_bot_args.join(" ")}'"
|
|
155
|
+
"bash -c 'cd #{release_path} && exec #{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot #{process_bot_args.join(" ")}'"
|
|
122
156
|
|
|
123
157
|
puts "WARNING: A known bug prevents Sidekiq from starting when pty is set (which it is)" if fetch(:pty)
|
|
124
158
|
puts "ProcessBot Sidekiq command: #{command}"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
|
|
3
|
+
class ProcessBot::ClientSocket
|
|
4
|
+
attr_reader :options
|
|
5
|
+
|
|
6
|
+
def initialize(options:)
|
|
7
|
+
@options = options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def client
|
|
11
|
+
@client ||= Socket.tcp("localhost", options.fetch(:port).to_i, connect_timeout: 2)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def close
|
|
15
|
+
client.close
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def logger
|
|
19
|
+
@logger ||= ProcessBot::Logger.new(options: options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_command(data) # rubocop:disable Metrics/AbcSize
|
|
23
|
+
logger.log "Sending: #{data}"
|
|
24
|
+
client.puts(JSON.generate(data))
|
|
25
|
+
response_raw = client.gets
|
|
26
|
+
|
|
27
|
+
# Happens if process is interrupted
|
|
28
|
+
return :nil if response_raw.nil?
|
|
29
|
+
|
|
30
|
+
response = JSON.parse(response_raw)
|
|
31
|
+
|
|
32
|
+
return :success if response.fetch("type") == "success"
|
|
33
|
+
|
|
34
|
+
if response.fetch("type") == "error"
|
|
35
|
+
error = RuntimeError.new("Command raised an error: #{response.fetch("message")}")
|
|
36
|
+
error.set_backtrace(response.fetch("backtrace") + Thread.current.backtrace)
|
|
37
|
+
|
|
38
|
+
raise error
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
|
|
1
3
|
class ProcessBot::ControlSocket
|
|
2
4
|
attr_reader :options, :process, :server
|
|
3
5
|
|
|
@@ -6,14 +8,25 @@ class ProcessBot::ControlSocket
|
|
|
6
8
|
@process = process
|
|
7
9
|
end
|
|
8
10
|
|
|
11
|
+
def logger
|
|
12
|
+
@logger ||= ProcessBot::Logger.new(options: options)
|
|
13
|
+
end
|
|
14
|
+
|
|
9
15
|
def port
|
|
10
16
|
options.fetch(:port).to_i
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
def start
|
|
14
|
-
|
|
20
|
+
@server = TCPServer.new("localhost", port)
|
|
21
|
+
run_client_loop
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
logger.logs "TCPServer started"
|
|
24
|
+
|
|
25
|
+
options.events.call(:on_socket_opened, port: port)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stop
|
|
29
|
+
server.close
|
|
17
30
|
end
|
|
18
31
|
|
|
19
32
|
def run_client_loop
|
|
@@ -26,14 +39,48 @@ class ProcessBot::ControlSocket
|
|
|
26
39
|
end
|
|
27
40
|
end
|
|
28
41
|
|
|
29
|
-
def handle_client(client)
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
def handle_client(client) # rubocop:disable Metrics/AbcSize
|
|
43
|
+
loop do
|
|
44
|
+
data = client.gets
|
|
45
|
+
break if data.nil? # Client disconnected
|
|
46
|
+
|
|
47
|
+
command = JSON.parse(data)
|
|
48
|
+
command_type = command.fetch("command")
|
|
49
|
+
|
|
50
|
+
if command_type == "graceful" || command_type == "stop"
|
|
51
|
+
begin
|
|
52
|
+
command_options = if command["options"]
|
|
53
|
+
symbolize_keys(command.fetch("options"))
|
|
54
|
+
else
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
logger.logs "Command #{command_type} with options #{command_options}"
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
process.__send__(command_type, **command_options)
|
|
61
|
+
client.puts(JSON.generate(type: "success"))
|
|
62
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
|
63
|
+
logger.logs e.message, type: :stderr
|
|
64
|
+
logger.logs e.backtrace, type: :stderr
|
|
65
|
+
|
|
66
|
+
client.puts(JSON.generate(type: "error", message: e.message, backtrace: e.backtrace))
|
|
67
|
+
|
|
68
|
+
raise e
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
client.puts(JSON.generate(type: "error", message: "Unknown command: #{command_type}", backtrace: Thread.current.backtrace))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def symbolize_keys(hash)
|
|
77
|
+
new_hash = {}
|
|
78
|
+
hash.each do |key, value|
|
|
79
|
+
next if key == "port"
|
|
80
|
+
|
|
81
|
+
new_hash[key.to_sym] = value
|
|
37
82
|
end
|
|
83
|
+
|
|
84
|
+
new_hash
|
|
38
85
|
end
|
|
39
86
|
end
|
data/lib/process_bot/logger.rb
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
class ProcessBot::Logger
|
|
2
|
-
attr_reader :
|
|
2
|
+
attr_reader :options
|
|
3
3
|
|
|
4
4
|
def initialize(options:)
|
|
5
5
|
@options = options
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def log(output, type: :stdout)
|
|
9
|
+
if type == :stdout || (type == :debug && options[:debug])
|
|
10
|
+
$stdout.print output
|
|
11
|
+
elsif type == :stderr
|
|
12
|
+
$stderr.print output
|
|
13
|
+
else
|
|
14
|
+
raise "Unknown type: #{type}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return unless log_to_file?
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
fp_log.write(output)
|
|
20
|
+
fp_log.flush
|
|
8
21
|
end
|
|
9
22
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
fp_log&.flush
|
|
23
|
+
def logs(output, **args)
|
|
24
|
+
log("#{output}\n", **args)
|
|
13
25
|
end
|
|
14
26
|
|
|
15
27
|
def log_file_path
|
|
16
28
|
options.fetch(:log_file_path)
|
|
17
29
|
end
|
|
18
30
|
|
|
19
|
-
def
|
|
20
|
-
|
|
31
|
+
def log_to_file?
|
|
32
|
+
options.present?(:log_file_path)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fp_log
|
|
36
|
+
@fp_log ||= File.open(log_file_path, "a") if log_to_file?
|
|
21
37
|
end
|
|
22
38
|
end
|
data/lib/process_bot/options.rb
CHANGED
|
@@ -1,12 +1,73 @@
|
|
|
1
1
|
class ProcessBot::Options
|
|
2
2
|
attr_reader :options
|
|
3
3
|
|
|
4
|
+
def self.from_args(args)
|
|
5
|
+
options = ProcessBot::Options.new
|
|
6
|
+
|
|
7
|
+
args.each do |key, value|
|
|
8
|
+
options.set(key, value)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
options
|
|
12
|
+
end
|
|
13
|
+
|
|
4
14
|
def initialize(options = {})
|
|
5
15
|
@options = options
|
|
6
16
|
end
|
|
7
17
|
|
|
8
|
-
def
|
|
9
|
-
options
|
|
18
|
+
def [](key)
|
|
19
|
+
options[key]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def events
|
|
23
|
+
@events ||= begin
|
|
24
|
+
require "knjrbfw"
|
|
25
|
+
|
|
26
|
+
event_handler = ::Knj::Event_handler.new
|
|
27
|
+
event_handler.add_event(name: :on_process_started)
|
|
28
|
+
event_handler.add_event(name: :on_socket_opened)
|
|
29
|
+
event_handler
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fetch(...)
|
|
34
|
+
options.fetch(...)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def application_basename
|
|
38
|
+
@application_basename ||= begin
|
|
39
|
+
app_path_parts = release_path.split("/")
|
|
40
|
+
|
|
41
|
+
if release_path.include?("/releases/")
|
|
42
|
+
app_path_parts.pop(2)
|
|
43
|
+
elsif release_path.end_with?("/current")
|
|
44
|
+
app_path_parts.pop
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
app_path_parts.last
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def possible_process_titles
|
|
52
|
+
possible_names = []
|
|
53
|
+
|
|
54
|
+
# Sidekiq name can by current Rails root base name
|
|
55
|
+
possible_names << application_basename
|
|
56
|
+
|
|
57
|
+
# Sidekiq name can be set tag name (but we wrongly read application for some reason?)
|
|
58
|
+
possible_names << options.fetch(:application)
|
|
59
|
+
|
|
60
|
+
possible_names
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def possible_process_titles_joined_regex
|
|
64
|
+
possible_process_titles_joined_regex = ""
|
|
65
|
+
possible_process_titles.each_with_index do |possible_name, index|
|
|
66
|
+
possible_process_titles_joined_regex << "|" if index >= 1
|
|
67
|
+
possible_process_titles_joined_regex << Regexp.escape(possible_name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
possible_process_titles_joined_regex
|
|
10
71
|
end
|
|
11
72
|
|
|
12
73
|
def present?(key)
|
|
@@ -15,6 +76,10 @@ class ProcessBot::Options
|
|
|
15
76
|
false
|
|
16
77
|
end
|
|
17
78
|
|
|
79
|
+
def release_path
|
|
80
|
+
@release_path ||= fetch(:release_path)
|
|
81
|
+
end
|
|
82
|
+
|
|
18
83
|
def set(key, value)
|
|
19
84
|
options[key] = value
|
|
20
85
|
end
|
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
class ProcessBot::Process::Handlers::Sidekiq
|
|
2
|
-
attr_reader :options
|
|
2
|
+
attr_reader :options, :process
|
|
3
3
|
|
|
4
|
-
def initialize(
|
|
5
|
-
@
|
|
4
|
+
def initialize(process)
|
|
5
|
+
@process = process
|
|
6
|
+
@options = process.options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def current_pid
|
|
10
|
+
process.current_pid
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def daemonize
|
|
14
|
+
logger.logs "Daemonize!"
|
|
15
|
+
|
|
16
|
+
pid = Process.fork do
|
|
17
|
+
Process.daemon
|
|
18
|
+
yield
|
|
19
|
+
end
|
|
6
20
|
|
|
7
|
-
|
|
21
|
+
Process.detach(pid) if pid
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def false_value?(value)
|
|
25
|
+
!value || value == "false"
|
|
8
26
|
end
|
|
9
27
|
|
|
10
28
|
def fetch(*args, **opts)
|
|
11
29
|
options.fetch(*args, **opts)
|
|
12
30
|
end
|
|
13
31
|
|
|
32
|
+
def logger
|
|
33
|
+
@logger ||= ProcessBot::Logger.new(options: options)
|
|
34
|
+
end
|
|
35
|
+
|
|
14
36
|
def set_option(key, value)
|
|
15
37
|
raise "Unknown option for Sidekiq handler: #{key}" unless options.key?(key)
|
|
16
38
|
|
|
@@ -21,35 +43,95 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
21
43
|
options.set(*args, **opts)
|
|
22
44
|
end
|
|
23
45
|
|
|
24
|
-
def
|
|
25
|
-
set :sidekiq_default_hooks, true
|
|
26
|
-
set :sidekiq_pid, -> { File.join(shared_path, "tmp", "pids", "sidekiq.pid") }
|
|
27
|
-
set :sidekiq_timeout, 10
|
|
28
|
-
set :sidekiq_roles, fetch(:sidekiq_role, :app)
|
|
29
|
-
set :sidekiq_processes, 1
|
|
30
|
-
set :sidekiq_options_per_process, nil
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def command # rubocop:disable Metrics/AbcSize
|
|
46
|
+
def start_command # rubocop:disable Metrics/AbcSize
|
|
34
47
|
args = []
|
|
35
48
|
|
|
36
49
|
options.options.each do |key, value|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
else
|
|
45
|
-
args.push "--#{sidekiq_key} #{value}"
|
|
50
|
+
next unless (match = key.to_s.match(/\Asidekiq_(.+)\Z/))
|
|
51
|
+
|
|
52
|
+
sidekiq_key = match[1]
|
|
53
|
+
|
|
54
|
+
if sidekiq_key == "queue"
|
|
55
|
+
value.split(",").each do |queue|
|
|
56
|
+
args.push "--queue #{queue}"
|
|
46
57
|
end
|
|
58
|
+
else
|
|
59
|
+
args.push "--#{sidekiq_key} #{value}"
|
|
47
60
|
end
|
|
48
61
|
end
|
|
49
62
|
|
|
50
|
-
command = ""
|
|
63
|
+
command = "bash -c 'cd #{options.fetch(:release_path)} && exec "
|
|
51
64
|
command << "#{options.fetch(:bundle_prefix)} " if options.present?(:bundle_prefix)
|
|
52
65
|
command << "bundle exec sidekiq #{args.compact.join(' ')}"
|
|
66
|
+
command << "'"
|
|
53
67
|
command
|
|
54
68
|
end
|
|
69
|
+
|
|
70
|
+
def graceful(**args)
|
|
71
|
+
wait_for_gracefully_stopped = args.fetch(:wait_for_gracefully_stopped, true)
|
|
72
|
+
process.set_stopped
|
|
73
|
+
|
|
74
|
+
unless current_pid
|
|
75
|
+
warn "Sidekiq not running with a PID"
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
Process.kill("TSTP", current_pid)
|
|
80
|
+
|
|
81
|
+
if false_value?(wait_for_gracefully_stopped)
|
|
82
|
+
logger.logs "Dont wait for gracefully stopped - doing that in fork..."
|
|
83
|
+
|
|
84
|
+
daemonize do
|
|
85
|
+
wait_for_no_jobs_and_stop_sidekiq
|
|
86
|
+
exit
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
logger.logs "Wait for gracefully stopped..."
|
|
90
|
+
wait_for_no_jobs_and_stop_sidekiq
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def stop(**_args)
|
|
95
|
+
process.set_stopped
|
|
96
|
+
|
|
97
|
+
unless current_pid
|
|
98
|
+
warn "#{handler_name} not running with a PID"
|
|
99
|
+
return
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
Process.kill("TERM", current_pid)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def wait_for_no_jobs # rubocop:disable Metrics/AbcSize
|
|
106
|
+
loop do
|
|
107
|
+
found_process = false
|
|
108
|
+
|
|
109
|
+
Knj::Unix_proc.list("grep" => current_pid) do |process|
|
|
110
|
+
process_command = process.data.fetch("cmd")
|
|
111
|
+
process_pid = process.data.fetch("pid").to_i
|
|
112
|
+
next unless process_pid == current_pid
|
|
113
|
+
|
|
114
|
+
found_process = true
|
|
115
|
+
sidekiq_regex = /\Asidekiq (\d+).(\d+).(\d+) (#{options.possible_process_titles_joined_regex}) \[(\d+) of (\d+)(\]|) (.+?)(\]|)\Z/
|
|
116
|
+
match = process_command.match(sidekiq_regex)
|
|
117
|
+
raise "Couldnt match Sidekiq command: #{process_command} with Sidekiq regex: #{sidekiq_regex}" unless match
|
|
118
|
+
|
|
119
|
+
running_jobs = match[5].to_i
|
|
120
|
+
|
|
121
|
+
logger.logs "running_jobs: #{running_jobs}"
|
|
122
|
+
|
|
123
|
+
return if running_jobs.zero? # rubocop:disable Lint/NonLocalExitFromIterator
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
raise "Couldn't find running process with PID #{current_pid}" unless found_process
|
|
127
|
+
|
|
128
|
+
sleep 1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def wait_for_no_jobs_and_stop_sidekiq
|
|
133
|
+
logger.logs "Wait for no jobs and Stop sidekiq"
|
|
134
|
+
wait_for_no_jobs
|
|
135
|
+
stop
|
|
136
|
+
end
|
|
55
137
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
require "knjrbfw"
|
|
2
|
+
|
|
1
3
|
class ProcessBot::Process::Runner
|
|
2
|
-
attr_reader :command, :exit_status, :logger, :monitor, :options, :stop_time
|
|
4
|
+
attr_reader :command, :exit_status, :logger, :monitor, :options, :pid, :stop_time, :subprocess_pid
|
|
3
5
|
|
|
4
6
|
def initialize(command:, logger:, options:)
|
|
5
7
|
@command = command
|
|
@@ -9,8 +11,12 @@ class ProcessBot::Process::Runner
|
|
|
9
11
|
@output = []
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
def output(output:, type:)
|
|
13
|
-
logger.log(output)
|
|
14
|
+
def output(output:, type:)
|
|
15
|
+
logger.log(output, type: type)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def running?
|
|
19
|
+
!stop_time
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def run # rubocop:disable Metrics/AbcSize
|
|
@@ -20,8 +26,8 @@ class ProcessBot::Process::Runner
|
|
|
20
26
|
require "pty"
|
|
21
27
|
|
|
22
28
|
PTY.spawn(command, err: stderr_writer.fileno) do |stdout, _stdin, pid|
|
|
23
|
-
@
|
|
24
|
-
logger.
|
|
29
|
+
@subprocess_pid = pid
|
|
30
|
+
logger.logs "Command running with PID #{pid}: #{command}"
|
|
25
31
|
|
|
26
32
|
stdout_reader_thread = Thread.new do
|
|
27
33
|
stdout.each_char do |chunk|
|
|
@@ -32,7 +38,7 @@ class ProcessBot::Process::Runner
|
|
|
32
38
|
rescue Errno::EIO
|
|
33
39
|
# Process done
|
|
34
40
|
ensure
|
|
35
|
-
status = Process::Status.wait(
|
|
41
|
+
status = Process::Status.wait(subprocess_pid, 0)
|
|
36
42
|
|
|
37
43
|
@exit_status = status.exitstatus
|
|
38
44
|
stderr_writer.close
|
|
@@ -46,10 +52,56 @@ class ProcessBot::Process::Runner
|
|
|
46
52
|
end
|
|
47
53
|
end
|
|
48
54
|
|
|
55
|
+
find_sidekiq_pid
|
|
56
|
+
|
|
49
57
|
stdout_reader_thread.join
|
|
50
58
|
stderr_reader_thread.join
|
|
51
59
|
|
|
52
60
|
@stop_time = Time.new
|
|
53
61
|
end
|
|
54
62
|
end
|
|
63
|
+
|
|
64
|
+
def subprocess_pgid
|
|
65
|
+
@subprocess_pgid ||= Process.getpgid(subprocess_pid)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def sidekiq_app_name
|
|
69
|
+
options.fetch(:application)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def find_sidekiq_pid # rubocop:disable Metrics/AbcSize
|
|
73
|
+
Thread.new do
|
|
74
|
+
while running? && !pid
|
|
75
|
+
Knj::Unix_proc.list("grep" => "sidekiq") do |process|
|
|
76
|
+
cmd = process.data.fetch("cmd")
|
|
77
|
+
|
|
78
|
+
if /sidekiq ([0-9]+\.[0-9]+\.[0-9]+) (#{options.possible_process_titles_joined_regex})/.match?(cmd)
|
|
79
|
+
sidekiq_pid = process.data.fetch("pid").to_i
|
|
80
|
+
|
|
81
|
+
begin
|
|
82
|
+
sidekiq_pgid = Process.getpgid(sidekiq_pid)
|
|
83
|
+
rescue Errno::ESRCH
|
|
84
|
+
# Process no longer running
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if subprocess_pgid == sidekiq_pgid
|
|
88
|
+
puts "FOUND PID: #{sidekiq_pid}"
|
|
89
|
+
|
|
90
|
+
@pid = sidekiq_pid
|
|
91
|
+
options.events.call(:on_process_started, pid: pid)
|
|
92
|
+
|
|
93
|
+
break
|
|
94
|
+
else
|
|
95
|
+
puts "PGID didn't match - Sidekiq: #{sidekiq_pgid} Own: #{subprocess_pgid}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
unless pid
|
|
101
|
+
puts "Waiting 1 second before trying to find Sidekiq PID again"
|
|
102
|
+
sleep 1
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
55
107
|
end
|
data/lib/process_bot/process.rb
CHANGED
|
@@ -1,35 +1,79 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
require "json"
|
|
3
|
+
require "string-cases"
|
|
4
|
+
|
|
1
5
|
class ProcessBot::Process
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
def_delegator :handler_instance, :graceful
|
|
9
|
+
def_delegator :handler_instance, :stop
|
|
10
|
+
|
|
2
11
|
autoload :Handlers, "#{__dir__}/process/handlers"
|
|
3
12
|
autoload :Runner, "#{__dir__}/process/runner"
|
|
4
13
|
|
|
5
|
-
attr_reader :options, :stopped
|
|
14
|
+
attr_reader :current_pid, :current_process_title, :options, :port, :stopped
|
|
6
15
|
|
|
7
16
|
def initialize(options)
|
|
8
17
|
@options = options
|
|
9
18
|
@stopped = false
|
|
10
|
-
end
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
options.events.connect(:on_process_started, &method(:on_process_started)) # rubocop:disable Performance/MethodObjectAsBlock
|
|
21
|
+
options.events.connect(:on_socket_opened, &method(:on_socket_opened)) # rubocop:disable Performance/MethodObjectAsBlock
|
|
22
|
+
|
|
23
|
+
logger.logs("ProcessBot 1 - Options: #{options.options}")
|
|
14
24
|
end
|
|
15
25
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
def execute!
|
|
27
|
+
command = options.fetch(:command)
|
|
28
|
+
|
|
29
|
+
if command == "start"
|
|
30
|
+
start
|
|
31
|
+
elsif command == "graceful" || command == "stop"
|
|
32
|
+
client.send_command(command: command, options: options.options)
|
|
33
|
+
else
|
|
34
|
+
raise "Unknown command: #{command}"
|
|
35
|
+
end
|
|
19
36
|
end
|
|
20
37
|
|
|
21
|
-
def
|
|
22
|
-
@
|
|
38
|
+
def client
|
|
39
|
+
@client ||= ProcessBot::ClientSocket.new(options: options)
|
|
23
40
|
end
|
|
24
41
|
|
|
25
42
|
def handler_class
|
|
26
43
|
@handler_class ||= begin
|
|
27
|
-
require_relative "process/handlers/#{
|
|
28
|
-
ProcessBot::Process::Handlers.const_get(StringCases.snake_to_camel(
|
|
44
|
+
require_relative "process/handlers/#{handler_name}"
|
|
45
|
+
ProcessBot::Process::Handlers.const_get(StringCases.snake_to_camel(handler_name))
|
|
29
46
|
end
|
|
30
47
|
end
|
|
31
48
|
|
|
32
|
-
def
|
|
49
|
+
def handler_instance
|
|
50
|
+
@handler_instance ||= handler_class.new(self)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def handler_name
|
|
54
|
+
@handler_name ||= options.fetch(:handler)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def logger
|
|
58
|
+
@logger ||= ProcessBot::Logger.new(options: options)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def on_process_started(_event_name, pid:)
|
|
62
|
+
@current_pid = pid
|
|
63
|
+
update_process_title
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def on_socket_opened(_event_name, port:)
|
|
67
|
+
@port = port
|
|
68
|
+
update_process_title
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def start_control_socket
|
|
72
|
+
@control_socket = ProcessBot::ControlSocket.new(options: options, process: self)
|
|
73
|
+
@control_socket.start
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def start
|
|
33
77
|
start_control_socket
|
|
34
78
|
|
|
35
79
|
loop do
|
|
@@ -38,15 +82,24 @@ class ProcessBot::Process
|
|
|
38
82
|
if stopped
|
|
39
83
|
break
|
|
40
84
|
else
|
|
41
|
-
|
|
85
|
+
logger.logs "Process stopped - starting again after 1 sec"
|
|
42
86
|
sleep 1
|
|
43
87
|
end
|
|
44
88
|
end
|
|
45
89
|
end
|
|
46
90
|
|
|
91
|
+
def set_stopped
|
|
92
|
+
@stopped = true
|
|
93
|
+
end
|
|
94
|
+
|
|
47
95
|
def run
|
|
48
|
-
|
|
49
|
-
runner = ProcessBot::Process::Runner.new(command: handler_instance.command, logger: logger, options: options)
|
|
96
|
+
runner = ProcessBot::Process::Runner.new(command: handler_instance.start_command, logger: logger, options: options)
|
|
50
97
|
runner.run
|
|
51
98
|
end
|
|
99
|
+
|
|
100
|
+
def update_process_title
|
|
101
|
+
process_args = {application: options[:application], handler: handler_name, id: options[:id], pid: current_pid, port: port}
|
|
102
|
+
@current_process_title = "ProcessBot #{JSON.generate(process_args)}"
|
|
103
|
+
Process.setproctitle(current_process_title)
|
|
104
|
+
end
|
|
52
105
|
end
|
data/lib/process_bot/version.rb
CHANGED
data/lib/process_bot.rb
CHANGED
|
@@ -4,6 +4,7 @@ module ProcessBot
|
|
|
4
4
|
class Error < StandardError; end
|
|
5
5
|
|
|
6
6
|
autoload :Capistrano, "#{__dir__}/process_bot/capistrano"
|
|
7
|
+
autoload :ClientSocket, "#{__dir__}/process_bot/client_socket"
|
|
7
8
|
autoload :ControlSocket, "#{__dir__}/process_bot/control_socket"
|
|
8
9
|
autoload :Logger, "#{__dir__}/process_bot/logger"
|
|
9
10
|
autoload :Options, "#{__dir__}/process_bot/options"
|
data/peak_flow.yml
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
rvm: true
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
builds:
|
|
3
|
+
build_1:
|
|
4
|
+
environment:
|
|
5
|
+
RUBY_VERSION: 2.7.8
|
|
6
|
+
name: Ruby 2.7.8
|
|
7
|
+
script:
|
|
8
|
+
- bundle exec rspec
|
|
9
|
+
build_2:
|
|
10
|
+
environment:
|
|
11
|
+
RUBY_VERSION: 3.2.2
|
|
12
|
+
name: Ruby 3.2.2
|
|
13
|
+
script:
|
|
14
|
+
- bundle exec rspec
|
|
15
|
+
build_3:
|
|
16
|
+
environment:
|
|
17
|
+
RUBY_VERSION: 2.7.8
|
|
18
|
+
name: Rubocop
|
|
19
|
+
script:
|
|
20
|
+
- bundle exec rubocop
|
data/process_bot.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = "Run and control processes."
|
|
13
13
|
spec.homepage = "https://github.com/kaspernj/process_bot"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = ">= 2.
|
|
15
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
16
16
|
|
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
18
|
|
|
@@ -31,15 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
32
32
|
spec.require_paths = ["lib"]
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
|
34
|
+
spec.add_dependency "knjrbfw", ">= 0.0.116"
|
|
36
35
|
|
|
37
|
-
spec.add_development_dependency "rubocop"
|
|
38
|
-
spec.add_development_dependency "rubocop-performance"
|
|
39
|
-
spec.add_development_dependency "rubocop-rake"
|
|
40
|
-
spec.add_development_dependency "rubocop-rspec"
|
|
41
|
-
|
|
42
|
-
# For more information and examples about making a new gem, check out our
|
|
43
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
|
44
36
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
45
37
|
end
|
metadata
CHANGED
|
@@ -1,71 +1,29 @@
|
|
|
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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kaspernj
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-12-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: knjrbfw
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
20
|
-
type: :
|
|
19
|
+
version: 0.0.116
|
|
20
|
+
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: rubocop-performance
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rubocop-rake
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rubocop-rspec
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
26
|
+
version: 0.0.116
|
|
69
27
|
description: Run and control processes.
|
|
70
28
|
email:
|
|
71
29
|
- k@spernj.org
|
|
@@ -76,6 +34,7 @@ extra_rdoc_files: []
|
|
|
76
34
|
files:
|
|
77
35
|
- ".rspec"
|
|
78
36
|
- ".rubocop.yml"
|
|
37
|
+
- ".ruby-version"
|
|
79
38
|
- CHANGELOG.md
|
|
80
39
|
- Gemfile
|
|
81
40
|
- Gemfile.lock
|
|
@@ -91,6 +50,7 @@ files:
|
|
|
91
50
|
- lib/process_bot/capistrano/sidekiq.rake
|
|
92
51
|
- lib/process_bot/capistrano/sidekiq.rb
|
|
93
52
|
- lib/process_bot/capistrano/sidekiq_helpers.rb
|
|
53
|
+
- lib/process_bot/client_socket.rb
|
|
94
54
|
- lib/process_bot/control_socket.rb
|
|
95
55
|
- lib/process_bot/logger.rb
|
|
96
56
|
- lib/process_bot/options.rb
|
|
@@ -119,14 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
119
79
|
requirements:
|
|
120
80
|
- - ">="
|
|
121
81
|
- !ruby/object:Gem::Version
|
|
122
|
-
version: 2.
|
|
82
|
+
version: 2.7.0
|
|
123
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
84
|
requirements:
|
|
125
85
|
- - ">="
|
|
126
86
|
- !ruby/object:Gem::Version
|
|
127
87
|
version: '0'
|
|
128
88
|
requirements: []
|
|
129
|
-
rubygems_version: 3.
|
|
89
|
+
rubygems_version: 3.4.17
|
|
130
90
|
signing_key:
|
|
131
91
|
specification_version: 4
|
|
132
92
|
summary: Run and control processes.
|