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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29f1d0a6e2f8360306d132467bcb8b562d126f799dd9d4fbc510128b58ef2f44
4
- data.tar.gz: 67cfeb1bb088f7b8acaa543e216a365566993aea81fae49366cc4f19510b54c8
3
+ metadata.gz: 074d3e8a15a3340496bf231b14a3a8ca664bd8c96c22372ee9bf2251740e8eda
4
+ data.tar.gz: 2696dbf343af1d30ca5b4c34b14f69c6278e4d441728b8454943426fe3872112
5
5
  SHA512:
6
- metadata.gz: 0a205be1611136f88f7bb5165f2db1a8296b2bc8a1eec383dcf7b32324a6d8ba98e824b9c29f9c1818b24b412b1964d88ba11ab60e56f291d97279b291140c49
7
- data.tar.gz: b045d94ae4d0d4c92ae6f919411e0fbf33e44ddb4a0606515439c6e0313f73538baaa8674191fb5d37dcf89b81971a509d58007c8ea9d041b30b74d27fdadc2f
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- process_bot (0.1.12)
4
+ process_bot (0.1.13)
5
5
  knjrbfw (>= 0.0.116)
6
6
  pry
7
7
  rake
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
- 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
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
- if command == :graceful && !fetch(:process_bot_wait_for_gracefully_stopped).nil?
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 ||= Socket.tcp("localhost", options.fetch(:port).to_i, connect_timeout: 2)
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)
@@ -10,22 +10,16 @@ class ProcessBot::Logger
10
10
  end
11
11
 
12
12
  def log(output, type: :stdout)
13
- if type == :stdout || (type == :debug && options[:debug])
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
- fp_log.write(output)
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
@@ -48,7 +48,7 @@ class ProcessBot::Process::Handlers::Custom
48
48
  end
49
49
 
50
50
  def stop(**_args)
51
- puts "Stop related processes"
51
+ logger.logs "Stop related processes"
52
52
  process.runner.stop_related_processes
53
53
  end
54
54
  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 "Terminating PID #{process.pid}: #{process.data.fetch("cmd")}"
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
- while running? && !pid
134
- related_sidekiq_processes.each do |related_sidekiq_process| # rubocop:disable Lint/UnreachableLoop
135
- puts "FOUND PID: #{related_sidekiq_process.pid}"
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
- break
140
- end
137
+ def wait_for_sidekiq_pid
138
+ while running? && !pid
139
+ assign_related_sidekiq_pid
141
140
 
142
- unless pid
143
- puts "Waiting 1 second before trying to find Sidekiq PID again"
144
- sleep 1
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
@@ -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
- begin
33
- client.send_command(command: command, options: options.options)
34
- rescue Errno::ECONNREFUSED => e
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
- puts "Stop process #{args}"
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,
@@ -1,3 +1,3 @@
1
1
  module ProcessBot
2
- VERSION = "0.1.12".freeze
2
+ VERSION = "0.1.13".freeze
3
3
  end
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.12
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