exekutor 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/exekutor/configuration.rb +55 -24
- data/lib/exekutor/internal/cli/app.rb +3 -1
- data/lib/exekutor/internal/cli/info.rb +1 -1
- data/lib/exekutor/internal/cli/manager.rb +79 -35
- data/lib/exekutor/internal/configuration_builder.rb +13 -7
- data/lib/exekutor/internal/listener.rb +22 -7
- data/lib/exekutor/internal/logger.rb +1 -0
- data/lib/exekutor/internal/provider.rb +3 -3
- data/lib/exekutor/internal/reserver.rb +43 -11
- data/lib/exekutor/internal/status_server.rb +9 -8
- data/lib/exekutor/queue.rb +40 -19
- data/lib/exekutor/version.rb +1 -1
- data/lib/exekutor/worker.rb +6 -6
- data/lib/generators/exekutor/configuration_generator.rb +4 -3
- data/lib/generators/exekutor/install_generator.rb +3 -5
- data/lib/generators/exekutor/templates/install/functions/exekutor_broadcast_job_enqueued.sql +10 -0
- data/lib/generators/exekutor/templates/install/functions/exekutor_requeue_orphaned_jobs.sql +11 -0
- data/lib/generators/exekutor/templates/install/migrations/create_exekutor_schema.rb.erb +12 -12
- data/lib/generators/exekutor/templates/install/triggers/exekutor_broadcast_job_enqueued.sql +7 -0
- data/lib/generators/exekutor/templates/install/triggers/exekutor_requeue_orphaned_jobs.sql +5 -0
- data.tar.gz.sig +0 -0
- metadata +6 -6
- metadata.gz.sig +0 -0
- data/lib/generators/exekutor/templates/install/functions/job_notifier.sql +0 -7
- data/lib/generators/exekutor/templates/install/functions/requeue_orphaned_jobs.sql +0 -7
- data/lib/generators/exekutor/templates/install/triggers/notify_workers.sql +0 -6
- data/lib/generators/exekutor/templates/install/triggers/requeue_orphaned_jobs.sql +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5b178052eb277168916a76f663ba53882204481f740a7c30bc309d243de79cf
|
4
|
+
data.tar.gz: bfb648002d86bf278b41b5f7ca6fde0f1d3cb39954c445eec0e268bf72120efe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2d7a7787f83dc362aeca814d511d0584e5e3e2af24209f1bfc00bd1efbae47d9226bc91e5615a0c7b38b5a515ad64146ec43d74e6cb3b757f3144fcc100d05e
|
7
|
+
data.tar.gz: 651ee14b261990adfe498e017afd6ea0e3e382e12f7bf4a352d01dd4d203938f62f2223159e7930bd517ee5fd68f191b2d0ad9189c13670158064e8381278b5b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -180,18 +180,19 @@ module Exekutor
|
|
180
180
|
|
181
181
|
# @!macro
|
182
182
|
# @!method $1
|
183
|
-
# The polling interval
|
183
|
+
# The polling interval. When set, the worker will poll the database with this interval to check for
|
184
184
|
# any pending jobs that a listener might have missed (if enabled).
|
185
185
|
# === Default value:
|
186
|
-
# 60
|
187
|
-
# @return [
|
186
|
+
# 60 seconds
|
187
|
+
# @return [ActiveSupport::Duration]
|
188
188
|
# @!method $1=(value)
|
189
|
-
# Sets the polling interval
|
190
|
-
# should be reasonably low so jobs don't have to wait in the queue too long; if
|
191
|
-
# be reasonably high.
|
192
|
-
# @param value [
|
189
|
+
# Sets the polling interval. Set to +nil+ to disable polling. If the listener is disabled, this value
|
190
|
+
# should be reasonably low so jobs don't have to wait in the queue too long; if the listener is enabled, this
|
191
|
+
# value can be reasonably high.
|
192
|
+
# @param value [ActiveSupport::Duration] the interval
|
193
193
|
# @return [self]
|
194
|
-
define_option :polling_interval, default:
|
194
|
+
define_option :polling_interval, default: 1.minute, type: [ActiveSupport::Duration, nil],
|
195
|
+
range: (1.second)...(1.day)
|
195
196
|
|
196
197
|
# @!macro
|
197
198
|
# @!method $1
|
@@ -239,21 +240,22 @@ module Exekutor
|
|
239
240
|
|
240
241
|
# @!macro
|
241
242
|
# @!method $1
|
242
|
-
# The maximum
|
243
|
+
# The maximum duration a thread may be idle before being stopped.
|
243
244
|
# === Default value:
|
244
|
-
# 60
|
245
|
-
# @return [
|
245
|
+
# 60 seconds
|
246
|
+
# @return [ActiveSupport::Duration]
|
246
247
|
# @!method $1=(value)
|
247
|
-
# Sets the maximum
|
248
|
-
# @param value [
|
248
|
+
# Sets the maximum duration a thread may be idle before being stopped
|
249
|
+
# @param value [ActiveSupport::Duration] the number of threads
|
249
250
|
# @return [self]
|
250
|
-
define_option :max_execution_thread_idletime, default:
|
251
|
+
define_option :max_execution_thread_idletime, default: 1.minute, type: ActiveSupport::Duration,
|
252
|
+
range: (1.second)..(1.day)
|
251
253
|
|
252
254
|
# @!macro
|
253
255
|
# @!method $1?
|
254
256
|
# The rack handler for the status server
|
255
257
|
# === Default value:
|
256
|
-
# webrick
|
258
|
+
# +"webrick"+
|
257
259
|
# @return [String]
|
258
260
|
# @!method $1=(value)
|
259
261
|
# Sets the rack handler for the status server. The handler should respond to +#shutdown+ or +#stop+.
|
@@ -263,18 +265,46 @@ module Exekutor
|
|
263
265
|
|
264
266
|
# @!macro
|
265
267
|
# @!method $1?
|
266
|
-
# The
|
268
|
+
# The port number for the status server
|
269
|
+
# === Default value:
|
270
|
+
# +nil+ (ie. the status server is disabled)
|
271
|
+
# @return [Integer]
|
272
|
+
# @!method $1=(value)
|
273
|
+
# Sets the port number for the status server.
|
274
|
+
# @param value [Integer] the port number
|
275
|
+
# @return [self]
|
276
|
+
define_option :status_server_port, default: nil, type: Integer
|
277
|
+
|
278
|
+
# @!macro
|
279
|
+
# @!method $1?
|
280
|
+
# The heartbeat timeout for the `/live` endpoint of the status server. If the heartbeat of a worker
|
267
281
|
# is older than this timeout, the status server will respond with a 503 status indicating the service is
|
268
282
|
# down.
|
269
283
|
# === Default value:
|
270
|
-
# 30
|
271
|
-
# @return [
|
284
|
+
# 30 minutes
|
285
|
+
# @return [ActiveSupport::Duration]
|
286
|
+
# @!method $1=(value)
|
287
|
+
# Sets the heartbeat timeout for the `/live` endpoint of the status server. Must be between 1 minute
|
288
|
+
# and 24 hours.
|
289
|
+
# @param value [ActiveSupport::Duration] The timeout in minutes
|
290
|
+
# @return [self]
|
291
|
+
define_option :healthcheck_timeout, default: 30.minutes, type: ActiveSupport::Duration,
|
292
|
+
range: (1.minute)..(1.day)
|
293
|
+
|
294
|
+
# @!macro
|
295
|
+
# @!method $1?
|
296
|
+
# Whether the priority should be inverted. If true, the job with the highest value as the priority is the most
|
297
|
+
# important. If false, the job with the lowest value as the priority is the most important.
|
298
|
+
# === Default value:
|
299
|
+
# false
|
300
|
+
# @return [Boolean]
|
272
301
|
# @!method $1=(value)
|
273
|
-
# Sets the
|
274
|
-
#
|
275
|
-
# @param value [
|
302
|
+
# Sets whether the the priority should be inverted. When true, the job with the highest value as the priority is
|
303
|
+
# the most important; when false, the job with the lowest value as the priority is the most important.
|
304
|
+
# @param value [Boolean] whether the job with the highest priority value is the most important.
|
276
305
|
# @return [self]
|
277
|
-
define_option :
|
306
|
+
define_option :inverse_priority, reader: :inverse_priority?, type: [TrueClass, FalseClass], required: true,
|
307
|
+
default: false
|
278
308
|
|
279
309
|
# @!macro
|
280
310
|
# @!method $1?
|
@@ -294,13 +324,14 @@ module Exekutor
|
|
294
324
|
{
|
295
325
|
min_threads: min_execution_threads,
|
296
326
|
max_threads: max_execution_threads,
|
297
|
-
max_thread_idletime: max_execution_thread_idletime
|
327
|
+
max_thread_idletime: max_execution_thread_idletime.to_f
|
298
328
|
}.tap do |opts|
|
299
329
|
opts[:set_db_connection_name] = set_db_connection_name? unless set_db_connection_name.nil?
|
300
330
|
%i[enable_listener delete_completed_jobs delete_discarded_jobs delete_failed_jobs].each do |option|
|
301
331
|
opts[option] = send(:"#{option}?") ? true : false
|
302
332
|
end
|
303
|
-
%i[polling_interval polling_jitter status_server_handler healthcheck_timeout]
|
333
|
+
%i[polling_interval polling_jitter status_server_handler status_server_port healthcheck_timeout]
|
334
|
+
.each do |option|
|
304
335
|
opts[option] = send(option)
|
305
336
|
end
|
306
337
|
end
|
@@ -35,9 +35,11 @@ module Exekutor
|
|
35
35
|
cmd.flag %i[env environment], desc: "The Rails environment"
|
36
36
|
cmd.flag %i[q queue], default_value: Manager::DEFAULT_QUEUE, multiple: true,
|
37
37
|
desc: "Queue to work from"
|
38
|
+
cmd.flag %i[p priority], type: String, default_value: Manager::DEFAULT_PRIORITIES,
|
39
|
+
desc: "The job priorities to execute, specified as `min` or `min:max`"
|
38
40
|
cmd.flag %i[t threads], type: String, default_value: Manager::DEFAULT_THREADS,
|
39
41
|
desc: "The number of threads for executing jobs, specified as `min:max`"
|
40
|
-
cmd.flag %i[
|
42
|
+
cmd.flag %i[i poll_interval], type: Integer, default_value: DefaultOptionValue.new(value: 60),
|
41
43
|
desc: "Interval between polls for available jobs (in seconds)"
|
42
44
|
cmd.flag %i[cfg configfile], type: String, default_value: Manager::DEFAULT_CONFIG_FILES, multiple: true,
|
43
45
|
desc: "The YAML configuration file to load. If specifying multiple files, the last file takes " \
|
@@ -101,7 +101,7 @@ module Exekutor
|
|
101
101
|
table = Terminal::Table.new headings: ["id", "Status", "Last heartbeat"]
|
102
102
|
table.title = host if many_hosts
|
103
103
|
worker_count = 0
|
104
|
-
Exekutor::Info::Worker.where(hostname: host).
|
104
|
+
Exekutor::Info::Worker.where(hostname: host).find_each do |worker|
|
105
105
|
worker_count += 1
|
106
106
|
table << worker_info_row(worker)
|
107
107
|
end
|
@@ -23,6 +23,7 @@ module Exekutor
|
|
23
23
|
# @option options [String] :environment The Rails environment to load
|
24
24
|
# @option options [String] :queue The queue(s) to watch
|
25
25
|
# @option options [String] :threads The number of threads to use for job execution
|
26
|
+
# @option options [String] :priority The priorities to execute
|
26
27
|
# @option options [Integer] :poll_interval The interval in seconds for job polling
|
27
28
|
# @return [Void]
|
28
29
|
def start(options)
|
@@ -76,7 +77,7 @@ module Exekutor
|
|
76
77
|
def worker_options(config_file, cli_overrides)
|
77
78
|
worker_options = DEFAULT_CONFIGURATION.dup
|
78
79
|
|
79
|
-
|
80
|
+
ConfigLoader.new(config_file, @global_options).load_config(worker_options)
|
80
81
|
|
81
82
|
worker_options.merge! Exekutor.config.worker_options
|
82
83
|
worker_options.merge! @global_options.slice(:identifier)
|
@@ -97,16 +98,22 @@ module Exekutor
|
|
97
98
|
.reject { |_, value| value.is_a? DefaultOptionValue }
|
98
99
|
.transform_keys(poll_interval: :polling_interval)
|
99
100
|
|
100
|
-
min_threads, max_threads =
|
101
|
+
min_threads, max_threads = parse_integer_range(cli_options[:threads])
|
101
102
|
if min_threads
|
102
103
|
worker_options[:min_threads] = min_threads
|
103
104
|
worker_options[:max_threads] = max_threads || min_threads
|
104
105
|
end
|
105
106
|
|
107
|
+
min_priority, max_priority = parse_integer_range(cli_options[:priority])
|
108
|
+
if min_threads
|
109
|
+
worker_options[:min_priority] = min_priority
|
110
|
+
worker_options[:max_priority] = max_priority if max_priority
|
111
|
+
end
|
112
|
+
|
106
113
|
worker_options
|
107
114
|
end
|
108
115
|
|
109
|
-
def
|
116
|
+
def parse_integer_range(threads)
|
110
117
|
return if threads.blank? || threads.is_a?(DefaultOptionValue)
|
111
118
|
|
112
119
|
if threads.is_a?(Integer)
|
@@ -116,37 +123,6 @@ module Exekutor
|
|
116
123
|
end
|
117
124
|
end
|
118
125
|
|
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
126
|
def start_and_join_worker(worker_options, is_daemonized)
|
151
127
|
worker = Worker.new(worker_options)
|
152
128
|
%w[INT TERM QUIT].each do |signal|
|
@@ -258,6 +234,73 @@ module Exekutor
|
|
258
234
|
end
|
259
235
|
end
|
260
236
|
|
237
|
+
# Takes care of loading YAML configuration
|
238
|
+
class ConfigLoader
|
239
|
+
def initialize(files, options)
|
240
|
+
@config_files = files
|
241
|
+
@options = options
|
242
|
+
end
|
243
|
+
|
244
|
+
def load_config(worker_options)
|
245
|
+
each_file do |path|
|
246
|
+
config = load_config_file(path)
|
247
|
+
convert_duration_options! config
|
248
|
+
|
249
|
+
worker_options.merge! extract_worker_options!(config)
|
250
|
+
apply_config_file(config)
|
251
|
+
end
|
252
|
+
Exekutor.config
|
253
|
+
end
|
254
|
+
|
255
|
+
private
|
256
|
+
|
257
|
+
WORKER_OPTIONS = %i[queues min_priority max_priority min_threads max_threads max_thread_idletime
|
258
|
+
wait_for_termination].freeze
|
259
|
+
|
260
|
+
def each_file(&block)
|
261
|
+
if @config_files.is_a? DefaultConfigFileValue
|
262
|
+
@config_files.to_a(@options[:identifier]).each(&block)
|
263
|
+
elsif @config_files.is_a? String
|
264
|
+
yield File.expand_path(@config_files, Rails.root)
|
265
|
+
else
|
266
|
+
@config_files.map { |path| File.expand_path(path, Rails.root) }.each(&block)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def extract_worker_options!(config)
|
271
|
+
config.extract!(*WORKER_OPTIONS)
|
272
|
+
end
|
273
|
+
|
274
|
+
def load_config_file(path)
|
275
|
+
puts "Loading config file: #{path}" if @options[:verbose]
|
276
|
+
config = begin
|
277
|
+
YAML.safe_load(File.read(path), symbolize_names: true)
|
278
|
+
rescue StandardError => e
|
279
|
+
raise Error, "Cannot read config file: #{path} (#{e})"
|
280
|
+
end
|
281
|
+
unless config.keys == [:exekutor]
|
282
|
+
raise Error, "Config should have an `exekutor` root node: #{path} (Found: #{config.keys.join(", ")})"
|
283
|
+
end
|
284
|
+
|
285
|
+
config[:exekutor]
|
286
|
+
end
|
287
|
+
|
288
|
+
def apply_config_file(config)
|
289
|
+
Exekutor.config.set(**config)
|
290
|
+
rescue StandardError => e
|
291
|
+
raise Error, "Cannot load config file (#{e})"
|
292
|
+
end
|
293
|
+
|
294
|
+
def convert_duration_options!(config)
|
295
|
+
{ polling_interval: :seconds, max_execution_thread_idletime: :seconds, healthcheck_timeout: :minutes }
|
296
|
+
.each do |duration_option, duration_interval|
|
297
|
+
if config[duration_option].is_a? Numeric
|
298
|
+
config[duration_option] = config[duration_option].send(duration_interval)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
261
304
|
# The default value for the pid file
|
262
305
|
class DefaultPidFileValue < DefaultOptionValue
|
263
306
|
def initialize
|
@@ -278,7 +321,7 @@ module Exekutor
|
|
278
321
|
# The default value for the config file
|
279
322
|
class DefaultConfigFileValue < DefaultOptionValue
|
280
323
|
def initialize
|
281
|
-
super
|
324
|
+
super(<<~DESC)
|
282
325
|
"config/exekutor.yml", overridden by "config/exekutor.%{identifier}.yml" if an identifier is specified
|
283
326
|
DESC
|
284
327
|
end
|
@@ -316,6 +359,7 @@ module Exekutor
|
|
316
359
|
"Minimum: 1, Maximum: Active record pool size minus 1, with a minimum of 1"
|
317
360
|
).freeze
|
318
361
|
DEFAULT_QUEUE = DefaultOptionValue.new("All queues").freeze
|
362
|
+
DEFAULT_PRIORITIES = DefaultOptionValue.new("All priorities").freeze
|
319
363
|
DEFAULT_FOREVER = DefaultOptionValue.new("Forever").freeze
|
320
364
|
|
321
365
|
DEFAULT_CONFIGURATION = { set_db_connection_name: true }.freeze
|
@@ -26,7 +26,7 @@ module Exekutor
|
|
26
26
|
end
|
27
27
|
|
28
28
|
options.each do |name, value|
|
29
|
-
send "#{name}=", value
|
29
|
+
send :"#{name}=", value
|
30
30
|
end
|
31
31
|
self
|
32
32
|
end
|
@@ -54,7 +54,7 @@ module Exekutor
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def define_writer(name, required, type, enum, range)
|
57
|
-
define_method "#{name}=" do |value|
|
57
|
+
define_method :"#{name}=" do |value|
|
58
58
|
validate_option_presence! name, value if required
|
59
59
|
validate_option_type! name, value, *type if type.present?
|
60
60
|
validate_option_enum! name, value, *enum if enum.present?
|
@@ -93,7 +93,9 @@ module Exekutor
|
|
93
93
|
return if allowed_types.include?(value.class)
|
94
94
|
|
95
95
|
raise error_class, "##{name} should be an instance of #{
|
96
|
-
allowed_types.
|
96
|
+
allowed_types.map { |c| c == NilClass || c.nil? ? "nil" : c }
|
97
|
+
.to_sentence(two_words_connector: " or ", last_word_connector: ", or ")
|
98
|
+
} (Actual: #{value.class})"
|
97
99
|
end
|
98
100
|
|
99
101
|
# Validates whether the value is a valid enum option
|
@@ -101,8 +103,10 @@ module Exekutor
|
|
101
103
|
def validate_option_enum!(name, value, *allowed_values)
|
102
104
|
return if allowed_values.include?(value)
|
103
105
|
|
104
|
-
raise error_class, "##{name} should be one of #{
|
105
|
-
|
106
|
+
raise error_class, "##{name} should be one of #{
|
107
|
+
allowed_values.map(&:inspect)
|
108
|
+
.to_sentence(two_words_connector: " or ", last_word_connector: ", or ")
|
109
|
+
}"
|
106
110
|
end
|
107
111
|
|
108
112
|
# Validates whether the value falls in the allowed range
|
@@ -110,8 +114,10 @@ module Exekutor
|
|
110
114
|
def validate_option_range!(name, value, allowed_range)
|
111
115
|
return if allowed_range.include?(value)
|
112
116
|
|
113
|
-
raise error_class,
|
114
|
-
|
117
|
+
raise error_class, <<~MSG
|
118
|
+
##{name} should be between #{allowed_range.first.inspect} and #{allowed_range.last.inspect}#{
|
119
|
+
" (exclusive)" if allowed_range.respond_to?(:exclude_end?) && allowed_range.exclude_end?}
|
120
|
+
MSG
|
115
121
|
end
|
116
122
|
|
117
123
|
protected
|
@@ -24,11 +24,14 @@ module Exekutor
|
|
24
24
|
# @param pool [ThreadPoolExecutor] the thread pool to use
|
25
25
|
# @param wait_timeout [Integer] the time to listen for notifications
|
26
26
|
# @param set_db_connection_name [Boolean] whether to set the application name on the DB connection
|
27
|
-
def initialize(worker_id:, provider:, pool:, queues: nil,
|
27
|
+
def initialize(worker_id:, provider:, pool:, queues: nil, min_priority: nil, max_priority: nil, wait_timeout: 60,
|
28
|
+
set_db_connection_name: false)
|
28
29
|
super()
|
29
30
|
@config = {
|
30
31
|
worker_id: worker_id,
|
31
|
-
queues: queues
|
32
|
+
queues: queues.presence,
|
33
|
+
min_priority: min_priority,
|
34
|
+
max_priority: max_priority,
|
32
35
|
wait_timeout: wait_timeout,
|
33
36
|
set_db_connection_name: set_db_connection_name
|
34
37
|
}
|
@@ -67,11 +70,22 @@ module Exekutor
|
|
67
70
|
PROVIDER_CHANNEL % @config[:worker_id]
|
68
71
|
end
|
69
72
|
|
70
|
-
#
|
71
|
-
|
73
|
+
# @return [Boolean] Whether the job matches the configured queues and priority range
|
74
|
+
def job_filter_match?(job_info)
|
75
|
+
listening_to_queue?(job_info["q"]) && listening_to_priority?(job_info["p"].to_i)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Boolean] Whether this listener is listening to the given queue
|
72
79
|
def listening_to_queue?(queue)
|
73
80
|
queues = @config[:queues]
|
74
|
-
queues.
|
81
|
+
queues.nil? || queues.include?(queue)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Boolean] Whether this listener is listening to the given priority
|
85
|
+
def listening_to_priority?(priority)
|
86
|
+
minimum = @config[:min_priority]
|
87
|
+
maximum = @config[:max_priority]
|
88
|
+
(minimum.nil? || minimum <= priority) && (maximum.nil? || maximum >= priority)
|
75
89
|
end
|
76
90
|
|
77
91
|
# Starts the listener thread
|
@@ -129,8 +143,9 @@ module Exekutor
|
|
129
143
|
JobParser.parse(payload)
|
130
144
|
rescue StandardError => e
|
131
145
|
logger.error e.message
|
146
|
+
nil
|
132
147
|
end
|
133
|
-
next unless job_info &&
|
148
|
+
next unless job_info && job_filter_match?(job_info)
|
134
149
|
|
135
150
|
@provider.update_earliest_scheduled_at(job_info["t"].to_f)
|
136
151
|
end
|
@@ -177,7 +192,7 @@ module Exekutor
|
|
177
192
|
|
178
193
|
# Parses a NOTIFY payload to a job
|
179
194
|
class JobParser
|
180
|
-
JOB_INFO_KEYS = %w[id q t].freeze
|
195
|
+
JOB_INFO_KEYS = %w[id q p t].freeze
|
181
196
|
|
182
197
|
def self.parse(payload)
|
183
198
|
job_info = begin
|
@@ -82,6 +82,7 @@ module Exekutor
|
|
82
82
|
# @return [String] The given error class, message, and cleaned backtrace as a string
|
83
83
|
def self.strferr(err)
|
84
84
|
raise ArgumentError, "err must not be nil" if err.nil?
|
85
|
+
return err if err.is_a? String
|
85
86
|
|
86
87
|
"#{err.class} – #{err.message}\nat #{
|
87
88
|
err.backtrace ? backtrace_cleaner.clean(err.backtrace).join("\n ") : "unknown location"
|
@@ -25,16 +25,16 @@ module Exekutor
|
|
25
25
|
# @param reserver [Reserver] the job reserver
|
26
26
|
# @param executor [Executor] the job executor
|
27
27
|
# @param pool [ThreadPoolExecutor] the thread pool to use
|
28
|
-
# @param polling_interval [
|
28
|
+
# @param polling_interval [ActiveSupport::Duration] the polling interval
|
29
29
|
# @param interval_jitter [Float] the polling interval jitter
|
30
|
-
def initialize(reserver:, executor:, pool:, polling_interval: 60,
|
30
|
+
def initialize(reserver:, executor:, pool:, polling_interval: 60.seconds,
|
31
31
|
interval_jitter: polling_interval.to_i > 1 ? polling_interval * 0.1 : 0)
|
32
32
|
super()
|
33
33
|
@reserver = reserver
|
34
34
|
@executor = executor
|
35
35
|
@pool = pool
|
36
36
|
|
37
|
-
@polling_interval = polling_interval.freeze
|
37
|
+
@polling_interval = polling_interval.to_i.freeze
|
38
38
|
@interval_jitter = interval_jitter.to_f.freeze
|
39
39
|
|
40
40
|
@event = Concurrent::Event.new
|
@@ -11,9 +11,9 @@ module Exekutor
|
|
11
11
|
# Creates a new Reserver
|
12
12
|
# @param worker_id [String] the id of the worker
|
13
13
|
# @param queues [Array<String>] the queues to watch
|
14
|
-
def initialize(worker_id, queues)
|
14
|
+
def initialize(worker_id, queues: nil, min_priority: nil, max_priority: nil)
|
15
15
|
@worker_id = worker_id
|
16
|
-
@
|
16
|
+
@reserve_filter_sql = build_filter_sql(queues: queues, min_priority: min_priority, max_priority: max_priority)
|
17
17
|
@json_serializer = Exekutor.config.load_json_serializer
|
18
18
|
end
|
19
19
|
|
@@ -26,8 +26,8 @@ module Exekutor
|
|
26
26
|
results = Exekutor::Job.connection.exec_query <<~SQL, ACTION_NAME, [@worker_id, limit], prepare: true
|
27
27
|
UPDATE exekutor_jobs SET worker_id = $1, status = 'e' WHERE id IN (
|
28
28
|
SELECT id FROM exekutor_jobs
|
29
|
-
WHERE scheduled_at <= now() AND "status"='p' #{@
|
30
|
-
ORDER BY priority, scheduled_at, enqueued_at
|
29
|
+
WHERE scheduled_at <= now() AND "status"='p'#{" AND #{@reserve_filter_sql}" if @reserve_filter_sql}
|
30
|
+
ORDER BY priority#{" DESC" if Exekutor.config.inverse_priority?}, scheduled_at, enqueued_at
|
31
31
|
FOR UPDATE SKIP LOCKED
|
32
32
|
LIMIT $2
|
33
33
|
) RETURNING "id", "payload", "options", "scheduled_at"
|
@@ -51,7 +51,7 @@ module Exekutor
|
|
51
51
|
# @return [Time,nil] The earliest scheduled at, or nil if the queues are empty
|
52
52
|
def earliest_scheduled_at
|
53
53
|
jobs = Exekutor::Job.pending
|
54
|
-
jobs.where! @
|
54
|
+
jobs.where! @reserve_filter_sql unless @reserve_filter_sql.nil?
|
55
55
|
jobs.minimum(:scheduled_at)
|
56
56
|
end
|
57
57
|
|
@@ -72,6 +72,16 @@ module Exekutor
|
|
72
72
|
@json_serializer.load str unless str.nil?
|
73
73
|
end
|
74
74
|
|
75
|
+
# Builds SQL filter for the given queues and priorities
|
76
|
+
def build_filter_sql(queues:, min_priority:, max_priority:)
|
77
|
+
filters = [
|
78
|
+
build_queue_filter_sql(queues),
|
79
|
+
build_priority_filter_sql(min_priority, max_priority)
|
80
|
+
]
|
81
|
+
filters.compact!
|
82
|
+
filters.join(" AND ") unless filters.empty?
|
83
|
+
end
|
84
|
+
|
75
85
|
# Builds SQL filter for the given queues
|
76
86
|
def build_queue_filter_sql(queues)
|
77
87
|
return nil if queues.nil? || (queues.is_a?(Array) && queues.empty?)
|
@@ -79,11 +89,32 @@ module Exekutor
|
|
79
89
|
queues = queues.first if queues.is_a?(Array) && queues.one?
|
80
90
|
validate_queues! queues
|
81
91
|
|
82
|
-
if queues.is_a? Array
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
92
|
+
conditions = if queues.is_a? Array
|
93
|
+
["queue IN (?)", queues]
|
94
|
+
else
|
95
|
+
["queue = ?", queues]
|
96
|
+
end
|
97
|
+
Exekutor::Job.sanitize_sql_for_conditions conditions
|
98
|
+
end
|
99
|
+
|
100
|
+
# Builds SQL filter for the given priorities
|
101
|
+
def build_priority_filter_sql(minimum, maximum)
|
102
|
+
minimum = coerce_priority(minimum)
|
103
|
+
maximum = coerce_priority(maximum)
|
104
|
+
|
105
|
+
conditions = if minimum && maximum
|
106
|
+
["priority BETWEEN ? AND ?", minimum, maximum]
|
107
|
+
elsif minimum
|
108
|
+
["priority >= ?", minimum]
|
109
|
+
elsif maximum
|
110
|
+
["priority <= ?", maximum]
|
111
|
+
end
|
112
|
+
Exekutor::Job.sanitize_sql_for_conditions conditions if conditions
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [Integer,nil] returns nil unless +priority+ is between 1 and 32,766
|
116
|
+
def coerce_priority(priority)
|
117
|
+
priority if priority && (1..32_766).cover?(priority)
|
87
118
|
end
|
88
119
|
|
89
120
|
# Raises an error if the queues value is invalid
|
@@ -96,7 +127,8 @@ module Exekutor
|
|
96
127
|
when String, Symbol
|
97
128
|
raise ArgumentError, "queue name cannot be empty" unless valid_queue_name? queues
|
98
129
|
else
|
99
|
-
raise ArgumentError,
|
130
|
+
raise ArgumentError,
|
131
|
+
"queues must be nil, a String, Symbol, or an array of Strings or Symbols (Actual: #{queues.class})"
|
100
132
|
end
|
101
133
|
end
|
102
134
|
|
@@ -23,7 +23,7 @@ module Exekutor
|
|
23
23
|
|
24
24
|
DEFAULT_HANDLER = "webrick"
|
25
25
|
|
26
|
-
def initialize(worker:, pool:, port:, handler: DEFAULT_HANDLER, heartbeat_timeout: 30)
|
26
|
+
def initialize(worker:, pool:, port:, handler: DEFAULT_HANDLER, heartbeat_timeout: 30.minutes)
|
27
27
|
super()
|
28
28
|
@worker = worker
|
29
29
|
@pool = pool
|
@@ -52,12 +52,13 @@ module Exekutor
|
|
52
52
|
return unless @thread_running.value
|
53
53
|
|
54
54
|
server = @server.value
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
server
|
55
|
+
shutdown_method = %i[shutdown stop].find { |method| server.respond_to? method }
|
56
|
+
if shutdown_method
|
57
|
+
server.send(shutdown_method)
|
58
|
+
Exekutor.say "Status server stopped"
|
59
59
|
elsif server
|
60
|
-
Exekutor.
|
60
|
+
Exekutor.print_error "Cannot shutdown status server, " \
|
61
|
+
"#{server.class.name} does not respond to `shutdown` or `stop`"
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
@@ -67,7 +68,7 @@ module Exekutor
|
|
67
68
|
def run(worker, port)
|
68
69
|
return unless state == :started && @thread_running.make_true
|
69
70
|
|
70
|
-
Exekutor.say "Starting status server at 0.0.0.0:#{port}… (Timeout: #{@heartbeat_timeout}
|
71
|
+
Exekutor.say "Starting status server at 0.0.0.0:#{port}… (Timeout: #{@heartbeat_timeout.inspect})"
|
71
72
|
@handler.run(App.new(worker, @heartbeat_timeout), Port: port, Host: "0.0.0.0", Silent: true,
|
72
73
|
Logger: ::Logger.new(File.open(File::NULL, "w")), AccessLog: []) do |server|
|
73
74
|
@server.set server
|
@@ -107,7 +108,7 @@ module Exekutor
|
|
107
108
|
private
|
108
109
|
|
109
110
|
def flatlined?(last_heartbeat = @worker.last_heartbeat)
|
110
|
-
last_heartbeat.nil? || last_heartbeat < @heartbeat_timeout.
|
111
|
+
last_heartbeat.nil? || last_heartbeat < @heartbeat_timeout.ago
|
111
112
|
end
|
112
113
|
|
113
114
|
def render_threads
|
data/lib/exekutor/queue.rb
CHANGED
@@ -17,18 +17,18 @@ module Exekutor
|
|
17
17
|
MAX_NAME_LENGTH = 63
|
18
18
|
|
19
19
|
# Adds a job to the queue, scheduled to perform immediately
|
20
|
-
# @param jobs [Array<ActiveJob::Base>] the jobs to enqueue
|
21
|
-
# @return [
|
22
|
-
def push(
|
23
|
-
create_records(jobs)
|
20
|
+
# @param jobs [ActiveJob::Base,Array<ActiveJob::Base>] the jobs to enqueue
|
21
|
+
# @return [Integer] the number of enqueued jobs
|
22
|
+
def push(jobs)
|
23
|
+
create_records Array.wrap(jobs)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Adds a job to the queue, scheduled to be performed at the indicated time
|
27
|
-
# @param jobs [Array<ActiveJob::Base>] the jobs to enqueue
|
27
|
+
# @param jobs [ActiveJob::Base,Array<ActiveJob::Base>] the jobs to enqueue
|
28
28
|
# @param timestamp [Time,Date,Integer,Float] when the job should be performed
|
29
|
-
# @return [
|
30
|
-
def schedule_at(
|
31
|
-
create_records(jobs, scheduled_at: timestamp)
|
29
|
+
# @return [Integer] the number of enqueued jobs
|
30
|
+
def schedule_at(jobs, timestamp)
|
31
|
+
create_records(Array.wrap(jobs), scheduled_at: timestamp)
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
@@ -38,16 +38,22 @@ module Exekutor
|
|
38
38
|
# @param scheduled_at [Time,Date,Integer,Float] when the job should be performed
|
39
39
|
# @return [void]
|
40
40
|
def create_records(jobs, scheduled_at: nil)
|
41
|
-
|
42
|
-
|
41
|
+
raise ArgumentError, "jobs must be an array with ActiveJob items (Actual: #{jobs.class})" unless jobs.is_a?(Array)
|
42
|
+
|
43
|
+
unless jobs.all?(ActiveJob::Base)
|
44
|
+
raise ArgumentError, "jobs must be an array with ActiveJob items (Found: #{
|
45
|
+
jobs.map { |job| job.class unless job.is_a? ActiveJob::Base }.compact.join(", ")
|
46
|
+
})"
|
43
47
|
end
|
44
48
|
|
45
49
|
scheduled_at = parse_scheduled_at(scheduled_at)
|
46
50
|
json_serializer = Exekutor.config.load_json_serializer
|
47
51
|
|
52
|
+
inserted_records = nil
|
48
53
|
Internal::Hooks.run :enqueue, jobs do
|
49
|
-
insert_job_records(jobs, scheduled_at, json_serializer)
|
54
|
+
inserted_records = insert_job_records(jobs, scheduled_at, json_serializer)
|
50
55
|
end
|
56
|
+
inserted_records
|
51
57
|
end
|
52
58
|
|
53
59
|
# Converts the given value to an epoch timestamp. Returns the current epoch timestamp if the given value is nil
|
@@ -78,22 +84,37 @@ module Exekutor
|
|
78
84
|
# @param json_serializer [#dump] the serializer to use to convert hashes into JSON
|
79
85
|
def insert_job_records(jobs, scheduled_at, json_serializer)
|
80
86
|
if jobs.one?
|
81
|
-
|
82
|
-
Exekutor::Job.connection.exec_query <<~SQL, ACTION_NAME, sql_binds, prepare: true
|
83
|
-
INSERT INTO exekutor_jobs ("queue", "priority", "scheduled_at", "active_job_id", "payload", "options") VALUES ($1, $2, to_timestamp($3), $4, $5, $6) RETURNING id;
|
84
|
-
SQL
|
87
|
+
insert_singular_job(jobs.first, json_serializer, scheduled_at)
|
85
88
|
else
|
86
89
|
insert_statements = jobs.map do |job|
|
87
90
|
Exekutor::Job.sanitize_sql_for_assignment(
|
88
91
|
["(?, ?, to_timestamp(?), ?, ?::jsonb, ?::jsonb)", *job_sql_binds(job, scheduled_at, json_serializer)]
|
89
92
|
)
|
90
93
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
+
begin
|
95
|
+
pg_result = Exekutor::Job.connection.execute <<~SQL, ACTION_NAME
|
96
|
+
INSERT INTO exekutor_jobs ("queue", "priority", "scheduled_at", "active_job_id", "payload", "options") VALUES #{insert_statements.join(",")}
|
97
|
+
SQL
|
98
|
+
inserted_records = pg_result.cmd_tuples
|
99
|
+
ensure
|
100
|
+
pg_result.clear
|
101
|
+
end
|
102
|
+
inserted_records
|
94
103
|
end
|
95
104
|
end
|
96
105
|
|
106
|
+
# Fires off an INSERT INTO query for the given job using a prepared statement
|
107
|
+
# @param job [ActiveJob::Base] the job to insert
|
108
|
+
# @param scheduled_at [Integer,Float] the scheduled execution time for the jobs as an epoch timestamp
|
109
|
+
# @param json_serializer [#dump] the serializer to use to convert hashes into JSON
|
110
|
+
def insert_singular_job(job, json_serializer, scheduled_at)
|
111
|
+
sql_binds = job_sql_binds(job, scheduled_at, json_serializer)
|
112
|
+
ar_result = Exekutor::Job.connection.exec_query <<~SQL, ACTION_NAME, sql_binds, prepare: true
|
113
|
+
INSERT INTO exekutor_jobs ("queue", "priority", "scheduled_at", "active_job_id", "payload", "options") VALUES ($1, $2, to_timestamp($3), $4, $5, $6) RETURNING id;
|
114
|
+
SQL
|
115
|
+
ar_result.length
|
116
|
+
end
|
117
|
+
|
97
118
|
# Converts the specified job to SQL bind parameters to insert it into the database
|
98
119
|
# @param job [ActiveJob::Base] the job to insert
|
99
120
|
# @param scheduled_at [Float] the epoch timestamp for when the job should be executed
|
@@ -124,7 +145,7 @@ module Exekutor
|
|
124
145
|
def exekutor_options(job)
|
125
146
|
return nil unless job.respond_to?(:exekutor_options)
|
126
147
|
|
127
|
-
options = job.exekutor_options
|
148
|
+
options = job.exekutor_options&.stringify_keys
|
128
149
|
if options && options["queue_timeout"]
|
129
150
|
options["start_execution_before"] = Time.now.to_f + options.delete("queue_timeout").to_f
|
130
151
|
end
|
data/lib/exekutor/version.rb
CHANGED
data/lib/exekutor/worker.rb
CHANGED
@@ -23,17 +23,17 @@ module Exekutor
|
|
23
23
|
# @option config [Array<String>] :queues the queues to work on
|
24
24
|
# @option config [Integer] :min_threads the minimum number of execution threads that should be active
|
25
25
|
# @option config [Integer] :max_threads the maximum number of execution threads that may be active
|
26
|
-
# @option config [
|
27
|
-
# stopped
|
28
|
-
# @option config [
|
26
|
+
# @option config [ActiveSupport::Duration] :max_thread_idletime the maximum duration a thread may be idle before
|
27
|
+
# being stopped
|
28
|
+
# @option config [ActiveSupport::Duration] :polling_interval the polling interval
|
29
29
|
# @option config [Float] :poling_jitter the polling jitter
|
30
30
|
# @option config [Boolean] :set_db_connection_name whether the DB connection name should be set
|
31
31
|
# @option config [Integer,Boolean] :wait_for_termination how long the worker should wait on jobs to be completed
|
32
32
|
# before exiting
|
33
33
|
# @option config [Integer] :status_server_port the port to run the status server on
|
34
34
|
# @option config [String] :status_server_handler The name of the rack handler to use for the status server
|
35
|
-
# @option config [
|
36
|
-
# deems it as down
|
35
|
+
# @option config [ActiveSupport::Duration] :healthcheck_timeout The timeout of a worker in minutes before the
|
36
|
+
# healthcheck server deems it as down
|
37
37
|
def initialize(config = {})
|
38
38
|
super()
|
39
39
|
@config = config
|
@@ -178,7 +178,7 @@ module Exekutor
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def create_provider(worker_options, executor, thread_pool)
|
181
|
-
@reserver = Internal::Reserver.new @record.id, worker_options
|
181
|
+
@reserver = Internal::Reserver.new @record.id, **worker_options.slice(:queues, :min_priority, :max_priority)
|
182
182
|
provider = Internal::Provider.new reserver: @reserver, executor: executor, pool: thread_pool,
|
183
183
|
**provider_options(worker_options)
|
184
184
|
|
@@ -12,11 +12,12 @@ module Exekutor
|
|
12
12
|
# Creates the configuration file at +config/exekutor.yml+. Uses the current worker configuration as the base.
|
13
13
|
def create_configuration_file
|
14
14
|
config = { queues: %w[queues to watch] }.merge(Exekutor.config.worker_options)
|
15
|
-
config[:status_port] =
|
15
|
+
config[:status_port] = 12_677
|
16
16
|
config[:set_db_connection_name] = true
|
17
17
|
config[:wait_for_termination] = 120
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
filename = "config/exekutor#{".#{options[:identifier]}" if options[:identifier]}.yml"
|
20
|
+
create_file filename, { "exekutor" => config.stringify_keys }.to_yaml
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -43,11 +43,9 @@ module Exekutor
|
|
43
43
|
def create_fx_files
|
44
44
|
return unless defined?(Fx)
|
45
45
|
|
46
|
-
%w[
|
47
|
-
copy_file "functions/#{
|
48
|
-
|
49
|
-
%w[notify_workers requeue_orphaned_jobs].each do |trigger|
|
50
|
-
copy_file "triggers/#{trigger}.sql", Fx::Definition.new(name: trigger, version: 1, type: "trigger").full_path
|
46
|
+
%w[exekutor_broadcast_job_enqueued exekutor_requeue_orphaned_jobs].each do |name|
|
47
|
+
copy_file "functions/#{name}.sql", Fx::Definition.new(name: name, version: 1).full_path
|
48
|
+
copy_file "triggers/#{name}.sql", Fx::Definition.new(name: name, version: 1, type: "trigger").full_path
|
51
49
|
end
|
52
50
|
end
|
53
51
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
CREATE
|
2
|
+
OR REPLACE FUNCTION exekutor_broadcast_job_enqueued() RETURNS TRIGGER AS $$
|
3
|
+
BEGIN
|
4
|
+
PERFORM
|
5
|
+
pg_notify('exekutor::job_enqueued',
|
6
|
+
CONCAT('id:', NEW.id,';q:', NEW.queue,';p:', NEW.priority, ';t:', extract ('epoch' from NEW.scheduled_at)));
|
7
|
+
RETURN NULL;
|
8
|
+
END;
|
9
|
+
$$
|
10
|
+
LANGUAGE plpgsql
|
@@ -42,40 +42,40 @@ class CreateExekutorSchema < ActiveRecord::Migration[<%= migration_version %>]
|
|
42
42
|
t.jsonb :error, null: false
|
43
43
|
end
|
44
44
|
<% if defined? Fx %>
|
45
|
-
create_function :
|
46
|
-
create_trigger :
|
45
|
+
create_function :exekutor_broadcast_job_enqueued
|
46
|
+
create_trigger :exekutor_broadcast_job_enqueued, on: :exekutor_jobs
|
47
47
|
|
48
|
-
create_function :
|
49
|
-
create_trigger :
|
48
|
+
create_function :exekutor_requeue_orphaned_jobs
|
49
|
+
create_trigger :exekutor_requeue_orphaned_jobs, on: :exekutor_workers
|
50
50
|
<% else %>
|
51
51
|
reversible do |direction|
|
52
52
|
direction.up do
|
53
53
|
execute <<~SQL
|
54
|
-
<%= function_sql "
|
54
|
+
<%= function_sql "exekutor_broadcast_job_enqueued" %>
|
55
55
|
SQL
|
56
56
|
execute <<~SQL
|
57
|
-
<%= trigger_sql "
|
57
|
+
<%= trigger_sql "exekutor_broadcast_job_enqueued" %>
|
58
58
|
SQL
|
59
59
|
|
60
60
|
execute <<~SQL
|
61
|
-
<%= function_sql "
|
61
|
+
<%= function_sql "exekutor_requeue_orphaned_jobs" %>
|
62
62
|
SQL
|
63
63
|
execute <<~SQL
|
64
|
-
<%= trigger_sql "
|
64
|
+
<%= trigger_sql "exekutor_requeue_orphaned_jobs" %>
|
65
65
|
SQL
|
66
66
|
end
|
67
67
|
direction.down do
|
68
68
|
execute <<~SQL
|
69
|
-
DROP TRIGGER
|
69
|
+
DROP TRIGGER exekutor_requeue_orphaned_jobs ON exekutor_workers
|
70
70
|
SQL
|
71
71
|
execute <<~SQL
|
72
|
-
DROP FUNCTION
|
72
|
+
DROP FUNCTION exekutor_requeue_orphaned_jobs
|
73
73
|
SQL
|
74
74
|
execute <<~SQL
|
75
|
-
DROP TRIGGER
|
75
|
+
DROP TRIGGER exekutor_broadcast_job_enqueued ON exekutor_jobs
|
76
76
|
SQL
|
77
77
|
execute <<~SQL
|
78
|
-
DROP FUNCTION
|
78
|
+
DROP FUNCTION exekutor_broadcast_job_enqueued
|
79
79
|
SQL
|
80
80
|
end
|
81
81
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: exekutor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roy
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
34
34
|
LMMHpsz0vpxVqcs8USL9494hQUWRVlYd1F2PJeWtdKi0bU8dCduphJd8cTvCtS2l
|
35
35
|
CAY756btGBLeeWMBZ/DRMj1Cz3ifI9DV+KHqXg==
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2023-
|
37
|
+
date: 2023-12-19 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: activejob
|
@@ -410,12 +410,12 @@ files:
|
|
410
410
|
- lib/exekutor/worker.rb
|
411
411
|
- lib/generators/exekutor/configuration_generator.rb
|
412
412
|
- lib/generators/exekutor/install_generator.rb
|
413
|
-
- lib/generators/exekutor/templates/install/functions/
|
414
|
-
- lib/generators/exekutor/templates/install/functions/
|
413
|
+
- lib/generators/exekutor/templates/install/functions/exekutor_broadcast_job_enqueued.sql
|
414
|
+
- lib/generators/exekutor/templates/install/functions/exekutor_requeue_orphaned_jobs.sql
|
415
415
|
- lib/generators/exekutor/templates/install/initializers/exekutor.rb.erb
|
416
416
|
- lib/generators/exekutor/templates/install/migrations/create_exekutor_schema.rb.erb
|
417
|
-
- lib/generators/exekutor/templates/install/triggers/
|
418
|
-
- lib/generators/exekutor/templates/install/triggers/
|
417
|
+
- lib/generators/exekutor/templates/install/triggers/exekutor_broadcast_job_enqueued.sql
|
418
|
+
- lib/generators/exekutor/templates/install/triggers/exekutor_requeue_orphaned_jobs.sql
|
419
419
|
homepage: https://github.com/devdicated/exekutor
|
420
420
|
licenses:
|
421
421
|
- MIT
|
metadata.gz.sig
CHANGED
Binary file
|