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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/exekutor/configuration.rb +55 -24
  4. data/lib/exekutor/internal/cli/app.rb +3 -1
  5. data/lib/exekutor/internal/cli/info.rb +1 -1
  6. data/lib/exekutor/internal/cli/manager.rb +79 -35
  7. data/lib/exekutor/internal/configuration_builder.rb +13 -7
  8. data/lib/exekutor/internal/listener.rb +22 -7
  9. data/lib/exekutor/internal/logger.rb +1 -0
  10. data/lib/exekutor/internal/provider.rb +3 -3
  11. data/lib/exekutor/internal/reserver.rb +43 -11
  12. data/lib/exekutor/internal/status_server.rb +9 -8
  13. data/lib/exekutor/queue.rb +40 -19
  14. data/lib/exekutor/version.rb +1 -1
  15. data/lib/exekutor/worker.rb +6 -6
  16. data/lib/generators/exekutor/configuration_generator.rb +4 -3
  17. data/lib/generators/exekutor/install_generator.rb +3 -5
  18. data/lib/generators/exekutor/templates/install/functions/exekutor_broadcast_job_enqueued.sql +10 -0
  19. data/lib/generators/exekutor/templates/install/functions/exekutor_requeue_orphaned_jobs.sql +11 -0
  20. data/lib/generators/exekutor/templates/install/migrations/create_exekutor_schema.rb.erb +12 -12
  21. data/lib/generators/exekutor/templates/install/triggers/exekutor_broadcast_job_enqueued.sql +7 -0
  22. data/lib/generators/exekutor/templates/install/triggers/exekutor_requeue_orphaned_jobs.sql +5 -0
  23. data.tar.gz.sig +0 -0
  24. metadata +6 -6
  25. metadata.gz.sig +0 -0
  26. data/lib/generators/exekutor/templates/install/functions/job_notifier.sql +0 -7
  27. data/lib/generators/exekutor/templates/install/functions/requeue_orphaned_jobs.sql +0 -7
  28. data/lib/generators/exekutor/templates/install/triggers/notify_workers.sql +0 -6
  29. 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: 94b3158b3e0df0ca892836df74beb23ccfd8bddf5a1377a46b5ebd881cb5bee8
4
- data.tar.gz: a24e415df40ba9bf69d6972da2fb6461f60c655976d81f87ff36f2e365da42a6
3
+ metadata.gz: d5b178052eb277168916a76f663ba53882204481f740a7c30bc309d243de79cf
4
+ data.tar.gz: bfb648002d86bf278b41b5f7ca6fde0f1d3cb39954c445eec0e268bf72120efe
5
5
  SHA512:
6
- metadata.gz: 8de1e356a03b80f1e7dd82802956902531f76111cbea7210ee61eeeb34dbccc22120e5127f24ec9112328bfcb0f175f593f7ab4a9e6f347165b168503c4dbf49
7
- data.tar.gz: 33c945b0e29d611d2e25d98985e75df68494bb3463d014efda5a60c7402acb711541a465b6e4db10afcbb8f2db4b86d9d2c2fd3d6b23a140bf324eefbeb8cf51
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 in seconds. When set, the worker will poll the database with this interval to check for
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 [Integer]
186
+ # 60 seconds
187
+ # @return [ActiveSupport::Duration]
188
188
  # @!method $1=(value)
189
- # Sets the polling interval in seconds. 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 it is enabled, this value can
191
- # be reasonably high.
192
- # @param value [Integer] the interval
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: 60, type: [Integer, nil], range: 1...(1.day.to_i)
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 number of seconds a thread may be idle before being stopped.
243
+ # The maximum duration a thread may be idle before being stopped.
243
244
  # === Default value:
244
- # 60
245
- # @return [Integer]
245
+ # 60 seconds
246
+ # @return [ActiveSupport::Duration]
246
247
  # @!method $1=(value)
247
- # Sets the maximum number of seconds a thread may be idle before being stopped
248
- # @param value [Integer] the number of threads
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: 60, type: Integer, range: 1..(1.day.to_i)
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 heartbeat timeout for the `/live` endpoint of the status server, in minutes. If the heartbeat of a worker
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 [Integer]
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 heartbeat timeout for the `/live` endpoint of the status server, in minutes. Must be between 2
274
- # and 1440 (24 hours).
275
- # @param value [Integer] The timeout in minutes
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 :healthcheck_timeout, default: 30, type: Integer, range: 2..1440
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].each do |option|
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[p poll_interval], type: Integer, default_value: DefaultOptionValue.new(value: 60),
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).each do |worker|
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
- load_config_files(config_file, worker_options)
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 = parse_thread_limitations(cli_options[: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 parse_thread_limitations(threads)
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 <<~DESC
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.to_sentence(last_word_connector: ", or ")} (Actual: #{value.class})"
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 #{allowed_values.map(&:inspect)
105
- .to_sentence(last_word_connector: ", or ")}"
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, "##{name} should be between #{allowed_range.first} and #{allowed_range.last}#{
114
- " (exclusive)" if allowed_range.respond_to?(:exclude_end?) && allowed_range.exclude_end?}"
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, wait_timeout: 60, set_db_connection_name: false)
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
- # Whether this listener is listening to the given queue
71
- # @return [Boolean]
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.empty? || queues.include?(queue)
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 && listening_to_queue?(job_info["q"])
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 [Integer] the 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
- @queue_filter_sql = build_queue_filter_sql(queues)
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' #{@queue_filter_sql}
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! @queue_filter_sql.gsub(/^\s*AND\s+/, "") unless @queue_filter_sql.nil?
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
- Exekutor::Job.sanitize_sql_for_conditions(["AND queue IN (?)", queues])
84
- else
85
- Exekutor::Job.sanitize_sql_for_conditions(["AND queue = ?", queues])
86
- end
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, "queues must be nil, a String, Symbol, or an array of Strings or Symbols"
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
- if server.respond_to? :shutdown
56
- server.shutdown
57
- elsif server.respond_to? :stop
58
- server.stop
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.say! "Cannot shutdown status server, #{server.class.name} does not respond to shutdown or stop"
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} minutes)"
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.minutes.ago
111
+ last_heartbeat.nil? || last_heartbeat < @heartbeat_timeout.ago
111
112
  end
112
113
 
113
114
  def render_threads
@@ -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 [void]
22
- def push(*jobs)
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 [void]
30
- def schedule_at(*jobs, timestamp)
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
- unless jobs.is_a?(Array) && jobs.all?(ActiveJob::Base)
42
- raise ArgumentError, "jobs must be an array with ActiveJob items"
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
- sql_binds = job_sql_binds(jobs.first, scheduled_at, json_serializer)
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
- Exekutor::Job.connection.insert <<~SQL, ACTION_NAME
92
- INSERT INTO exekutor_jobs ("queue", "priority", "scheduled_at", "active_job_id", "payload", "options") VALUES #{insert_statements.join(",")}
93
- SQL
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.stringify_keys
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Exekutor
4
4
  # The current version of Exekutor
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
@@ -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 [Integer] :max_thread_idletime the maximum number of seconds a thread may be idle before being
27
- # stopped
28
- # @option config [Integer] :polling_interval the polling interval in seconds
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 [Integer] :healthcheck_timeout The timeout of a worker in minutes before the healthcheck server
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[:queues]
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] = 8765
15
+ config[:status_port] = 12_677
16
16
  config[:set_db_connection_name] = true
17
17
  config[:wait_for_termination] = 120
18
- create_file "config/exekutor#{".#{options[:identifier]}" if options[:identifier]}.yml",
19
- { "exekutor" => config.stringify_keys }.to_yaml
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[job_notifier requeue_orphaned_jobs].each do |function|
47
- copy_file "functions/#{function}.sql", Fx::Definition.new(name: function, version: 1).full_path
48
- end
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
@@ -0,0 +1,11 @@
1
+ CREATE
2
+ OR REPLACE FUNCTION exekutor_requeue_orphaned_jobs() RETURNS TRIGGER AS $$
3
+ BEGIN
4
+ UPDATE exekutor_jobs
5
+ SET status = 'p'
6
+ WHERE worker_id = OLD.id
7
+ AND status = 'e';
8
+ RETURN OLD;
9
+ END;
10
+ $$
11
+ 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 :job_notifier
46
- create_trigger :notify_workers
45
+ create_function :exekutor_broadcast_job_enqueued
46
+ create_trigger :exekutor_broadcast_job_enqueued, on: :exekutor_jobs
47
47
 
48
- create_function :requeue_orphaned_jobs
49
- create_trigger :requeue_orphaned_jobs
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 "job_notifier" %>
54
+ <%= function_sql "exekutor_broadcast_job_enqueued" %>
55
55
  SQL
56
56
  execute <<~SQL
57
- <%= trigger_sql "notify_workers" %>
57
+ <%= trigger_sql "exekutor_broadcast_job_enqueued" %>
58
58
  SQL
59
59
 
60
60
  execute <<~SQL
61
- <%= function_sql "requeue_orphaned_jobs" %>
61
+ <%= function_sql "exekutor_requeue_orphaned_jobs" %>
62
62
  SQL
63
63
  execute <<~SQL
64
- <%= trigger_sql "requeue_orphaned_jobs" %>
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 requeue_orphaned_jobs ON exekutor_workers
69
+ DROP TRIGGER exekutor_requeue_orphaned_jobs ON exekutor_workers
70
70
  SQL
71
71
  execute <<~SQL
72
- DROP FUNCTION requeue_orphaned_jobs
72
+ DROP FUNCTION exekutor_requeue_orphaned_jobs
73
73
  SQL
74
74
  execute <<~SQL
75
- DROP TRIGGER notify_exekutor_workers ON exekutor_jobs
75
+ DROP TRIGGER exekutor_broadcast_job_enqueued ON exekutor_jobs
76
76
  SQL
77
77
  execute <<~SQL
78
- DROP FUNCTION exekutor_job_notifier
78
+ DROP FUNCTION exekutor_broadcast_job_enqueued
79
79
  SQL
80
80
  end
81
81
  end
@@ -0,0 +1,7 @@
1
+ CREATE TRIGGER exekutor_broadcast_job_enqueued
2
+ AFTER INSERT OR
3
+ UPDATE OF queue, scheduled_at, status
4
+ ON exekutor_jobs
5
+ FOR EACH ROW
6
+ WHEN (NEW.status = 'p')
7
+ EXECUTE FUNCTION exekutor_broadcast_job_enqueued()
@@ -0,0 +1,5 @@
1
+ CREATE TRIGGER exekutor_requeue_orphaned_jobs
2
+ BEFORE DELETE
3
+ ON exekutor_workers
4
+ FOR EACH ROW
5
+ EXECUTE FUNCTION exekutor_requeue_orphaned_jobs()
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.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-05-14 00:00:00.000000000 Z
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/job_notifier.sql
414
- - lib/generators/exekutor/templates/install/functions/requeue_orphaned_jobs.sql
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/notify_workers.sql
418
- - lib/generators/exekutor/templates/install/triggers/requeue_orphaned_jobs.sql
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
@@ -1,7 +0,0 @@
1
- CREATE OR REPLACE FUNCTION exekutor_job_notifier() RETURNS TRIGGER AS $$
2
- BEGIN
3
- PERFORM pg_notify('exekutor::job_enqueued',
4
- CONCAT('id:', NEW.id,';q:', NEW.queue,';t:', extract ('epoch' from NEW.scheduled_at)));
5
- RETURN NULL;
6
- END;
7
- $$ LANGUAGE plpgsql
@@ -1,7 +0,0 @@
1
- CREATE OR REPLACE FUNCTION requeue_orphaned_jobs() RETURNS TRIGGER AS $$ BEGIN
2
- UPDATE exekutor_jobs
3
- SET status = 'p'
4
- WHERE worker_id = OLD.id
5
- AND status = 'e';
6
- RETURN OLD; END;
7
- $$ LANGUAGE plpgsql
@@ -1,6 +0,0 @@
1
- CREATE TRIGGER notify_exekutor_workers
2
- AFTER INSERT OR UPDATE OF queue, scheduled_at, status
3
- ON exekutor_jobs
4
- FOR EACH ROW
5
- WHEN (NEW.status = 'p')
6
- EXECUTE FUNCTION exekutor_job_notifier()
@@ -1,5 +0,0 @@
1
- CREATE TRIGGER requeue_orphaned_jobs
2
- BEFORE DELETE
3
- ON exekutor_workers
4
- FOR EACH ROW
5
- EXECUTE FUNCTION requeue_orphaned_jobs()