process_bot 0.1.12 → 0.1.13
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/AGENTS.md +15 -0
- data/Gemfile.lock +1 -1
- data/README.md +37 -0
- data/lib/process_bot/capistrano/sidekiq.rake +12 -0
- data/lib/process_bot/capistrano/sidekiq_helpers.rb +13 -6
- data/lib/process_bot/client_socket.rb +4 -1
- data/lib/process_bot/control_socket.rb +6 -1
- data/lib/process_bot/logger.rb +44 -11
- data/lib/process_bot/process/handlers/custom.rb +1 -1
- data/lib/process_bot/process/handlers/sidekiq.rb +2 -0
- data/lib/process_bot/process/runner.rb +21 -13
- data/lib/process_bot/process.rb +12 -6
- data/lib/process_bot/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 074d3e8a15a3340496bf231b14a3a8ca664bd8c96c22372ee9bf2251740e8eda
|
|
4
|
+
data.tar.gz: 2696dbf343af1d30ca5b4c34b14f69c6278e4d441728b8454943426fe3872112
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c3d186e16d47a402c01d2e8b1468f814185662a397785cc4ced9f9cacd379b32dc67cf76f12e32d732184e91a361619f773163b1a701baada52f62d6c952291
|
|
7
|
+
data.tar.gz: 8763c67b5f30f9fef7f71f739802560b500f23b158b5d8d34cff49c9077f6cfcc06ab24d03c74a4e776aa3c13a0a27fbb4d576fa4a8a8262cc701a1f92c2b893
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# AGENTS
|
|
2
|
+
|
|
3
|
+
## Notes
|
|
4
|
+
- Added an internal logging toggle via `--log`/`--logging` and routed internal actions through the logger.
|
|
5
|
+
- Added log lines for client connections, command sends, and signal handling.
|
|
6
|
+
- Documented logging usage in `README.md`.
|
|
7
|
+
- Branch: logging-toggle
|
|
8
|
+
- PR: https://github.com/kaspernj/process_bot/pull/164
|
|
9
|
+
- Made graceful shutdown waiting optional and defaulted Capistrano to not wait.
|
|
10
|
+
- Kept graceful handling synchronous and verified `bundle exec rspec`.
|
|
11
|
+
- Enabled ProcessBot logging by default for Capistrano hooks (configurable via `process_bot_log`).
|
|
12
|
+
- Always run RuboCop against changed or created Ruby files.
|
|
13
|
+
- Added `graceful_no_wait` command and Capistrano task for non-blocking graceful shutdowns.
|
|
14
|
+
- Always add or update tests for new/changed functionality, and run them.
|
|
15
|
+
- Added coverage for graceful_no_wait and Capistrano wait defaults.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -46,6 +46,43 @@ ProcessBot provides these Sidekiq tasks:
|
|
|
46
46
|
- `process_bot:sidekiq:graceful_no_wait` (stops fetching new jobs and returns immediately)
|
|
47
47
|
- `process_bot:sidekiq:restart`
|
|
48
48
|
|
|
49
|
+
You can also skip waiting for graceful completion:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cap production process_bot:sidekiq:graceful_no_wait
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Logging
|
|
56
|
+
|
|
57
|
+
ProcessBot can log its internal actions (connecting, sending commands, signals, etc.) to stdout.
|
|
58
|
+
Enable this with `--log true` (or `--logging true`):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bundle exec process_bot --command start --log true
|
|
62
|
+
bundle exec process_bot --command graceful --log true
|
|
63
|
+
bundle exec process_bot --command graceful_no_wait --log true
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To write logs to a file, add `--log-file-path`:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bundle exec process_bot --command start --log true --log-file-path /var/log/process_bot.log
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Graceful shutdown waiting
|
|
73
|
+
|
|
74
|
+
Use `process_bot:sidekiq:graceful` to wait for running jobs, and
|
|
75
|
+
`process_bot:sidekiq:graceful_no_wait` to return immediately while Sidekiq drains.
|
|
76
|
+
|
|
77
|
+
### Capistrano logging
|
|
78
|
+
|
|
79
|
+
ProcessBot logging is enabled by default in the Capistrano integration.
|
|
80
|
+
You can override it with:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
set :process_bot_log, false
|
|
84
|
+
```
|
|
85
|
+
|
|
49
86
|
### CLI options
|
|
50
87
|
|
|
51
88
|
When running ProcessBot directly, you can control graceful waiting and log file output:
|
|
@@ -13,6 +13,7 @@ namespace :load do
|
|
|
13
13
|
set :sidekiq_processes, 1
|
|
14
14
|
set :sidekiq_options_per_process, nil
|
|
15
15
|
set :sidekiq_user, nil
|
|
16
|
+
set :process_bot_log, true
|
|
16
17
|
# Rbenv, Chruby, and RVM integration
|
|
17
18
|
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
18
19
|
set :rvm_map_bins, fetch(:rvm_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
|
@@ -35,6 +36,17 @@ namespace :process_bot do
|
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
desc "Stop Sidekiq and ProcessBot gracefully without waiting for completion"
|
|
40
|
+
task :graceful_no_wait do
|
|
41
|
+
on roles fetch(:sidekiq_roles) do |role|
|
|
42
|
+
git_plugin.switch_user(role) do
|
|
43
|
+
git_plugin.running_process_bot_processes.each do |process_bot_process|
|
|
44
|
+
git_plugin.process_bot_command(process_bot_process, :graceful_no_wait)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
desc "Stop Sidekiq and ProcessBot (graceful shutdown within timeout, put unfinished tasks back to Redis)"
|
|
39
51
|
task :stop do
|
|
40
52
|
on roles fetch(:sidekiq_roles) do |role|
|
|
@@ -36,13 +36,13 @@ 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)
|
|
39
40
|
|
|
40
41
|
if mode == "runner"
|
|
41
42
|
args = {command: command, port: process_bot_data.fetch("port")}
|
|
43
|
+
args["log"] = fetch(:process_bot_log) unless fetch(:process_bot_log).nil?
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
args["wait_for_gracefully_stopped"] = fetch(:process_bot_wait_for_gracefully_stopped)
|
|
45
|
-
end
|
|
45
|
+
args["wait_for_gracefully_stopped"] = wait_for_gracefully_stopped unless wait_for_gracefully_stopped.nil?
|
|
46
46
|
|
|
47
47
|
escaped_args = JSON.generate(args).gsub("\"", "\\\"")
|
|
48
48
|
rails_runner_command = "require 'process_bot'; ProcessBot::Process.new(ProcessBot::Options.from_args(#{escaped_args})).execute!"
|
|
@@ -55,13 +55,13 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
55
55
|
"--command #{command} " \
|
|
56
56
|
"--port #{process_bot_data.fetch("port")}"
|
|
57
57
|
|
|
58
|
+
backend_command << " --log #{fetch(:process_bot_log)}" unless fetch(:process_bot_log).nil?
|
|
59
|
+
|
|
58
60
|
args.each do |key, value|
|
|
59
61
|
backend_command << "#{key} #{value}"
|
|
60
62
|
end
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
backend_command << " --wait-for-gracefully-stopped #{fetch(:process_bot_wait_for_gracefully_stopped)}"
|
|
64
|
-
end
|
|
64
|
+
backend_command << " --wait-for-gracefully-stopped #{wait_for_gracefully_stopped}" unless wait_for_gracefully_stopped.nil?
|
|
65
65
|
else
|
|
66
66
|
raise "Unknown mode: #{mode}"
|
|
67
67
|
end
|
|
@@ -69,6 +69,13 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
|
|
|
69
69
|
backend.execute backend_command
|
|
70
70
|
end
|
|
71
71
|
|
|
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
|
+
|
|
72
79
|
def running_process_bot_processes
|
|
73
80
|
sidekiq_app_name = fetch(:sidekiq_app_name, fetch(:application))
|
|
74
81
|
raise "No :sidekiq_app_name was set" unless sidekiq_app_name
|
|
@@ -8,7 +8,10 @@ class ProcessBot::ClientSocket
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def client
|
|
11
|
-
@client
|
|
11
|
+
return @client if @client
|
|
12
|
+
|
|
13
|
+
logger.logs "Connecting to process on port #{options.fetch(:port)}"
|
|
14
|
+
@client = Socket.tcp("localhost", options.fetch(:port).to_i, connect_timeout: 2)
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def close
|
|
@@ -59,7 +59,7 @@ class ProcessBot::ControlSocket
|
|
|
59
59
|
command = JSON.parse(data)
|
|
60
60
|
command_type = command.fetch("command")
|
|
61
61
|
|
|
62
|
-
if command_type == "graceful" || command_type == "stop"
|
|
62
|
+
if command_type == "graceful" || command_type == "graceful_no_wait" || command_type == "stop"
|
|
63
63
|
begin
|
|
64
64
|
command_options = if command["options"]
|
|
65
65
|
symbolize_keys(command.fetch("options"))
|
|
@@ -67,6 +67,11 @@ class ProcessBot::ControlSocket
|
|
|
67
67
|
{}
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
if command_type == "graceful_no_wait"
|
|
71
|
+
command_type = "graceful"
|
|
72
|
+
command_options[:wait_for_gracefully_stopped] = false
|
|
73
|
+
end
|
|
74
|
+
|
|
70
75
|
logger.logs "Command #{command_type} with options #{command_options}"
|
|
71
76
|
|
|
72
77
|
process.__send__(command_type, **command_options)
|
data/lib/process_bot/logger.rb
CHANGED
|
@@ -10,22 +10,16 @@ class ProcessBot::Logger
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def log(output, type: :stdout)
|
|
13
|
-
|
|
14
|
-
$stdout.print output
|
|
15
|
-
elsif type == :stderr
|
|
16
|
-
$stderr.print output
|
|
17
|
-
else
|
|
18
|
-
raise "Unknown type: #{type}"
|
|
19
|
-
end
|
|
13
|
+
write_output(output, type)
|
|
20
14
|
|
|
21
15
|
return unless log_to_file?
|
|
16
|
+
return if type == :debug && !debug_enabled?
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
fp_log.flush
|
|
18
|
+
write_log_file(output)
|
|
25
19
|
end
|
|
26
20
|
|
|
27
|
-
def logs(output, **args)
|
|
28
|
-
log("#{output}\n", **args)
|
|
21
|
+
def logs(output, type: :info, **args)
|
|
22
|
+
log("#{output}\n", type: type, **args)
|
|
29
23
|
end
|
|
30
24
|
|
|
31
25
|
def log_file_path
|
|
@@ -39,4 +33,43 @@ class ProcessBot::Logger
|
|
|
39
33
|
def fp_log
|
|
40
34
|
@fp_log ||= File.open(log_file_path, "a") if log_to_file?
|
|
41
35
|
end
|
|
36
|
+
|
|
37
|
+
def write_output(output, type)
|
|
38
|
+
case type
|
|
39
|
+
when :stdout
|
|
40
|
+
$stdout.print output
|
|
41
|
+
when :stderr
|
|
42
|
+
$stderr.print output
|
|
43
|
+
when :info
|
|
44
|
+
$stdout.print output if logging_enabled?
|
|
45
|
+
when :debug
|
|
46
|
+
$stdout.print output if debug_enabled?
|
|
47
|
+
else
|
|
48
|
+
raise "Unknown type: #{type}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def write_log_file(output)
|
|
53
|
+
fp_log.write(output)
|
|
54
|
+
fp_log.flush
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def debug_enabled?
|
|
58
|
+
truthy_option?(:debug)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def logging_enabled?
|
|
62
|
+
truthy_option?(:log) || truthy_option?(:logging) || debug_enabled?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def truthy_option?(key)
|
|
66
|
+
value = options[key]
|
|
67
|
+
return false if value.nil?
|
|
68
|
+
return value if value == true || value == false
|
|
69
|
+
|
|
70
|
+
normalized = value.to_s.strip.downcase
|
|
71
|
+
return false if normalized == "false" || normalized == "0" || normalized == ""
|
|
72
|
+
|
|
73
|
+
true
|
|
74
|
+
end
|
|
42
75
|
end
|
|
@@ -79,6 +79,7 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def send_tstp_or_return
|
|
82
|
+
logger.logs "Killing process with signal TSTP for PID #{current_pid}"
|
|
82
83
|
Process.kill("TSTP", current_pid)
|
|
83
84
|
true
|
|
84
85
|
rescue Errno::ESRCH
|
|
@@ -101,6 +102,7 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
def terminate_pid(pid)
|
|
105
|
+
logger.logs "Killing process with signal TERM for PID #{pid}"
|
|
104
106
|
Process.kill("TERM", pid)
|
|
105
107
|
rescue Errno::ESRCH
|
|
106
108
|
logger.logs "Sidekiq PID #{pid} is not running - nothing to stop"
|
|
@@ -7,7 +7,7 @@ class ProcessBot::Process::Runner
|
|
|
7
7
|
@command = command
|
|
8
8
|
@handler_instance = handler_instance
|
|
9
9
|
@handler_name = handler_name
|
|
10
|
-
@logger = logger
|
|
10
|
+
@logger = logger || ProcessBot::Logger.new(options: options)
|
|
11
11
|
@monitor = Monitor.new
|
|
12
12
|
@options = options
|
|
13
13
|
end
|
|
@@ -119,7 +119,7 @@ class ProcessBot::Process::Runner
|
|
|
119
119
|
break
|
|
120
120
|
else
|
|
121
121
|
processes.each do |process|
|
|
122
|
-
logger.logs "
|
|
122
|
+
logger.logs "Killing process with signal TERM for PID #{process.pid}: #{process.data.fetch("cmd")}"
|
|
123
123
|
Process.kill("TERM", process.pid)
|
|
124
124
|
end
|
|
125
125
|
|
|
@@ -130,20 +130,28 @@ class ProcessBot::Process::Runner
|
|
|
130
130
|
|
|
131
131
|
def find_sidekiq_pid
|
|
132
132
|
Thread.new do
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@pid = related_sidekiq_process.pid
|
|
137
|
-
options.events.call(:on_process_started, pid: related_sidekiq_process.pid)
|
|
133
|
+
wait_for_sidekiq_pid
|
|
134
|
+
end
|
|
135
|
+
end
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
def wait_for_sidekiq_pid
|
|
138
|
+
while running? && !pid
|
|
139
|
+
assign_related_sidekiq_pid
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
end
|
|
141
|
+
unless pid
|
|
142
|
+
logger.logs "Waiting 1 second before trying to find Sidekiq PID again"
|
|
143
|
+
sleep 1
|
|
146
144
|
end
|
|
147
145
|
end
|
|
148
146
|
end
|
|
147
|
+
|
|
148
|
+
def assign_related_sidekiq_pid
|
|
149
|
+
related_sidekiq_processes.each do |related_sidekiq_process| # rubocop:disable Lint/UnreachableLoop
|
|
150
|
+
logger.logs "Found PID: #{related_sidekiq_process.pid}"
|
|
151
|
+
@pid = related_sidekiq_process.pid
|
|
152
|
+
options.events.call(:on_process_started, pid: related_sidekiq_process.pid)
|
|
153
|
+
|
|
154
|
+
break
|
|
155
|
+
end
|
|
156
|
+
end
|
|
149
157
|
end
|
data/lib/process_bot/process.rb
CHANGED
|
@@ -27,13 +27,12 @@ class ProcessBot::Process
|
|
|
27
27
|
command = options.fetch(:command)
|
|
28
28
|
|
|
29
29
|
if command == "start"
|
|
30
|
+
logger.logs "Starting process"
|
|
30
31
|
start
|
|
31
32
|
elsif command == "graceful" || command == "stop"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
raise e unless options[:ignore_no_process_bot]
|
|
36
|
-
end
|
|
33
|
+
send_control_command(command)
|
|
34
|
+
elsif command == "graceful_no_wait"
|
|
35
|
+
send_control_command(command, wait_for_gracefully_stopped: false)
|
|
37
36
|
else
|
|
38
37
|
raise "Unknown command: #{command}"
|
|
39
38
|
end
|
|
@@ -97,7 +96,7 @@ class ProcessBot::Process
|
|
|
97
96
|
end
|
|
98
97
|
|
|
99
98
|
def stop(**args)
|
|
100
|
-
|
|
99
|
+
logger.logs "Stop process #{args}"
|
|
101
100
|
@stopped = true
|
|
102
101
|
handler_instance.stop
|
|
103
102
|
end
|
|
@@ -106,6 +105,13 @@ class ProcessBot::Process
|
|
|
106
105
|
runner.run
|
|
107
106
|
end
|
|
108
107
|
|
|
108
|
+
def send_control_command(command, **command_options)
|
|
109
|
+
logger.logs "Sending #{command} command"
|
|
110
|
+
client.send_command(command: command, options: options.options.merge(command_options))
|
|
111
|
+
rescue Errno::ECONNREFUSED => e
|
|
112
|
+
raise e unless options[:ignore_no_process_bot]
|
|
113
|
+
end
|
|
114
|
+
|
|
109
115
|
def runner
|
|
110
116
|
@runner ||= ProcessBot::Process::Runner.new(
|
|
111
117
|
command: handler_instance.start_command,
|
data/lib/process_bot/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kaspernj
|
|
@@ -147,6 +147,7 @@ files:
|
|
|
147
147
|
- ".rspec"
|
|
148
148
|
- ".rubocop.yml"
|
|
149
149
|
- ".ruby-version"
|
|
150
|
+
- AGENTS.md
|
|
150
151
|
- CHANGELOG.md
|
|
151
152
|
- Gemfile
|
|
152
153
|
- Gemfile.lock
|