process_bot 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|