process_bot 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b719a497be535319bf027f1669321e58466f845657bdebb82f94d78f6d6e984f
4
- data.tar.gz: cbbbc984e75edacbcdf6e9f8579633e6ebac87cf4e0690b944a7e32b4424c4f1
3
+ metadata.gz: 24914d42c043171014a4dacebea43987204236a2dadb14d2fcc4f8bda936e628
4
+ data.tar.gz: b9248c4189e0d52826728401e940bed7ba46e330fb7f391d587ba00f23b160e3
5
5
  SHA512:
6
- metadata.gz: fdbd9fd4a6196cf6d9183289ec7c9ee984d25e5e2af0af5c8213a5fe649865687aa70f0dcf5a49f91100c7ceae72298e9bb9bdcb9dac9861c86b50209528d1a6
7
- data.tar.gz: 97942320a0a9354770aa8f4e155a620a1c232c5fd712d2269f525d76b1a00adc2480c6c8e51c79c8eb2047a391224c6597e4c9bc74bf4b3369aa0664b7418e1d
6
+ metadata.gz: a92b6568aaf9e799f5d318cd63c900185c4f6cacb4578c7f6fc3769933e571b27b7f48f27d7c33a93ce700d5e3990ea53c0f50a5b39c31ae9153130502c770bc
7
+ data.tar.gz: 3eb45c1dd31b202ac80b0c1b7925e60ed682fd5f42210a768459c6b456d84b9a76cc7618b67ead9f576482ded5b9d47501039d51d158a5125bfab258097c43ff
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- process_bot (0.1.3)
4
+ process_bot (0.1.4)
5
5
  knjrbfw (>= 0.0.116)
6
6
 
7
7
  GEM
@@ -13,7 +13,7 @@ GEM
13
13
  diff-lcs (1.5.0)
14
14
  http2 (0.0.36)
15
15
  string-cases (~> 0)
16
- json (2.6.3)
16
+ json (2.7.0)
17
17
  knjrbfw (0.0.116)
18
18
  datet
19
19
  http2
@@ -21,10 +21,12 @@ GEM
21
21
  ruby_process
22
22
  tsafe
23
23
  wref (>= 0.0.8)
24
+ language_server-protocol (3.17.0.3)
24
25
  method_source (1.0.0)
25
26
  parallel (1.23.0)
26
- parser (3.2.2.0)
27
+ parser (3.2.2.4)
27
28
  ast (~> 2.4.1)
29
+ racc
28
30
  php4r (0.0.4)
29
31
  datet
30
32
  http2
@@ -32,10 +34,11 @@ GEM
32
34
  pry (0.14.2)
33
35
  coderay (~> 1.1)
34
36
  method_source (~> 1.0)
37
+ racc (1.7.3)
35
38
  rainbow (3.1.1)
36
- rake (13.0.6)
37
- regexp_parser (2.8.0)
38
- rexml (3.2.5)
39
+ rake (13.1.0)
40
+ regexp_parser (2.8.2)
41
+ rexml (3.2.6)
39
42
  rspec (3.12.0)
40
43
  rspec-core (~> 3.12.0)
41
44
  rspec-expectations (~> 3.12.0)
@@ -49,28 +52,32 @@ GEM
49
52
  diff-lcs (>= 1.2.0, < 2.0)
50
53
  rspec-support (~> 3.12.0)
51
54
  rspec-support (3.12.0)
52
- rubocop (1.50.2)
55
+ rubocop (1.58.0)
53
56
  json (~> 2.3)
57
+ language_server-protocol (>= 3.17.0)
54
58
  parallel (~> 1.10)
55
- parser (>= 3.2.0.0)
59
+ parser (>= 3.2.2.4)
56
60
  rainbow (>= 2.2.2, < 4.0)
57
61
  regexp_parser (>= 1.8, < 3.0)
58
62
  rexml (>= 3.2.5, < 4.0)
59
- rubocop-ast (>= 1.28.0, < 2.0)
63
+ rubocop-ast (>= 1.30.0, < 2.0)
60
64
  ruby-progressbar (~> 1.7)
61
65
  unicode-display_width (>= 2.4.0, < 3.0)
62
- rubocop-ast (1.28.0)
66
+ rubocop-ast (1.30.0)
63
67
  parser (>= 3.2.1.0)
64
- rubocop-capybara (2.18.0)
68
+ rubocop-capybara (2.19.0)
65
69
  rubocop (~> 1.41)
66
- rubocop-performance (1.17.1)
70
+ rubocop-factory_bot (2.24.0)
71
+ rubocop (~> 1.33)
72
+ rubocop-performance (1.19.1)
67
73
  rubocop (>= 1.7.0, < 2.0)
68
74
  rubocop-ast (>= 0.4.0)
69
75
  rubocop-rake (0.6.0)
70
76
  rubocop (~> 1.0)
71
- rubocop-rspec (2.20.0)
72
- rubocop (~> 1.33)
77
+ rubocop-rspec (2.25.0)
78
+ rubocop (~> 1.40)
73
79
  rubocop-capybara (~> 2.17)
80
+ rubocop-factory_bot (~> 2.22)
74
81
  ruby-progressbar (1.13.0)
75
82
  ruby_process (0.0.13)
76
83
  string-cases
@@ -79,7 +86,7 @@ GEM
79
86
  string-cases (0.0.4)
80
87
  string-strtr (0.0.3)
81
88
  tsafe (0.0.12)
82
- unicode-display_width (2.4.2)
89
+ unicode-display_width (2.5.0)
83
90
  wref (0.0.8)
84
91
 
85
92
  PLATFORMS
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
 
@@ -32,16 +32,34 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
32
32
  end
33
33
  end
34
34
 
35
- def process_bot_command(process_bot_data, command)
35
+ def process_bot_command(process_bot_data, command) # rubocop:disable Metrics/AbcSize
36
36
  raise "No port in process bot data? #{process_bot_data}" unless process_bot_data["port"]
37
37
 
38
- backend_command = "cd #{release_path} && " \
39
- "#{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot " \
40
- "--command #{command} " \
41
- "--port #{process_bot_data.fetch("port")}"
38
+ mode = "exec"
42
39
 
43
- if command == :graceful && !fetch(:process_bot_wait_for_gracefully_stopped).nil?
44
- backend_command << " --wait-for-gracefully-stopped #{fetch(:process_bot_wait_for_gracefully_stopped)}"
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
46
+
47
+ escaped_args = JSON.generate(args).gsub("\"", "\\\"")
48
+ rails_runner_command = "require 'process_bot'; ProcessBot::Process.new(ProcessBot::Options.from_args(#{escaped_args})).execute!"
49
+
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}"
45
63
  end
46
64
 
47
65
  backend.execute backend_command
@@ -126,7 +144,7 @@ module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLe
126
144
  screen_args = ["-dmS process-bot--sidekiq--#{idx}-#{latest_release_version}"]
127
145
 
128
146
  if (process_bot_sidekiq_log = fetch(:process_bot_sidekig_log))
129
- screen_args << "-L -Logfile #{process_bot_sidekiq_log}"
147
+ screen_args << "-L -Logfile #{process_bot_sidekiq_log}_#{latest_release_version}_#{idx}.log"
130
148
  elsif fetch(:sidekiq_log)
131
149
  screen_args << "-L -Logfile #{fetch(:sidekiq_log)}"
132
150
  end
@@ -8,14 +8,19 @@ class ProcessBot::ClientSocket
8
8
  end
9
9
 
10
10
  def client
11
- @client ||= TCPSocket.new("localhost", options.fetch(:port).to_i)
11
+ @client ||= Socket.tcp("localhost", options.fetch(:port).to_i, connect_timeout: 2)
12
12
  end
13
13
 
14
14
  def close
15
15
  client.close
16
16
  end
17
17
 
18
- def send_command(data)
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}"
19
24
  client.puts(JSON.generate(data))
20
25
  response_raw = client.gets
21
26
 
@@ -26,6 +31,11 @@ class ProcessBot::ClientSocket
26
31
 
27
32
  return :success if response.fetch("type") == "success"
28
33
 
29
- raise "Command raised an error: #{response.fetch("message")}" if response.fetch("type") == "error"
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
30
40
  end
31
41
  end
@@ -8,6 +8,10 @@ class ProcessBot::ControlSocket
8
8
  @process = process
9
9
  end
10
10
 
11
+ def logger
12
+ @logger ||= ProcessBot::Logger.new(options: options)
13
+ end
14
+
11
15
  def port
12
16
  options.fetch(:port).to_i
13
17
  end
@@ -16,7 +20,7 @@ class ProcessBot::ControlSocket
16
20
  @server = TCPServer.new("localhost", port)
17
21
  run_client_loop
18
22
 
19
- puts "TCPServer started"
23
+ logger.logs "TCPServer started"
20
24
 
21
25
  options.events.call(:on_socket_opened, port: port)
22
26
  end
@@ -45,16 +49,38 @@ class ProcessBot::ControlSocket
45
49
 
46
50
  if command_type == "graceful" || command_type == "stop"
47
51
  begin
48
- process.__send__(command_type)
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}"
59
+
60
+ process.__send__(command_type, **command_options)
49
61
  client.puts(JSON.generate(type: "success"))
50
62
  rescue => e # rubocop:disable Style/RescueStandardError
51
- client.puts(JSON.generate(type: "error", message: e.message))
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))
52
67
 
53
68
  raise e
54
69
  end
55
70
  else
56
- client.puts(JSON.generate(type: "error", message: "Unknown command: #{command_type}"))
71
+ client.puts(JSON.generate(type: "error", message: "Unknown command: #{command_type}", backtrace: Thread.current.backtrace))
57
72
  end
58
73
  end
59
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
82
+ end
83
+
84
+ new_hash
85
+ end
60
86
  end
@@ -6,7 +6,7 @@ class ProcessBot::Logger
6
6
  end
7
7
 
8
8
  def log(output, type: :stdout)
9
- if type == :stdout
9
+ if type == :stdout || (type == :debug && options[:debug])
10
10
  $stdout.print output
11
11
  elsif type == :stderr
12
12
  $stderr.print output
@@ -20,6 +20,10 @@ class ProcessBot::Logger
20
20
  fp_log.flush
21
21
  end
22
22
 
23
+ def logs(output, **args)
24
+ log("#{output}\n", **args)
25
+ end
26
+
23
27
  def log_file_path
24
28
  options.fetch(:log_file_path)
25
29
  end
@@ -1,6 +1,16 @@
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
@@ -1,14 +1,38 @@
1
1
  class ProcessBot::Process::Handlers::Sidekiq
2
- attr_reader :options
2
+ attr_reader :options, :process
3
3
 
4
- def initialize(options)
5
- @options = options
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
20
+
21
+ Process.detach(pid) if pid
22
+ end
23
+
24
+ def false_value?(value)
25
+ !value || value == "false"
6
26
  end
7
27
 
8
28
  def fetch(*args, **opts)
9
29
  options.fetch(*args, **opts)
10
30
  end
11
31
 
32
+ def logger
33
+ @logger ||= ProcessBot::Logger.new(options: options)
34
+ end
35
+
12
36
  def set_option(key, value)
13
37
  raise "Unknown option for Sidekiq handler: #{key}" unless options.key?(key)
14
38
 
@@ -42,4 +66,72 @@ class ProcessBot::Process::Handlers::Sidekiq
42
66
  command << "'"
43
67
  command
44
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
45
137
  end
@@ -27,7 +27,7 @@ class ProcessBot::Process::Runner
27
27
 
28
28
  PTY.spawn(command, err: stderr_writer.fileno) do |stdout, _stdin, pid|
29
29
  @subprocess_pid = pid
30
- logger.log "Command running with PID #{pid}: #{command}\n"
30
+ logger.logs "Command running with PID #{pid}: #{command}"
31
31
 
32
32
  stdout_reader_thread = Thread.new do
33
33
  stdout.each_char do |chunk|
@@ -1,6 +1,13 @@
1
+ require "forwardable"
1
2
  require "json"
3
+ require "string-cases"
2
4
 
3
5
  class ProcessBot::Process
6
+ extend Forwardable
7
+
8
+ def_delegator :handler_instance, :graceful
9
+ def_delegator :handler_instance, :stop
10
+
4
11
  autoload :Handlers, "#{__dir__}/process/handlers"
5
12
  autoload :Runner, "#{__dir__}/process/runner"
6
13
 
@@ -13,7 +20,7 @@ class ProcessBot::Process
13
20
  options.events.connect(:on_process_started, &method(:on_process_started)) # rubocop:disable Performance/MethodObjectAsBlock
14
21
  options.events.connect(:on_socket_opened, &method(:on_socket_opened)) # rubocop:disable Performance/MethodObjectAsBlock
15
22
 
16
- logger.log("Options: #{options.options}")
23
+ logger.logs("ProcessBot 1 - Options: #{options.options}")
17
24
  end
18
25
 
19
26
  def execute!
@@ -22,7 +29,7 @@ class ProcessBot::Process
22
29
  if command == "start"
23
30
  start
24
31
  elsif command == "graceful" || command == "stop"
25
- client.send_command(command: command)
32
+ client.send_command(command: command, options: options.options)
26
33
  else
27
34
  raise "Unknown command: #{command}"
28
35
  end
@@ -39,6 +46,10 @@ class ProcessBot::Process
39
46
  end
40
47
  end
41
48
 
49
+ def handler_instance
50
+ @handler_instance ||= handler_class.new(self)
51
+ end
52
+
42
53
  def handler_name
43
54
  @handler_name ||= options.fetch(:handler)
44
55
  end
@@ -71,42 +82,17 @@ class ProcessBot::Process
71
82
  if stopped
72
83
  break
73
84
  else
74
- puts "Process stopped - starting again after 1 sec"
85
+ logger.logs "Process stopped - starting again after 1 sec"
75
86
  sleep 1
76
87
  end
77
88
  end
78
89
  end
79
90
 
80
- def graceful
81
- @stopped = true
82
-
83
- unless current_pid
84
- warn "#{handler_name} not running with a PID"
85
- return
86
- end
87
-
88
- Process.kill("TSTP", current_pid)
89
-
90
- if options[:wait_for_gracefully_stopped] == "false"
91
- Thread.new { wait_for_no_jobs_and_stop_sidekiq }
92
- else
93
- wait_for_no_jobs_and_stop_sidekiq
94
- end
95
- end
96
-
97
- def stop
91
+ def set_stopped
98
92
  @stopped = true
99
-
100
- unless current_pid
101
- warn "#{handler_name} not running with a PID"
102
- return
103
- end
104
-
105
- Process.kill("TERM", current_pid)
106
93
  end
107
94
 
108
95
  def run
109
- handler_instance = handler_class.new(options)
110
96
  runner = ProcessBot::Process::Runner.new(command: handler_instance.start_command, logger: logger, options: options)
111
97
  runner.run
112
98
  end
@@ -116,38 +102,4 @@ class ProcessBot::Process
116
102
  @current_process_title = "ProcessBot #{JSON.generate(process_args)}"
117
103
  Process.setproctitle(current_process_title)
118
104
  end
119
-
120
- def wait_for_no_jobs # rubocop:disable Metrics/AbcSize
121
- loop do
122
- found_process = false
123
-
124
- Knj::Unix_proc.list("grep" => current_pid) do |process|
125
- process_command = process.data.fetch("cmd")
126
- process_pid = process.data.fetch("pid").to_i
127
- next unless process_pid == current_pid
128
-
129
- found_process = true
130
- sidekiq_regex = /\Asidekiq (\d+).(\d+).(\d+) (#{options.possible_process_titles_joined_regex}) \[(\d+) of (\d+)(\]|) (.+?)(\]|)\Z/
131
- match = process_command.match(sidekiq_regex)
132
- raise "Couldnt match Sidekiq command: #{process_command} with Sidekiq regex: #{sidekiq_regex}" unless match
133
-
134
- running_jobs = match[5].to_i
135
-
136
- puts "running_jobs: #{running_jobs}"
137
-
138
- return if running_jobs.zero? # rubocop:disable Lint/NonLocalExitFromIterator
139
- end
140
-
141
- raise "Couldn't find running process with PID #{current_pid}" unless found_process
142
-
143
- sleep 1
144
- end
145
- end
146
-
147
- def wait_for_no_jobs_and_stop_sidekiq
148
- puts "Wait for no jobs and Stop sidekiq"
149
-
150
- wait_for_no_jobs
151
- stop
152
- end
153
105
  end
@@ -1,3 +1,3 @@
1
1
  module ProcessBot
2
- VERSION = "0.1.3".freeze
2
+ VERSION = "0.1.4".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
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: 2023-07-11 00:00:00.000000000 Z
11
+ date: 2023-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: knjrbfw
@@ -86,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  requirements: []
89
- rubygems_version: 3.1.6
89
+ rubygems_version: 3.4.17
90
90
  signing_key:
91
91
  specification_version: 4
92
92
  summary: Run and control processes.