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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/exe/exekutor +2 -2
- data/lib/active_job/queue_adapters/exekutor_adapter.rb +2 -1
- data/lib/exekutor/asynchronous.rb +143 -75
- data/lib/exekutor/cleanup.rb +27 -28
- data/lib/exekutor/configuration.rb +48 -25
- data/lib/exekutor/hook.rb +15 -11
- data/lib/exekutor/info/worker.rb +3 -3
- data/lib/exekutor/internal/base_record.rb +2 -1
- data/lib/exekutor/internal/callbacks.rb +55 -35
- data/lib/exekutor/internal/cli/app.rb +31 -23
- data/lib/exekutor/internal/cli/application_loader.rb +17 -6
- data/lib/exekutor/internal/cli/cleanup.rb +54 -40
- data/lib/exekutor/internal/cli/daemon.rb +9 -11
- data/lib/exekutor/internal/cli/default_option_value.rb +3 -1
- data/lib/exekutor/internal/cli/info.rb +117 -84
- data/lib/exekutor/internal/cli/manager.rb +190 -123
- data/lib/exekutor/internal/configuration_builder.rb +40 -27
- data/lib/exekutor/internal/database_connection.rb +6 -0
- data/lib/exekutor/internal/executable.rb +12 -7
- data/lib/exekutor/internal/executor.rb +50 -21
- data/lib/exekutor/internal/hooks.rb +11 -8
- data/lib/exekutor/internal/listener.rb +66 -39
- data/lib/exekutor/internal/logger.rb +28 -10
- data/lib/exekutor/internal/provider.rb +93 -74
- data/lib/exekutor/internal/reserver.rb +27 -12
- data/lib/exekutor/internal/status_server.rb +81 -49
- data/lib/exekutor/job.rb +1 -1
- data/lib/exekutor/job_error.rb +1 -1
- data/lib/exekutor/job_options.rb +22 -13
- data/lib/exekutor/plugins/appsignal.rb +7 -5
- data/lib/exekutor/plugins.rb +8 -4
- data/lib/exekutor/queue.rb +40 -22
- data/lib/exekutor/version.rb +1 -1
- data/lib/exekutor/worker.rb +88 -47
- data/lib/exekutor.rb +2 -2
- data/lib/generators/exekutor/configuration_generator.rb +9 -5
- data/lib/generators/exekutor/install_generator.rb +26 -15
- data/lib/generators/exekutor/templates/install/migrations/create_exekutor_schema.rb.erb +11 -10
- data.tar.gz.sig +0 -0
- metadata +63 -19
- 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
|
-
|
23
|
-
|
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 =
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
99
|
-
|
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
|
-
|
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.
|
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.
|
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
|
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
|
-
|
236
|
-
|
237
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
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
|
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(
|
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
|