good_job 1.3.5 → 1.6.0

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.
@@ -218,13 +218,13 @@ module GoodJob
218
218
  #
219
219
  %w(info debug warn error fatal unknown).each do |level|
220
220
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
221
- def #{level}(progname = nil, tags: [], &block)
222
- return unless logger
223
-
224
- tag_logger(*tags) do
225
- logger.#{level}(progname, &block)
226
- end
227
- end
221
+ def #{level}(progname = nil, tags: [], &block) # def info(progname = nil, tags: [], &block)
222
+ return unless logger # return unless logger
223
+ #
224
+ tag_logger(*tags) do # tag_logger(*tags) do
225
+ logger.#{level}(progname, &block) # logger.info(progname, &block)
226
+ end # end
227
+ end #
228
228
  METHOD
229
229
  end
230
230
  end
@@ -9,6 +9,9 @@ module GoodJob # :nodoc:
9
9
  # When a message is received, the notifier passes the message to each of its recipients.
10
10
  #
11
11
  class Notifier
12
+ # Raised if the Database adapter does not implement LISTEN.
13
+ AdapterCannotListenError = Class.new(StandardError)
14
+
12
15
  # Default Postgres channel for LISTEN/NOTIFY
13
16
  CHANNEL = 'good_job'.freeze
14
17
  # Defaults for instance of Concurrent::ThreadPoolExecutor
@@ -90,6 +93,20 @@ module GoodJob # :nodoc:
90
93
  !@pool.running?
91
94
  end
92
95
 
96
+ # Invoked on completion of ThreadPoolExecutor task
97
+ # @!visibility private
98
+ # @return [void]
99
+ def listen_observer(_time, _result, thread_error)
100
+ return if thread_error.is_a? AdapterCannotListenError
101
+
102
+ if thread_error
103
+ GoodJob.on_thread_error.call(thread_error) if GoodJob.on_thread_error.respond_to?(:call)
104
+ ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
105
+ end
106
+
107
+ listen unless shutdown?
108
+ end
109
+
93
110
  private
94
111
 
95
112
  def create_pool
@@ -100,7 +117,7 @@ module GoodJob # :nodoc:
100
117
  future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
101
118
  with_listen_connection do |conn|
102
119
  ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
103
- conn.async_exec "LISTEN #{CHANNEL}"
120
+ conn.async_exec("LISTEN #{CHANNEL}").clear
104
121
  end
105
122
 
106
123
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -120,14 +137,11 @@ module GoodJob # :nodoc:
120
137
  listening.make_false
121
138
  end
122
139
  end
123
- end
124
- rescue StandardError => e
125
- ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
126
- raise
127
- ensure
128
- @listening.make_false
129
- ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
130
- conn.async_exec "UNLISTEN *"
140
+ ensure
141
+ listening.make_false
142
+ ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
143
+ conn.async_exec("UNLISTEN *").clear
144
+ end
131
145
  end
132
146
  end
133
147
 
@@ -135,16 +149,14 @@ module GoodJob # :nodoc:
135
149
  future.execute
136
150
  end
137
151
 
138
- def listen_observer(_time, _result, _thread_error)
139
- listen unless shutdown?
140
- end
141
-
142
152
  def with_listen_connection
143
153
  ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
144
154
  ActiveRecord::Base.connection_pool.remove(conn)
145
155
  end
146
156
  pg_conn = ar_conn.raw_connection
147
- pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
157
+ raise AdapterCannotListenError unless pg_conn.respond_to? :wait_for_notify
158
+
159
+ pg_conn.async_exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}").clear
148
160
  yield pg_conn
149
161
  ensure
150
162
  ar_conn&.disconnect!
@@ -19,6 +19,9 @@ module GoodJob # :nodoc:
19
19
  # @return [array<GoodJob:Poller>]
20
20
  cattr_reader :instances, default: [], instance_reader: false
21
21
 
22
+ # Creates GoodJob::Poller from a GoodJob::Configuration instance.
23
+ # @param configuration [GoodJob::Configuration]
24
+ # @return [GoodJob::Poller]
22
25
  def self.from_configuration(configuration)
23
26
  GoodJob::Poller.new(poll_interval: configuration.poll_interval)
24
27
  end
@@ -1,8 +1,12 @@
1
1
  module GoodJob
2
2
  # Ruby on Rails integration.
3
3
  class Railtie < ::Rails::Railtie
4
- initializer "good_job.logger" do
5
- ActiveSupport.on_load(:good_job) { self.logger = ::Rails.logger }
4
+ config.good_job = ActiveSupport::OrderedOptions.new
5
+
6
+ initializer "good_job.logger" do |_app|
7
+ ActiveSupport.on_load(:good_job) do
8
+ self.logger = ::Rails.logger
9
+ end
6
10
  GoodJob::LogSubscriber.attach_to :good_job
7
11
  end
8
12
 
@@ -8,7 +8,7 @@ module GoodJob # :nodoc:
8
8
  # periodically checking for available tasks, executing tasks within a thread,
9
9
  # and efficiently scaling active threads.
10
10
  #
11
- # Every scheduler has a single {Performer} that will execute tasks.
11
+ # Every scheduler has a single {JobPerformer} that will execute tasks.
12
12
  # The scheduler is responsible for calling its performer efficiently across threads managed by an instance of +Concurrent::ThreadPoolExecutor+.
13
13
  # If a performer does not have work, the thread will go to sleep.
14
14
  # The scheduler maintains an instance of +Concurrent::TimerTask+, which wakes sleeping threads and causes them to check whether the performer has new work.
@@ -22,7 +22,7 @@ module GoodJob # :nodoc:
22
22
  max_threads: Configuration::DEFAULT_MAX_THREADS,
23
23
  auto_terminate: true,
24
24
  idletime: 60,
25
- max_queue: -1,
25
+ max_queue: 0,
26
26
  fallback_policy: :discard,
27
27
  }.freeze
28
28
 
@@ -40,19 +40,7 @@ module GoodJob # :nodoc:
40
40
  queue_string, max_threads = queue_string_and_max_threads.split(':')
41
41
  max_threads = (max_threads || configuration.max_threads).to_i
42
42
 
43
- job_query = GoodJob::Job.queue_string(queue_string)
44
- parsed = GoodJob::Job.queue_parser(queue_string)
45
- job_filter = proc do |state|
46
- if parsed[:exclude]
47
- parsed[:exclude].exclude?(state[:queue_name])
48
- elsif parsed[:include]
49
- parsed[:include].include? state[:queue_name]
50
- else
51
- true
52
- end
53
- end
54
- job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string, filter: job_filter)
55
-
43
+ job_performer = GoodJob::JobPerformer.new(queue_string)
56
44
  GoodJob::Scheduler.new(job_performer, max_threads: max_threads)
57
45
  end
58
46
 
@@ -63,7 +51,7 @@ module GoodJob # :nodoc:
63
51
  end
64
52
  end
65
53
 
66
- # @param performer [GoodJob::Performer]
54
+ # @param performer [GoodJob::JobPerformer]
67
55
  # @param max_threads [Numeric, nil] number of seconds between polls for jobs
68
56
  def initialize(performer, max_threads: nil)
69
57
  raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
@@ -170,10 +158,13 @@ module GoodJob # :nodoc:
170
158
  # @return [Integer]
171
159
  def ready_worker_count
172
160
  synchronize do
173
- workers_still_to_be_created = @max_length - @pool.length
174
- workers_created_but_waiting = @ready.length
175
-
176
- workers_still_to_be_created + workers_created_but_waiting
161
+ if Concurrent.on_jruby?
162
+ @executor.getMaximumPoolSize - @executor.getActiveCount
163
+ else
164
+ workers_still_to_be_created = @max_length - @pool.length
165
+ workers_created_but_waiting = @ready.length
166
+ workers_still_to_be_created + workers_created_but_waiting
167
+ end
177
168
  end
178
169
  end
179
170
  end
@@ -1,4 +1,4 @@
1
1
  module GoodJob
2
2
  # GoodJob gem version.
3
- VERSION = '1.3.5'.freeze
3
+ VERSION = '1.6.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-17 00:00:00.000000000 Z
11
+ date: 2021-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.0.2
55
- - !ruby/object:Gem::Dependency
56
- name: pg
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 1.0.0
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: 1.0.0
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: railties
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -262,34 +248,6 @@ dependencies:
262
248
  - - ">="
263
249
  - !ruby/object:Gem::Version
264
250
  version: '0'
265
- - !ruby/object:Gem::Dependency
266
- name: rails
267
- requirement: !ruby/object:Gem::Requirement
268
- requirements:
269
- - - ">="
270
- - !ruby/object:Gem::Version
271
- version: '0'
272
- type: :development
273
- prerelease: false
274
- version_requirements: !ruby/object:Gem::Requirement
275
- requirements:
276
- - - ">="
277
- - !ruby/object:Gem::Version
278
- version: '0'
279
- - !ruby/object:Gem::Dependency
280
- name: rbtrace
281
- requirement: !ruby/object:Gem::Requirement
282
- requirements:
283
- - - ">="
284
- - !ruby/object:Gem::Version
285
- version: '0'
286
- type: :development
287
- prerelease: false
288
- version_requirements: !ruby/object:Gem::Requirement
289
- requirements:
290
- - - ">="
291
- - !ruby/object:Gem::Version
292
- version: '0'
293
251
  - !ruby/object:Gem::Dependency
294
252
  name: rspec-rails
295
253
  requirement: !ruby/object:Gem::Requirement
@@ -399,12 +357,13 @@ files:
399
357
  - lib/good_job/cli.rb
400
358
  - lib/good_job/configuration.rb
401
359
  - lib/good_job/current_execution.rb
360
+ - lib/good_job/daemon.rb
402
361
  - lib/good_job/job.rb
362
+ - lib/good_job/job_performer.rb
403
363
  - lib/good_job/lockable.rb
404
364
  - lib/good_job/log_subscriber.rb
405
365
  - lib/good_job/multi_scheduler.rb
406
366
  - lib/good_job/notifier.rb
407
- - lib/good_job/performer.rb
408
367
  - lib/good_job/poller.rb
409
368
  - lib/good_job/railtie.rb
410
369
  - lib/good_job/scheduler.rb
@@ -441,7 +400,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
441
400
  - !ruby/object:Gem::Version
442
401
  version: '0'
443
402
  requirements: []
444
- rubygems_version: 3.1.4
403
+ rubygems_version: 3.2.4
445
404
  signing_key:
446
405
  specification_version: 4
447
406
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
@@ -1,60 +0,0 @@
1
- module GoodJob
2
- #
3
- # Performer queries the database for jobs and performs them on behalf of a
4
- # {Scheduler}. It mainly functions as glue between a {Scheduler} and the jobs
5
- # it should be executing.
6
- #
7
- # The Performer enforces a callable that does not rely on scoped/closure
8
- # variables because they might not be available when executed in a different
9
- # thread.
10
- #
11
- class Performer
12
- # @!attribute [r] name
13
- # @return [String]
14
- # a meaningful name to identify the performer in logs and for debugging.
15
- # This is usually set to the list of queues the performer will query,
16
- # e.g. +"-transactional_messages,batch_processing"+.
17
- attr_reader :name
18
-
19
- # @param target [Object]
20
- # An object that can perform jobs. It must respond to +method_name+ by
21
- # finding and performing jobs and is usually a {Job} query,
22
- # e.g. +GoodJob::Job.where(queue_name: ['queue1', 'queue2'])+.
23
- # @param method_name [Symbol]
24
- # The name of a method on +target+ that finds and performs jobs.
25
- # @param name [String]
26
- # A name for the performer to be used in logs and for debugging.
27
- # @param filter [#call]
28
- # Used to determine whether the performer should be used in GoodJob's
29
- # current state. GoodJob state is a +Hash+ that will be passed as the
30
- # first argument to +filter+ and includes info like the current queue.
31
- def initialize(target, method_name, name: nil, filter: nil)
32
- @target = target
33
- @method_name = method_name
34
- @name = name
35
- @filter = filter
36
- end
37
-
38
- # Find and perform any eligible jobs.
39
- def next
40
- @target.public_send(@method_name)
41
- end
42
-
43
- # Tests whether this performer should be used in GoodJob's current state by
44
- # calling the +filter+ callable set in {#initialize}. Always returns +true+
45
- # if there is no filter.
46
- #
47
- # For example, state will be a LISTEN/NOTIFY message that is passed down
48
- # from the Notifier to the Scheduler. The Scheduler is able to ask
49
- # its performer "does this message relate to you?", and if not, ignore it
50
- # to minimize thread wake-ups, database queries, and thundering herds.
51
- #
52
- # @return [Boolean] whether the performer's {#next} method should be
53
- # called in the current state.
54
- def next?(state = {})
55
- return true unless @filter.respond_to?(:call)
56
-
57
- @filter.call(state)
58
- end
59
- end
60
- end