exekutor 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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