exekutor 0.1.0 → 0.1.1

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/exe/exekutor +2 -2
  4. data/lib/active_job/queue_adapters/exekutor_adapter.rb +2 -1
  5. data/lib/exekutor/asynchronous.rb +143 -75
  6. data/lib/exekutor/cleanup.rb +27 -28
  7. data/lib/exekutor/configuration.rb +48 -25
  8. data/lib/exekutor/hook.rb +15 -11
  9. data/lib/exekutor/info/worker.rb +3 -3
  10. data/lib/exekutor/internal/base_record.rb +2 -1
  11. data/lib/exekutor/internal/callbacks.rb +55 -35
  12. data/lib/exekutor/internal/cli/app.rb +31 -23
  13. data/lib/exekutor/internal/cli/application_loader.rb +17 -6
  14. data/lib/exekutor/internal/cli/cleanup.rb +54 -40
  15. data/lib/exekutor/internal/cli/daemon.rb +9 -11
  16. data/lib/exekutor/internal/cli/default_option_value.rb +3 -1
  17. data/lib/exekutor/internal/cli/info.rb +117 -84
  18. data/lib/exekutor/internal/cli/manager.rb +190 -123
  19. data/lib/exekutor/internal/configuration_builder.rb +40 -27
  20. data/lib/exekutor/internal/database_connection.rb +6 -0
  21. data/lib/exekutor/internal/executable.rb +12 -7
  22. data/lib/exekutor/internal/executor.rb +50 -21
  23. data/lib/exekutor/internal/hooks.rb +11 -8
  24. data/lib/exekutor/internal/listener.rb +66 -39
  25. data/lib/exekutor/internal/logger.rb +28 -10
  26. data/lib/exekutor/internal/provider.rb +93 -74
  27. data/lib/exekutor/internal/reserver.rb +27 -12
  28. data/lib/exekutor/internal/status_server.rb +81 -49
  29. data/lib/exekutor/job.rb +1 -1
  30. data/lib/exekutor/job_error.rb +1 -1
  31. data/lib/exekutor/job_options.rb +22 -13
  32. data/lib/exekutor/plugins/appsignal.rb +7 -5
  33. data/lib/exekutor/plugins.rb +8 -4
  34. data/lib/exekutor/queue.rb +40 -22
  35. data/lib/exekutor/version.rb +1 -1
  36. data/lib/exekutor/worker.rb +88 -47
  37. data/lib/exekutor.rb +2 -2
  38. data/lib/generators/exekutor/configuration_generator.rb +9 -5
  39. data/lib/generators/exekutor/install_generator.rb +26 -15
  40. data/lib/generators/exekutor/templates/install/migrations/create_exekutor_schema.rb.erb +11 -10
  41. data.tar.gz.sig +0 -0
  42. metadata +63 -19
  43. metadata.gz.sig +0 -0
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "application_loader"
2
4
  require "terminal-table"
3
5
 
@@ -19,96 +21,128 @@ module Exekutor
19
21
  load_application(options[:environment], print_message: !quiet?)
20
22
 
21
23
  ActiveSupport.on_load(:active_record, yield: true) do
22
- # Use system time zone
23
- Time.zone = Time.new.zone
24
+ clear_application_loading_message
25
+ print_time_zone_warning if different_time_zone? && !quiet?
24
26
 
25
27
  hosts = Exekutor::Info::Worker.distinct.pluck(:hostname)
26
- job_info = Exekutor::Job.pending.order(:queue).group(:queue)
27
- .pluck(:queue, Arel.sql("COUNT(*)"), Arel.sql("MIN(scheduled_at)"))
28
-
29
- clear_application_loading_message unless quiet?
30
- puts Rainbow("Workers").bright.blue
31
- if hosts.present?
32
- total_workers = 0
33
- hosts.each do |host|
34
- table = Terminal::Table.new
35
- table.title = host if hosts.many?
36
- table.headings = ["id", "Status", "Last heartbeat"]
37
- worker_count = 0
38
- Exekutor::Info::Worker.where(hostname: host).each do |worker|
39
- worker_count += 1
40
- table << [
41
- worker.id.split("-").first << "…",
42
- worker.status,
43
- if worker.last_heartbeat_at.nil?
44
- if !worker.running?
45
- "N/A"
46
- elsif worker.created_at < 10.minutes.ago
47
- Rainbow("None").red
48
- else
49
- "None"
50
- end
51
- elsif worker.last_heartbeat_at > 2.minutes.ago
52
- worker.last_heartbeat_at.strftime "%R"
53
- elsif worker.last_heartbeat_at > 10.minutes.ago
54
- Rainbow(worker.last_heartbeat_at.strftime("%R")).yellow
55
- else
56
- Rainbow(worker.last_heartbeat_at.strftime("%D %R")).red
57
- end
58
- ]
59
- # TODO switch / flag to print threads and queues
60
- end
61
- total_workers += worker_count
62
- table.add_separator
63
- table.add_row [(hosts.many? ? "Subtotal" : "Total"), { value: worker_count, alignment: :right, colspan: 2 }]
64
- puts table
65
- end
66
-
67
- if hosts.many?
68
- puts Terminal::Table.new rows: [
69
- ["Total hosts", hosts.size],
70
- ["Total workers", total_workers]
71
- ]
72
- end
73
- else
74
- message = Rainbow("There are no active workers")
75
- message = message.red if job_info.present?
76
- puts message
28
+ job_info = pending_jobs_per_queue
29
+
30
+ print_workers(hosts, job_info.present?, options)
31
+ puts
32
+ print_jobs(job_info)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def pending_jobs_per_queue
39
+ Exekutor::Job.pending.order(:queue).group(:queue)
40
+ .pluck(:queue, Arel.sql("COUNT(*)"), Arel.sql("MIN(scheduled_at)"))
41
+ end
42
+
43
+ def print_jobs(job_info)
44
+ puts Rainbow("Jobs").bright.blue
45
+ if job_info.present?
46
+ puts create_job_info_table(job_info)
47
+ else
48
+ puts Rainbow("No pending jobs").green
49
+ end
50
+ end
51
+
52
+ def create_job_info_table(job_info)
53
+ Terminal::Table.new(headings: ["Queue", "Pending jobs", "Next job scheduled at"]).tap do |table|
54
+ total_count = 0
55
+ job_info.each do |queue, count, min_scheduled_at|
56
+ table << [queue, { value: count, alignment: :right }, format_scheduled_at(min_scheduled_at)]
57
+ total_count += count
58
+ end
59
+ if job_info.many?
60
+ table.add_separator
61
+ table << ["Total", { value: total_count, alignment: :right, colspan: 2 }]
62
+ end
63
+ end
64
+ end
65
+
66
+ def format_scheduled_at(min_scheduled_at)
67
+ if min_scheduled_at.nil?
68
+ "N/A"
69
+ elsif min_scheduled_at < 30.minutes.ago
70
+ Rainbow(min_scheduled_at.strftime("%D %R")).red
71
+ elsif min_scheduled_at < 1.minute.ago
72
+ Rainbow(min_scheduled_at.strftime("%D %R")).yellow
73
+ else
74
+ min_scheduled_at.strftime("%D %R")
75
+ end
76
+ end
77
+
78
+ def print_workers(hosts, has_pending_jobs, options)
79
+ puts Rainbow("Workers").bright.blue
80
+ if hosts.present?
81
+ total_workers = 0
82
+ hosts.each do |host|
83
+ total_workers += print_host_info(host, options.merge(many_hosts: hosts.many?))
77
84
  end
78
85
 
79
- puts " "
80
- puts "#{Rainbow("Jobs").bright.blue}"
81
- if job_info.present?
82
- table = Terminal::Table.new
83
- table.headings = ["Queue", "Pending jobs", "Next job scheduled at"]
84
- total_count = 0
85
- job_info.each do |queue, count, min_scheduled_at|
86
- table << [
87
- queue, count,
88
- if min_scheduled_at.nil?
89
- "N/A"
90
- elsif min_scheduled_at < 30.minutes.ago
91
- Rainbow(min_scheduled_at.strftime("%D %R")).red
92
- elsif min_scheduled_at < 1.minute.ago
93
- Rainbow(min_scheduled_at.strftime("%D %R")).yellow
94
- else
95
- min_scheduled_at.strftime("%D %R")
96
- end
97
- ]
98
- total_count += count
99
- end
100
- if job_info.many?
101
- table.add_separator
102
- table.add_row ["Total", { value: total_count, alignment: :right, colspan: 2 }]
103
- end
104
- puts table
105
- else
106
- puts Rainbow("No pending jobs").green
86
+ if hosts.many?
87
+ puts Terminal::Table.new rows: [
88
+ ["Total hosts", hosts.size],
89
+ ["Total workers", total_workers]
90
+ ]
107
91
  end
92
+ else
93
+ message = Rainbow("There are no active workers")
94
+ message = message.red if has_pending_jobs
95
+ puts message
108
96
  end
109
97
  end
110
98
 
111
- private
99
+ def print_host_info(host, options)
100
+ many_hosts = options[:many_hosts]
101
+ table = Terminal::Table.new headings: ["id", "Status", "Last heartbeat"]
102
+ table.title = host if many_hosts
103
+ worker_count = 0
104
+ Exekutor::Info::Worker.where(hostname: host).each do |worker|
105
+ worker_count += 1
106
+ table << worker_info_row(worker)
107
+ end
108
+ table.add_separator
109
+ table.add_row [(many_hosts ? "Subtotal" : "Total"),
110
+ { value: worker_count, alignment: :right, colspan: 2 }]
111
+ puts table
112
+ worker_count
113
+ end
114
+
115
+ def worker_info_row(worker)
116
+ [
117
+ worker.id.split("-").first << "…",
118
+ worker.status,
119
+ worker_heartbeat_column(worker)
120
+ ]
121
+ end
122
+
123
+ def worker_heartbeat_column(worker)
124
+ last_heartbeat_at = worker.last_heartbeat_at
125
+ if last_heartbeat_at
126
+ colorize_heartbeat(last_heartbeat_at)
127
+ elsif !worker.running?
128
+ "N/A"
129
+ elsif worker.started_at < 10.minutes.ago
130
+ Rainbow("None").red
131
+ else
132
+ "None"
133
+ end
134
+ end
135
+
136
+ def colorize_heartbeat(timestamp)
137
+ case Time.now - timestamp
138
+ when (10.minutes)..nil
139
+ Rainbow(timestamp.strftime("%D %R")).red
140
+ when (2.minutes)..(10.minutes)
141
+ Rainbow(timestamp.strftime("%R")).yellow
142
+ else
143
+ timestamp.strftime "%R"
144
+ end
145
+ end
112
146
 
113
147
  # @return [Boolean] Whether quiet mode is enabled. Overrides verbose mode.
114
148
  def quiet?
@@ -119,8 +153,7 @@ module Exekutor
119
153
  def verbose?
120
154
  !quiet? && !!@global_options[:verbose]
121
155
  end
122
-
123
156
  end
124
157
  end
125
158
  end
126
- end
159
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "application_loader"
2
4
  require_relative "default_option_value"
3
5
  require_relative "daemon"
@@ -24,101 +26,23 @@ module Exekutor
24
26
  # @option options [Integer] :poll_interval The interval in seconds for job polling
25
27
  # @return [Void]
26
28
  def start(options)
29
+ Process.setproctitle "Exekutor worker (Initializing…) [#{$PROGRAM_NAME}]"
27
30
  daemonize(restarting: options[:restart]) if options[:daemonize]
28
31
 
29
32
  load_application(options[:environment])
30
33
 
31
- config_files = if options[:configfile].is_a? DefaultConfigFileValue
32
- options[:configfile].to_a(@global_options[:identifier])
33
- else
34
- options[:configfile]&.map { |path| File.expand_path(path, Rails.root) }
35
- end
36
-
37
- worker_options = DEFAULT_CONFIGURATION.dup
38
-
39
- config_files&.each do |path|
40
- puts "Loading config file: #{path}" if verbose?
41
- config = begin
42
- YAML.safe_load(File.read(path), symbolize_names: true)
43
- rescue => e
44
- raise Error, "Cannot read config file: #{path} (#{e.to_s})"
45
- end
46
- unless config.keys == [:exekutor]
47
- raise Error, "Config should have an `exekutor` root node: #{path} (Found: #{config.keys.join(', ')})"
48
- end
49
-
50
- # Remove worker specific options before calling Exekutor.config.set
51
- worker_options.merge! config[:exekutor].extract!(:queue, :status_server_port)
52
-
53
- begin
54
- Exekutor.config.set **config[:exekutor]
55
- rescue => e
56
- raise Error, "Cannot load config file: #{path} (#{e.to_s})"
57
- end
58
- end
59
-
60
- worker_options.merge! Exekutor.config.worker_options
61
- worker_options.merge! @global_options.slice(:identifier)
62
- if verbose?
63
- worker_options[:verbose] = true
64
- elsif quiet?
65
- worker_options[:quiet] = true
66
- end
67
- if options[:threads] && !options[:threads].is_a?(DefaultOptionValue)
68
- min, max = if options[:threads].is_a?(Integer)
69
- [options[:threads], options[:threads]]
70
- else
71
- options[:threads].to_s.split(":")
72
- end
73
- if max.nil?
74
- options[:min_threads] = options[:max_threads] = Integer(min)
75
- else
76
- options[:min_threads] = Integer(min)
77
- options[:max_threads] = Integer(max)
78
- end
79
- end
80
- worker_options.merge!(
81
- options.slice(:queue, :min_threads, :max_threads, :poll_interval)
82
- .reject { |_, value| value.is_a? DefaultOptionValue }
83
- .transform_keys(poll_interval: :polling_interval)
84
- )
85
-
86
- worker_options[:queue] = nil if worker_options[:queue] == ["*"]
87
-
88
- # TODO health check server
89
-
90
34
  # Specify `yield: true` to prevent running in the context of the loaded module
91
35
  ActiveSupport.on_load(:exekutor, yield: true) do
92
- ActiveSupport.on_load(:active_record, yield: true) do
93
- worker = Worker.new(worker_options)
94
- %w[INT TERM QUIT].each do |signal|
95
- ::Kernel.trap(signal) { ::Thread.new { worker.stop } }
96
- end
36
+ worker_options = worker_options(options[:configfile], cli_worker_overrides(options))
97
37
 
98
- Process.setproctitle "Exekutor worker #{worker.id} [#{Rails.root}]"
99
- if worker_options[:set_db_connection_name]
100
- Internal::BaseRecord.connection.class.set_callback(:checkout, :after) do
101
- Internal::DatabaseConnection.set_application_name raw_connection, worker.id
102
- end
103
- Internal::BaseRecord.connection_pool.connections.each do |conn|
104
- Internal::DatabaseConnection.set_application_name conn.raw_connection, worker.id
105
- end
106
- end
107
-
108
- ActiveSupport.on_load(:active_job, yield: true) do
109
- puts "Worker #{worker.id} started (Use `#{Rainbow("ctrl + c").magenta}` to stop)" unless quiet?
110
- puts "#{worker_options.pretty_inspect}" if verbose?
111
- begin
112
- worker.start
113
- worker.join
114
- ensure
115
- worker.stop if worker.running?
116
- end
117
- end
38
+ ActiveSupport.on_load(:active_record, yield: true) do
39
+ start_and_join_worker(worker_options, options[:daemonize])
118
40
  end
119
41
  end
120
42
  end
121
43
 
44
+ # Stops a daemonized worker
45
+ # @return [Void]
122
46
  def stop(options)
123
47
  daemon = Daemon.new(pidfile: pidfile)
124
48
  pid = daemon.pid
@@ -136,23 +60,12 @@ module Exekutor
136
60
  end
137
61
 
138
62
  Process.kill("INT", pid)
139
- sleep(0.3)
140
- wait_until = if options[:shutdown_timeout].nil? || options[:shutdown_timeout] == DEFAULT_FOREVER
141
- nil
142
- else
143
- Time.now + options[:shutdown_timeout]
144
- end
145
- while daemon.status?(:running, :not_owned)
146
- puts "Waiting for worker to finish…" unless quiet?
147
- if wait_until && wait_until > Time.now
148
- Process.kill("TERM", pid)
149
- break
150
- end
151
- sleep 0.1
152
- end
63
+ wait_for_process_end(daemon, pid, shutdown_timeout(options))
153
64
  puts "Worker (PID: #{pid}) stopped." unless quiet?
154
65
  end
155
66
 
67
+ # Restarts a daemonized worker
68
+ # @return [Void]
156
69
  def restart(stop_options, start_options)
157
70
  stop stop_options.merge(restart: true)
158
71
  start start_options.merge(restart: true, daemonize: true)
@@ -160,6 +73,135 @@ module Exekutor
160
73
 
161
74
  private
162
75
 
76
+ def worker_options(config_file, cli_overrides)
77
+ worker_options = DEFAULT_CONFIGURATION.dup
78
+
79
+ load_config_files(config_file, worker_options)
80
+
81
+ worker_options.merge! Exekutor.config.worker_options
82
+ worker_options.merge! @global_options.slice(:identifier)
83
+ worker_options.merge! cli_overrides
84
+
85
+ if quiet?
86
+ worker_options[:quiet] = true
87
+ elsif verbose?
88
+ worker_options[:verbose] = true
89
+ end
90
+
91
+ worker_options[:queue] = nil if Array.wrap(worker_options[:queue]) == ["*"]
92
+ worker_options
93
+ end
94
+
95
+ def cli_worker_overrides(cli_options)
96
+ worker_options = cli_options.slice(:queue, :poll_interval)
97
+ .reject { |_, value| value.is_a? DefaultOptionValue }
98
+ .transform_keys(poll_interval: :polling_interval)
99
+
100
+ min_threads, max_threads = parse_thread_limitations(cli_options[:threads])
101
+ if min_threads
102
+ worker_options[:min_threads] = min_threads
103
+ worker_options[:max_threads] = max_threads || min_threads
104
+ end
105
+
106
+ worker_options
107
+ end
108
+
109
+ def parse_thread_limitations(threads)
110
+ return if threads.blank? || threads.is_a?(DefaultOptionValue)
111
+
112
+ if threads.is_a?(Integer)
113
+ [threads, threads]
114
+ else
115
+ threads.to_s.split(":").map { |s| Integer(s) }
116
+ end
117
+ end
118
+
119
+ def load_config_files(config_files, worker_options)
120
+ config_files = if config_files.is_a? DefaultConfigFileValue
121
+ config_files.to_a(@global_options[:identifier])
122
+ else
123
+ config_files&.map { |path| File.expand_path(path, Rails.root) }
124
+ end
125
+
126
+ config_files&.each { |path| load_config_file(path, worker_options) }
127
+ end
128
+
129
+ def load_config_file(path, worker_options)
130
+ puts "Loading config file: #{path}" if verbose?
131
+ config = begin
132
+ YAML.safe_load(File.read(path), symbolize_names: true)
133
+ rescue StandardError => e
134
+ raise Error, "Cannot read config file: #{path} (#{e})"
135
+ end
136
+ unless config.keys == [:exekutor]
137
+ raise Error, "Config should have an `exekutor` root node: #{path} (Found: #{config.keys.join(", ")})"
138
+ end
139
+
140
+ # Remove worker specific options before calling Exekutor.config.set
141
+ worker_options.merge! config[:exekutor].extract!(:queue, :status_server_port)
142
+
143
+ begin
144
+ Exekutor.config.set(**config[:exekutor])
145
+ rescue StandardError => e
146
+ raise Error, "Cannot load config file: #{path} (#{e})"
147
+ end
148
+ end
149
+
150
+ def start_and_join_worker(worker_options, is_daemonized)
151
+ worker = Worker.new(worker_options)
152
+ %w[INT TERM QUIT].each do |signal|
153
+ ::Kernel.trap(signal) { ::Thread.new { worker.stop } }
154
+ end
155
+
156
+ Process.setproctitle "Exekutor worker #{worker.id} [#{Rails.root}]"
157
+ set_db_connection_name(worker.id) if worker_options[:set_db_connection_name]
158
+
159
+ ActiveSupport.on_load(:active_job, yield: true) do
160
+ worker.start
161
+ print_startup_message(worker, worker_options) unless quiet? || is_daemonized
162
+ worker.join
163
+ ensure
164
+ worker.stop if worker.running?
165
+ end
166
+ end
167
+
168
+ def print_startup_message(worker, worker_options)
169
+ puts "Worker #{worker.id} started (Use `#{Rainbow("ctrl + c").magenta}` to stop)"
170
+ puts worker_options.pretty_inspect if verbose?
171
+ end
172
+
173
+ # rubocop:disable Naming/AccessorMethodName
174
+ def set_db_connection_name(worker_id)
175
+ # rubocop:enable Naming/AccessorMethodName
176
+ Internal::BaseRecord.connection.class.set_callback(:checkout, :after) do
177
+ Internal::DatabaseConnection.set_application_name raw_connection, worker_id
178
+ end
179
+ Internal::BaseRecord.connection_pool.connections.each do |conn|
180
+ Internal::DatabaseConnection.set_application_name conn.raw_connection, worker_id
181
+ end
182
+ end
183
+
184
+ def wait_for_process_end(daemon, pid, shutdown_timeout)
185
+ wait_until = (Time.now.to_f + shutdown_timeout if shutdown_timeout)
186
+ sleep 0.1
187
+ while daemon.status?(:running, :not_owned)
188
+ if wait_until && wait_until > Time.now.to_f
189
+ puts "Sending TERM signal" unless quiet?
190
+ Process.kill("TERM", pid) if pid
191
+ break
192
+ end
193
+ sleep 0.1
194
+ end
195
+ end
196
+
197
+ def shutdown_timeout(options)
198
+ if options[:shutdown_timeout].nil? || options[:shutdown_timeout] == DEFAULT_FOREVER
199
+ nil
200
+ else
201
+ options[:shutdown_timeout]
202
+ end
203
+ end
204
+
163
205
  # @return [Boolean] Whether quiet mode is enabled. Overrides verbose mode.
164
206
  def quiet?
165
207
  !!@global_options[:quiet]
@@ -175,6 +217,8 @@ module Exekutor
175
217
  @global_options[:identifier]
176
218
  end
177
219
 
220
+ # rubocop:disable Style/FormatStringToken
221
+
178
222
  # @return [String] The path to the pidfile
179
223
  def pidfile
180
224
  pidfile = @global_options[:pidfile] || DEFAULT_PIDFILE
@@ -187,37 +231,43 @@ module Exekutor
187
231
  end
188
232
  end
189
233
 
190
- # Daemonizes the current process. Do this before loading your application to prevent deadlocks.
234
+ # Daemonizes the current process.
191
235
  # @return [Void]
192
236
  def daemonize(restarting: false)
193
237
  daemonizer = Daemon.new(pidfile: pidfile)
194
238
  daemonizer.validate!
195
- unless quiet?
196
- if restarting
197
- puts "Restarting worker as a daemon…"
198
- else
199
- stop_options = if @global_options[:pidfile] && @global_options[:pidfile] != DEFAULT_PIDFILE
200
- "--pid #{pidfile} "
201
- elsif identifier
202
- "--id #{identifier} "
203
- end
239
+ print_daemonize_message(restarting) unless quiet?
204
240
 
205
- puts "Running worker as a daemon… (Use `#{Rainbow("exekutor #{stop_options}stop").magenta}` to stop)"
206
- end
207
- end
208
241
  daemonizer.daemonize
209
242
  rescue Daemon::Error => e
210
243
  puts Rainbow(e.message).red
211
244
  raise GLI::CustomExit.new(nil, 1)
212
245
  end
213
246
 
247
+ def print_daemonize_message(restarting)
248
+ if restarting
249
+ puts "Restarting worker as a daemon…"
250
+ else
251
+ stop_options = if @global_options[:pidfile] && @global_options[:pidfile] != DEFAULT_PIDFILE
252
+ "--pid #{pidfile} "
253
+ elsif identifier
254
+ "--id #{identifier} "
255
+ end
256
+
257
+ puts "Running worker as a daemon… (Use `#{Rainbow("exekutor #{stop_options}stop").magenta}` to stop)"
258
+ end
259
+ end
260
+
261
+ # The default value for the pid file
214
262
  class DefaultPidFileValue < DefaultOptionValue
215
263
  def initialize
216
264
  super("tmp/pids/exekutor[.%{identifier}].pid")
217
265
  end
218
266
 
267
+ # @param identifier [nil,String] the worker identifier
268
+ # @return [String] the path to the default pidfile of the worker with the specified identifier
219
269
  def for_identifier(identifier)
220
- if identifier.nil? || identifier.length.zero?
270
+ if identifier.nil? || identifier.empty? # rubocop:disable Rails/Blank – Rails is not loaded here
221
271
  "tmp/pids/exekutor.pid"
222
272
  else
223
273
  "tmp/pids/exekutor.#{identifier}.pid"
@@ -225,36 +275,53 @@ module Exekutor
225
275
  end
226
276
  end
227
277
 
278
+ # The default value for the config file
228
279
  class DefaultConfigFileValue < DefaultOptionValue
229
280
  def initialize
230
- super('"config/exekutor.yml", overridden by "config/exekutor.%{identifier}.yml" if an identifier is specified')
281
+ super <<~DESC
282
+ "config/exekutor.yml", overridden by "config/exekutor.%{identifier}.yml" if an identifier is specified
283
+ DESC
231
284
  end
232
285
 
286
+ # @param identifier [nil,String] the worker identifier
287
+ # @return [Array<String>] the paths to the configfiles to load
233
288
  def to_a(identifier = nil)
234
289
  files = []
235
- files << %w[config/exekutor.yml config/exekutor.yaml]
236
- .lazy.map { |path| Rails.root.join(path) }
237
- .find { |path| File.exists? path }
290
+ %w[config/exekutor.yml config/exekutor.yaml].each do |path|
291
+ path = Rails.root.join(path)
292
+ if File.exist? path
293
+ files.append path
294
+ break
295
+ end
296
+ end
238
297
  if identifier.present?
239
- files << %W[config/exekutor.#{identifier}.yml config/exekutor.#{identifier}.yaml]
240
- .lazy.map { |path| Rails.root.join(path) }
241
- .find { |path| File.exists? path }
298
+ %W[config/exekutor.#{identifier}.yml config/exekutor.#{identifier}.yaml].each do |path|
299
+ path = Rails.root.join(path)
300
+ if File.exist? path
301
+ files.append path
302
+ break
303
+ end
304
+ end
242
305
  end
243
- files.compact
306
+ files
244
307
  end
245
308
  end
246
309
 
310
+ # rubocop:enable Style/FormatStringToken
311
+
247
312
  DEFAULT_PIDFILE = DefaultPidFileValue.new.freeze
248
313
  DEFAULT_CONFIG_FILES = DefaultConfigFileValue.new.freeze
249
314
 
250
- DEFAULT_THREADS = DefaultOptionValue.new("Minimum: 1, Maximum: Active record pool size minus 1, with a minimum of 1").freeze
315
+ DEFAULT_THREADS = DefaultOptionValue.new(
316
+ "Minimum: 1, Maximum: Active record pool size minus 1, with a minimum of 1"
317
+ ).freeze
251
318
  DEFAULT_QUEUE = DefaultOptionValue.new("All queues").freeze
252
319
  DEFAULT_FOREVER = DefaultOptionValue.new("Forever").freeze
253
320
 
254
- DEFAULT_CONFIGURATION = { set_db_connection_name: true }
321
+ DEFAULT_CONFIGURATION = { set_db_connection_name: true }.freeze
255
322
 
256
323
  class Error < StandardError; end
257
324
  end
258
325
  end
259
326
  end
260
- end
327
+ end