exekutor 0.1.1 → 0.1.2
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/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
|