process_bot 0.1.30 → 0.1.34

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: 5e091db49a7e39df21edb300dc0ebb57cb5b98a73be033b220d24f72a7f72c36
4
- data.tar.gz: 59e9442735c2a07cc568e8f9b0f1ed3c3660fa407d1f85ed7326348722ed7dbc
3
+ metadata.gz: aa6d1ed1467a0ed8b7f59f249d4bd8223e5f4a9a3de588672bb1c288c8f9958a
4
+ data.tar.gz: 75e6da0f1d23bbbb54fcc0c0ad4d1feab4eeecb8ad1360bb0ab574a0ab541e0a
5
5
  SHA512:
6
- metadata.gz: 3459f12d21d2d221c152ed047cd51dc4c9134e285cfc52847997099ace48a1ddd60fc8c7c54d5a6b6ca8535b2054e47f9ae5b72da010188e56eca304194b30e6
7
- data.tar.gz: 8b7bd9482806698b84f3ea356b00c2f71d58850249fb0e2aabb28591495094e727324eb6bad5f0e5ee4740adef932c58f91f232d90e8ea8488f7f453a9bde138
6
+ metadata.gz: 8937bdd16d154473da1b528c9b99ef5f0ec606201615cb4669e3b251bfa01d3d67edd835e01b91709824a4c660ebda3d54e6db8d43f58804804ccfdd807a20e4
7
+ data.tar.gz: 9dfad8adee572aed628f93fabb211f2e76d9372a201804e89b00e7a8ef5af92683d21ef29db7d34d231b131bb06f2629b776aac6be01e2e0c901d6980020f01d
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.8
1
+ 3.2.5
data/CHANGELOG.md CHANGED
@@ -1,6 +1,8 @@
1
1
  ## [Unreleased]
2
+ - Add `process_bot:sidekiq:start_current` so failed deploys restart Sidekiq from the currently published release.
2
3
  - Drop Ruby 2.x support. Minimum Ruby is now 3.2; CI covers 3.2/3.3/3.4 and RuboCop's `TargetRubyVersion` follows. Shorthand anonymous forwarding (`*, **, &`) is applied where it replaces redundant `*args, **opts, &block` pass-throughs.
3
4
  - Fail `start_tcp_server` when another ProcessBot with the same `--id` is already running instead of silently drifting to a new port. Port drift across unrelated services is still supported; drift for a duplicate id was the root cause of Capistrano deploys leaving a stale previous-release ProcessBot alive on a drifted port while every subsequent `stop --port X` hit the wrong instance.
5
+ - Treat legacy ProcessBot titles without `application_basename` as duplicates when they share the same `--id` and control port, and include the same legacy match in forced stop diagnostics.
4
6
  - Stop accepting new control commands during shutdown so in-flight responses complete reliably.
5
7
  - Stream ProcessBot logs to connected control clients for Capistrano output.
6
8
  - Sanitize broadcast log output to keep JSON encoding safe.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- process_bot (0.1.30)
4
+ process_bot (0.1.34)
5
5
  knjrbfw (>= 0.0.116)
6
6
  pry
7
7
  rake
@@ -17,8 +17,8 @@ GEM
17
17
  diff-lcs (1.6.2)
18
18
  http2 (0.0.36)
19
19
  string-cases (~> 0)
20
- io-console (0.8.2)
21
- json (2.19.3)
20
+ io-console (0.9.2)
21
+ json (2.21.2)
22
22
  knjrbfw (0.0.116)
23
23
  datet
24
24
  http2
@@ -26,11 +26,11 @@ GEM
26
26
  ruby_process
27
27
  tsafe
28
28
  wref (>= 0.0.8)
29
- language_server-protocol (3.17.0.5)
29
+ language_server-protocol (3.17.0.6)
30
30
  lint_roller (1.1.0)
31
31
  method_source (1.1.0)
32
32
  parallel (1.28.0)
33
- parser (3.3.11.1)
33
+ parser (3.3.12.0)
34
34
  ast (~> 2.4.1)
35
35
  racc
36
36
  php4r (0.0.4)
@@ -47,7 +47,7 @@ GEM
47
47
  rake (13.4.2)
48
48
  ref (2.0.0)
49
49
  regexp_parser (2.12.0)
50
- reline (0.6.3)
50
+ reline (0.7.0)
51
51
  io-console (~> 0.5)
52
52
  rspec (3.13.2)
53
53
  rspec-core (~> 3.13.0)
@@ -62,7 +62,7 @@ GEM
62
62
  diff-lcs (>= 1.2.0, < 2.0)
63
63
  rspec-support (~> 3.13.0)
64
64
  rspec-support (3.13.7)
65
- rubocop (1.86.1)
65
+ rubocop (1.89.0)
66
66
  json (~> 2.3)
67
67
  language_server-protocol (~> 3.17.0.2)
68
68
  lint_roller (~> 1.1.0)
@@ -73,12 +73,12 @@ GEM
73
73
  rubocop-ast (>= 1.49.0, < 2.0)
74
74
  ruby-progressbar (~> 1.7)
75
75
  unicode-display_width (>= 2.4.0, < 4.0)
76
- rubocop-ast (1.49.1)
76
+ rubocop-ast (1.50.0)
77
77
  parser (>= 3.3.7.2)
78
78
  prism (~> 1.7)
79
- rubocop-performance (1.26.1)
79
+ rubocop-performance (1.27.0)
80
80
  lint_roller (~> 1.1)
81
- rubocop (>= 1.75.0, < 2.0)
81
+ rubocop (>= 1.89.0, < 2.0)
82
82
  rubocop-ast (>= 1.47.1, < 2.0)
83
83
  rubocop-rake (0.7.1)
84
84
  lint_roller (~> 1.1)
data/README.md CHANGED
@@ -26,7 +26,7 @@ Add to your `deploy.rb`:
26
26
  ```ruby
27
27
  after "deploy:starting", "process_bot:sidekiq:graceful"
28
28
  after "deploy:published", "process_bot:sidekiq:start"
29
- after "deploy:failed", "process_bot:sidekiq:start"
29
+ after "deploy:failed", "process_bot:sidekiq:start_current"
30
30
  ```
31
31
 
32
32
  ## Usage
@@ -41,6 +41,7 @@ cap production process_bot:sidekiq:graceful
41
41
 
42
42
  ProcessBot provides these Sidekiq tasks:
43
43
  - `process_bot:sidekiq:start`
44
+ - `process_bot:sidekiq:start_current` (starts from the currently published release after a failed deploy)
44
45
  - `process_bot:sidekiq:stop`
45
46
  - `process_bot:sidekiq:graceful` (stops fetching new jobs and waits for running jobs by default)
46
47
  - `process_bot:sidekiq:graceful_no_wait` (stops fetching new jobs and returns immediately)
@@ -71,6 +71,18 @@ namespace :process_bot do
71
71
  end
72
72
  end
73
73
 
74
+ desc "Start Sidekiq and ProcessBot from the currently published release"
75
+ task :start_current do
76
+ on roles fetch(:sidekiq_roles) do |role|
77
+ git_plugin.switch_user(role) do
78
+ fetch(:sidekiq_processes).times do |idx|
79
+ puts "Starting Sidekiq with ProcessBot #{idx} from the currently published release"
80
+ git_plugin.start_current_sidekiq(idx)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
74
86
  desc "Ensure the configured number of Sidekiq ProcessBots are running (starts replacements for graceful shutdowns)"
75
87
  task :ensure_running do
76
88
  on roles fetch(:sidekiq_roles) do |role|
@@ -140,12 +140,26 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
140
140
  backend.capture(:echo, SSHKit.config.command_map[:bundle]).strip
141
141
  end
142
142
 
143
- def start_sidekiq(idx = 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
143
+ def start_current_sidekiq(idx = 0)
144
+ current_release_path = backend.capture(:readlink, "-f", current_path).strip
145
+
146
+ start_sidekiq(
147
+ idx,
148
+ sidekiq_release_path: current_release_path,
149
+ sidekiq_release_version: File.basename(current_release_path)
150
+ )
151
+ end
152
+
153
+ def start_sidekiq( # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
154
+ idx = 0,
155
+ sidekiq_release_path: release_path,
156
+ sidekiq_release_version: nil
157
+ )
144
158
  releases = backend.capture(:ls, "-x", releases_path).split
145
159
  releases << release_timestamp.to_s if release_timestamp
146
160
  releases.uniq
147
161
 
148
- latest_release_version = releases.last
162
+ latest_release_version = sidekiq_release_version || releases.last
149
163
  raise "Invalid release timestamp: #{release_timestamp}" unless latest_release_version
150
164
 
151
165
  args = [
@@ -156,7 +170,7 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
156
170
  "--bundle-prefix", SSHKit.config.command_map.prefix[:bundle].join(" "),
157
171
  "--sidekiq-environment", fetch(:sidekiq_env),
158
172
  "--port", idx + 7050,
159
- "--release-path", release_path
173
+ "--release-path", sidekiq_release_path
160
174
  ]
161
175
 
162
176
  # Use screen for logging everything which is why this is disabled
@@ -184,7 +198,8 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
184
198
  process_bot_args = args.compact.map { |arg| "\"#{arg}\"" }
185
199
 
186
200
  command = "/usr/bin/screen #{screen_args.join(" ")} " \
187
- "bash -c 'cd #{release_path} && exec #{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot #{process_bot_args.join(" ")}'"
201
+ "bash -c 'cd #{sidekiq_release_path} && exec " \
202
+ "#{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot #{process_bot_args.join(" ")}'"
188
203
 
189
204
  puts "WARNING: A known bug prevents Sidekiq from starting when pty is set (which it is)" if fetch(:pty)
190
205
  puts "ProcessBot Sidekiq command: #{command}"
@@ -74,10 +74,23 @@ class ProcessBot::ControlSocket
74
74
 
75
75
  def find_duplicate_id_entries(id, basename)
76
76
  running_process_bot_entries.select do |entry|
77
- entry[:id] == id && entry[:application_basename] == basename
77
+ duplicate_id_entry_matches?(entry, id, basename)
78
78
  end
79
79
  end
80
80
 
81
+ def duplicate_id_entry_matches?(entry, id, basename)
82
+ return false unless entry[:id] == id
83
+ return true if basename && entry[:application_basename] == basename
84
+
85
+ entry[:application_basename].nil? && same_control_port?(entry)
86
+ end
87
+
88
+ def same_control_port?(entry)
89
+ return false unless entry[:port] && options[:port]
90
+
91
+ entry[:port].to_i == options[:port].to_i
92
+ end
93
+
81
94
  def duplicate_id_error_message(id, duplicates)
82
95
  details = duplicates.map { |entry| "PID #{entry[:pid]} on port #{entry[:port]}" }.join(", ")
83
96
  example_port = duplicates.first[:port]
@@ -235,15 +248,19 @@ class ProcessBot::ControlSocket
235
248
  next
236
249
  end
237
250
 
238
- pid = process.data["pid"] || process.pid
239
- entries << {
240
- application_basename: process_data["application_basename"],
241
- id: process_data["id"]&.to_s,
242
- pid: pid,
243
- port: process_data["port"]&.to_i
244
- }
251
+ entries << process_bot_entry_from_process_data(process, process_data)
245
252
  end
246
253
 
247
254
  entries
248
255
  end
256
+
257
+ def process_bot_entry_from_process_data(process, process_data)
258
+ {
259
+ application: process_data["application"],
260
+ application_basename: process_data["application_basename"],
261
+ id: process_data["id"]&.to_s,
262
+ pid: process.data["pid"] || process.pid,
263
+ port: process_data["port"]&.to_i
264
+ }
265
+ end
249
266
  end
@@ -36,17 +36,19 @@ class ProcessBot::Options
36
36
  end
37
37
 
38
38
  def application_basename
39
- @application_basename ||= begin
40
- app_path_parts = release_path.split("/")
39
+ @application_basename ||= application_basename_from_release_path
40
+ end
41
41
 
42
- if release_path.include?("/releases/")
43
- app_path_parts.pop(2)
44
- elsif release_path.end_with?("/current")
45
- app_path_parts.pop
46
- end
42
+ def application_basename_from_release_path
43
+ app_path_parts = release_path.split("/")
44
+ deployment_marker_index = [
45
+ app_path_parts.rindex("releases"),
46
+ app_path_parts.rindex("current")
47
+ ].compact.max
47
48
 
48
- app_path_parts.last
49
- end
49
+ return app_path_parts[0...deployment_marker_index].last if deployment_marker_index
50
+
51
+ app_path_parts.last
50
52
  end
51
53
 
52
54
  def possible_process_titles
@@ -334,9 +334,35 @@ class ProcessBot::Process # rubocop:disable Metrics/ClassLength
334
334
  end
335
335
 
336
336
  def process_bot_process_line_matches?(line)
337
- line.include?("ProcessBot {") &&
338
- line.include?("\"application\":\"#{options[:application]}\"") &&
339
- line.include?("\"id\":\"#{options[:id]}\"")
337
+ process_data = process_bot_data_from_line(line)
338
+ return false unless process_data
339
+ return false unless process_data["id"]&.to_s == options[:id].to_s
340
+
341
+ process_bot_application_matches?(process_data) || legacy_process_bot_port_matches?(process_data)
342
+ end
343
+
344
+ def process_bot_data_from_line(line)
345
+ match = line.match(/ProcessBot (\{.+\})/)
346
+ return unless match
347
+
348
+ JSON.parse(match[1])
349
+ rescue JSON::ParserError
350
+ nil
351
+ end
352
+
353
+ def process_bot_application_matches?(process_data)
354
+ application = options[:application]
355
+ return true if application && process_data["application"] == application
356
+
357
+ basename = safe_application_basename
358
+ basename && process_data["application_basename"] == basename
359
+ end
360
+
361
+ def legacy_process_bot_port_matches?(process_data)
362
+ return false unless process_data["port"] && options[:port]
363
+
364
+ process_data["application_basename"].nil? &&
365
+ process_data["port"].to_i == options[:port].to_i
340
366
  end
341
367
 
342
368
  def force_stop_process_bot_if_configured(matching_processes)
@@ -1,3 +1,3 @@
1
1
  module ProcessBot
2
- VERSION = "0.1.30".freeze
2
+ VERSION = "0.1.34".freeze
3
3
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.30
4
+ version: 0.1.34
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-08-26 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: knjrbfw
@@ -186,6 +187,7 @@ metadata:
186
187
  source_code_uri: https://github.com/kaspernj/process_bot
187
188
  changelog_uri: https://github.com/kaspernj/process_bot/blob/master/CHANGELOG.md
188
189
  rubygems_mfa_required: 'true'
190
+ post_install_message:
189
191
  rdoc_options: []
190
192
  require_paths:
191
193
  - lib
@@ -200,7 +202,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
202
  - !ruby/object:Gem::Version
201
203
  version: '0'
202
204
  requirements: []
203
- rubygems_version: 3.6.9
205
+ rubygems_version: 3.4.19
206
+ signing_key:
204
207
  specification_version: 4
205
208
  summary: Run and control processes.
206
209
  test_files: []