good_job 3.10.1 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e632ae76e69e8601479181fb0093e9c6777510ac0743184a8f578b3c4dacee8
4
- data.tar.gz: 577f36ad182e6f4ff841abcd073925f4477f1086b1a0c7a454c12037f781ace7
3
+ metadata.gz: '0298665dd1e0d426def8cd08d422e41bfaf308f26de80e7079f80f4ae3403259'
4
+ data.tar.gz: 6ed8d5a5891515f4ef6404c1776a9147df1625d5be1a654b0407eb94cc96f36e
5
5
  SHA512:
6
- metadata.gz: f9211423d43cee468602aa1476d0d4b71a3831448157dc7dc540f98e0d1cb476e60bed884679ef10733af21c21e1c595dac14dc2d1c0b25d4b49b8d246f7be98
7
- data.tar.gz: 25a51a4196ea6618ca44ac73ed6cb9f97b63d99596bd144a9d51f439033a22d15ab16a73cd9d48954dc9b447b19cc9f03a21a9e5667d5021c85b5510ad7ca577
6
+ metadata.gz: c0d4b598ee46722d8a23c6e1d044ba2a6ab9933998b096a1749896bc4fba7b50f19ec71ce02fc5924732210a0ea0a442a79ff66e34c12b94c3b7e79048f1cae9
7
+ data.tar.gz: 39fee537236f2304305dc9b5ae794a6045d5fb66450f67e40a859b4f62c05c88dcea2cb85b328839fff94989f260a40a7bc4a76fffffd1107a861bbe7ac19a2b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.11.0](https://github.com/bensheldon/good_job/tree/v3.11.0) (2023-02-06)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.10.1...v3.11.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Create ActiveJob extension to disable sending a NOTIFY on enqueue and retry [\#814](https://github.com/bensheldon/good_job/pull/814) ([bensheldon](https://github.com/bensheldon))
10
+ - Add global enable\_listen\_notify configuration to disable both notify and listen [\#810](https://github.com/bensheldon/good_job/pull/810) ([mitchellhenke](https://github.com/mitchellhenke))
11
+
12
+ **Merged pull requests:**
13
+
14
+ - Test Matrix: Use Ruby 3.2 to test against all Postgres versions; add PG 15, remove PG 10 [\#828](https://github.com/bensheldon/good_job/pull/828) ([bensheldon](https://github.com/bensheldon))
15
+ - Bump nokogiri from 1.14.0 to 1.14.1 [\#827](https://github.com/bensheldon/good_job/pull/827) ([dependabot[bot]](https://github.com/apps/dependabot))
16
+ - Bump rubocop-performance from 1.15.2 to 1.16.0 [\#826](https://github.com/bensheldon/good_job/pull/826) ([dependabot[bot]](https://github.com/apps/dependabot))
17
+
3
18
  ## [v3.10.1](https://github.com/bensheldon/good_job/tree/v3.10.1) (2023-02-06)
4
19
 
5
20
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.10.0...v3.10.1)
data/README.md CHANGED
@@ -176,6 +176,7 @@ Options:
176
176
  [--max-cache=COUNT] # Maximum number of scheduled jobs to cache in memory (env var: GOOD_JOB_MAX_CACHE, default: 10000)
177
177
  [--shutdown-timeout=SECONDS] # Number of seconds to wait for jobs to finish when shutting down before stopping the thread. (env var: GOOD_JOB_SHUTDOWN_TIMEOUT, default: -1 (forever))
178
178
  [--enable-cron] # Whether to run cron process (default: false)
179
+ [--enable-listen-notify] # Whether to enqueue and read jobs with Postgres LISTEN/NOTIFY (default: true)
179
180
  [--daemonize] # Run as a background daemon (default: false)
180
181
  [--pidfile=PIDFILE] # Path to write daemonized Process ID (env var: GOOD_JOB_PIDFILE, default: tmp/pids/good_job.pid)
181
182
  [--probe-port=PORT] # Port for http health check (env var: GOOD_JOB_PROBE_PORT, default: nil)
@@ -272,6 +273,7 @@ Available configuration options are:
272
273
  - `max_cache` (integer) sets the maximum number of scheduled jobs that will be stored in memory to reduce execution latency when also polling for scheduled jobs. Caching 10,000 scheduled jobs uses approximately 20MB of memory. You can also set this with the environment variable `GOOD_JOB_MAX_CACHE`.
273
274
  - `shutdown_timeout` (integer) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
274
275
  - `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
276
+ - `enable_listen_notify` (boolean) whether to enqueue and read jobs with Postgres LISTEN/NOTIFY. Defaults to `true`. You can also set this with the environment variable `GOOD_JOB_ENABLE_LISTEN_NOTIFY`.
275
277
  - `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
276
278
  - `cleanup_discarded_jobs` (boolean) whether to destroy discarded jobs when cleaning up preserved jobs using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `true`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_DISCARDED_JOBS`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
277
279
  - `cleanup_preserved_jobs_before_seconds_ago` (integer) number of seconds to preserve jobs when using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `1209600` (14 days). Can also be set with the environment variable `GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ module ActiveJobExtensions
4
+ # Allows configuring whether GoodJob should emit a NOTIFY event when a job is enqueued.
5
+ # Configuration will apply either globally to the Job Class, or individually to jobs
6
+ # on initial enqueue and subsequent retries.
7
+ #
8
+ # @example
9
+ # # Include the concern to your job class:
10
+ # class MyJob < ApplicationJob
11
+ # include GoodJob::ActiveJobExtensions::Notify
12
+ # self.good_job_notify = false
13
+ # end
14
+ #
15
+ # # Or, configure jobs individually to not notify:
16
+ # MyJob.set(good_job_notify: false).perform_later
17
+ #
18
+ module NotifyOptions
19
+ extend ActiveSupport::Concern
20
+
21
+ module Prepends
22
+ def enqueue(options = {})
23
+ self.good_job_notify = options[:good_job_notify] if options.key?(:good_job_notify)
24
+ super
25
+ end
26
+
27
+ def serialize
28
+ super.tap do |job_data|
29
+ # Only serialize the value if present to reduce the size of the serialized job
30
+ job_data["good_job_notify"] = good_job_notify unless good_job_notify.nil?
31
+ end
32
+ end
33
+
34
+ def deserialize(job_data)
35
+ super
36
+ self.good_job_notify = job_data["good_job_notify"]
37
+ end
38
+ end
39
+
40
+ included do
41
+ prepend Prepends
42
+ class_attribute :good_job_notify, instance_accessor: false, instance_predicate: false, default: nil
43
+ attr_accessor :good_job_notify
44
+ end
45
+ end
46
+ end
47
+ end
@@ -90,14 +90,20 @@ module GoodJob
90
90
  inline_executions.each(&:advisory_unlock)
91
91
  end
92
92
 
93
- executions.reject(&:finished_at).group_by(&:queue_name).each do |queue_name, executions_by_queue|
94
- executions_by_queue.group_by(&:scheduled_at).each do |scheduled_at, executions_by_queue_and_scheduled_at|
95
- # TODO: have Adapter#create_thread handle state[:count] values
96
- state = { queue_name: queue_name, count: executions_by_queue_and_scheduled_at.size }
97
- state[:scheduled_at] = scheduled_at if scheduled_at
98
-
99
- executed_locally = execute_async? && @scheduler&.create_thread(state)
100
- Notifier.notify(state) unless executed_locally
93
+ non_inline_executions = executions.reject(&:finished_at)
94
+ if non_inline_executions.any?
95
+ job_id_to_active_jobs = active_jobs.index_by(&:job_id)
96
+ non_inline_executions.group_by(&:queue_name).each do |queue_name, executions_by_queue|
97
+ executions_by_queue.group_by(&:scheduled_at).each do |scheduled_at, executions_by_queue_and_scheduled_at|
98
+ state = { queue_name: queue_name, count: executions_by_queue_and_scheduled_at.size }
99
+ state[:scheduled_at] = scheduled_at if scheduled_at
100
+
101
+ executed_locally = execute_async? && @scheduler&.create_thread(state)
102
+ unless executed_locally
103
+ state[:count] = job_id_to_active_jobs.values_at(*executions_by_queue_and_scheduled_at.map(&:active_job_id)).count { |active_job| send_notify?(active_job) }
104
+ Notifier.notify(state) unless state[:count].zero?
105
+ end
106
+ end
101
107
  end
102
108
  end
103
109
 
@@ -136,7 +142,7 @@ module GoodJob
136
142
  job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
137
143
 
138
144
  executed_locally = execute_async? && @scheduler&.create_thread(job_state)
139
- Notifier.notify(job_state) unless executed_locally
145
+ Notifier.notify(job_state) if !executed_locally && send_notify?(active_job)
140
146
  end
141
147
 
142
148
  execution
@@ -193,7 +199,7 @@ module GoodJob
193
199
  def start_async
194
200
  return unless execute_async?
195
201
 
196
- @notifier = GoodJob::Notifier.new
202
+ @notifier = GoodJob::Notifier.new(enable_listening: GoodJob.configuration.enable_listen_notify)
197
203
  @poller = GoodJob::Poller.new(poll_interval: GoodJob.configuration.poll_interval)
198
204
  @scheduler = GoodJob::Scheduler.from_configuration(GoodJob.configuration, warm_cache_on_initialize: true)
199
205
  @notifier.recipients << [@scheduler, :create_thread]
@@ -226,5 +232,12 @@ module GoodJob
226
232
  (Concurrent.on_jruby? && self_caller.grep(%r{jruby/rack/rails_booter}).any?) # EXAMPLE: uri:classloader:/jruby/rack/rails_booter.rb:83:in `load_environment'
227
233
  end
228
234
  end
235
+
236
+ def send_notify?(active_job)
237
+ return true unless active_job.respond_to?(:good_job_notify)
238
+ return false unless GoodJob.configuration.enable_listen_notify
239
+
240
+ !(active_job.good_job_notify == false || (active_job.class.good_job_notify == false && active_job.good_job_notify.nil?))
241
+ end
229
242
  end
230
243
  end
data/lib/good_job/cli.rb CHANGED
@@ -95,7 +95,7 @@ module GoodJob
95
95
 
96
96
  Daemon.new(pidfile: configuration.pidfile).daemonize if configuration.daemonize?
97
97
 
98
- notifier = GoodJob::Notifier.new
98
+ notifier = GoodJob::Notifier.new(enable_listening: GoodJob.configuration.enable_listen_notify)
99
99
  poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
100
100
  scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
101
101
  notifier.recipients << [scheduler, :create_thread]
@@ -26,6 +26,8 @@ module GoodJob
26
26
  DEFAULT_SHUTDOWN_TIMEOUT = -1
27
27
  # Default to not running cron
28
28
  DEFAULT_ENABLE_CRON = false
29
+ # Default to enabling LISTEN/NOTIFY
30
+ DEFAULT_ENABLE_LISTEN_NOTIFY = true
29
31
 
30
32
  def self.validate_execution_mode(execution_mode)
31
33
  raise ArgumentError, "GoodJob execution mode must be one of #{EXECUTION_MODES.join(', ')}. It was '#{execution_mode}' which is not valid." unless execution_mode.in?(EXECUTION_MODES)
@@ -331,6 +333,14 @@ module GoodJob
331
333
  env['GOOD_JOB_PROBE_PORT']
332
334
  end
333
335
 
336
+ def enable_listen_notify
337
+ return options[:enable_listen_notify] unless options[:enable_listen_notify].nil?
338
+ return rails_config[:enable_listen_notify] unless rails_config[:enable_listen_notify].nil?
339
+ return ActiveModel::Type::Boolean.new.cast(env['GOOD_JOB_ENABLE_LISTEN_NOTIFY']) unless env['GOOD_JOB_ENABLE_LISTEN_NOTIFY'].nil?
340
+
341
+ DEFAULT_ENABLE_LISTEN_NOTIFY
342
+ end
343
+
334
344
  private
335
345
 
336
346
  def rails_config
@@ -68,11 +68,14 @@ module GoodJob # :nodoc:
68
68
  attr_reader :recipients
69
69
 
70
70
  # @param recipients [Array<#call, Array(Object, Symbol)>]
71
- def initialize(*recipients)
71
+ # @param enable_listening [true, false]
72
+ def initialize(*recipients, enable_listening: true)
72
73
  @recipients = Concurrent::Array.new(recipients)
74
+ @connected = Concurrent::AtomicBoolean.new(false)
73
75
  @listening = Concurrent::AtomicBoolean.new(false)
74
76
  @connection_errors_count = Concurrent::AtomicFixnum.new(0)
75
77
  @connection_errors_reported = Concurrent::AtomicBoolean.new(false)
78
+ @enable_listening = enable_listening
76
79
 
77
80
  self.class.instances << self
78
81
 
@@ -80,7 +83,13 @@ module GoodJob # :nodoc:
80
83
  listen
81
84
  end
82
85
 
83
- # Tests whether the notifier is active and listening for new messages.
86
+ # Tests whether the notifier is active and has acquired a dedicated database connection.
87
+ # @return [true, false, nil]
88
+ def connected?
89
+ @connected.true?
90
+ end
91
+
92
+ # Tests whether the notifier is listening for new messages.
84
93
  # @return [true, false, nil]
85
94
  def listening?
86
95
  @listening.true?
@@ -165,15 +174,17 @@ module GoodJob # :nodoc:
165
174
  end
166
175
 
167
176
  def listen(delay: 0)
168
- future = Concurrent::ScheduledTask.new(delay, args: [@recipients, executor, @listening], executor: @executor) do |thr_recipients, thr_executor, thr_listening|
177
+ future = Concurrent::ScheduledTask.new(delay, args: [@recipients, executor, @enable_listening, @listening], executor: @executor) do |thr_recipients, thr_executor, thr_enable_listening, thr_listening|
169
178
  with_connection do
170
179
  begin
171
180
  Rails.application.executor.wrap do
172
181
  run_callbacks :listen do
173
- ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
174
- connection.execute("LISTEN #{CHANNEL}")
182
+ if thr_enable_listening
183
+ ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
184
+ connection.execute("LISTEN #{CHANNEL}")
185
+ thr_listening.make_true
186
+ end
175
187
  end
176
- thr_listening.make_true
177
188
  end
178
189
  end
179
190
 
@@ -195,9 +206,11 @@ module GoodJob # :nodoc:
195
206
  ensure
196
207
  Rails.application.executor.wrap do
197
208
  run_callbacks :unlisten do
198
- thr_listening.make_false
199
- ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
200
- connection.execute("UNLISTEN *")
209
+ if thr_enable_listening
210
+ ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
211
+ thr_listening.make_false
212
+ connection.execute("UNLISTEN *")
213
+ end
201
214
  end
202
215
  end
203
216
  end
@@ -215,20 +228,22 @@ module GoodJob # :nodoc:
215
228
  end
216
229
  end
217
230
  connection.execute("SET application_name = #{connection.quote(self.class.name)}")
231
+ @connected.make_true
218
232
 
219
233
  yield
220
234
  ensure
235
+ @connected.make_false
221
236
  connection&.disconnect!
222
237
  self.connection = nil
223
238
  end
224
239
 
225
240
  def wait_for_notify
226
241
  raw_connection = connection.raw_connection
227
- if raw_connection.respond_to?(:wait_for_notify)
242
+ if @enable_listening && raw_connection.respond_to?(:wait_for_notify)
228
243
  raw_connection.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
229
244
  yield(channel, payload)
230
245
  end
231
- elsif raw_connection.respond_to?(:jdbc_connection)
246
+ elsif @enable_listening && raw_connection.respond_to?(:jdbc_connection)
232
247
  raw_connection.execute_query("SELECT 1")
233
248
  notifications = raw_connection.jdbc_connection.getNotifications
234
249
  Array(notifications).each do |notification|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '3.10.1'
4
+ VERSION = '3.11.0'
5
5
  end
data/lib/good_job.rb CHANGED
@@ -7,8 +7,9 @@ require "good_job/engine"
7
7
 
8
8
  require "good_job/adapter"
9
9
  require "active_job/queue_adapters/good_job_adapter"
10
- require "good_job/active_job_extensions/concurrency"
11
10
  require "good_job/active_job_extensions/batches"
11
+ require "good_job/active_job_extensions/concurrency"
12
+ require "good_job/active_job_extensions/notify_options"
12
13
 
13
14
  require "good_job/assignable_connection"
14
15
  require "good_job/bulk"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.1
4
+ version: 3.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
@@ -403,6 +403,7 @@ files:
403
403
  - lib/good_job.rb
404
404
  - lib/good_job/active_job_extensions/batches.rb
405
405
  - lib/good_job/active_job_extensions/concurrency.rb
406
+ - lib/good_job/active_job_extensions/notify_options.rb
406
407
  - lib/good_job/adapter.rb
407
408
  - lib/good_job/assignable_connection.rb
408
409
  - lib/good_job/bulk.rb