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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -10
- data/README.md +59 -11
- data/engine/app/controllers/good_job/dashboards_controller.rb +2 -2
- data/engine/app/views/good_job/dashboards/index.html.erb +19 -7
- data/engine/app/views/layouts/good_job/base.html.erb +5 -5
- data/engine/app/views/shared/_chart.erb +1 -1
- data/engine/app/views/shared/_jobs_table.erb +27 -25
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -3
- data/lib/good_job.rb +7 -8
- data/lib/good_job/adapter.rb +18 -15
- data/lib/good_job/cli.rb +15 -1
- data/lib/good_job/configuration.rb +39 -13
- data/lib/good_job/daemon.rb +59 -0
- data/lib/good_job/job.rb +7 -1
- data/lib/good_job/job_performer.rb +63 -0
- data/lib/good_job/lockable.rb +42 -11
- data/lib/good_job/log_subscriber.rb +7 -7
- data/lib/good_job/notifier.rb +26 -14
- data/lib/good_job/poller.rb +3 -0
- data/lib/good_job/railtie.rb +6 -2
- data/lib/good_job/scheduler.rb +11 -20
- data/lib/good_job/version.rb +1 -1
- metadata +5 -46
- data/lib/good_job/performer.rb +0 -60
@@ -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
|
data/lib/good_job/notifier.rb
CHANGED
@@ -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
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
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!
|
data/lib/good_job/poller.rb
CHANGED
@@ -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
|
data/lib/good_job/railtie.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
module GoodJob
|
2
2
|
# Ruby on Rails integration.
|
3
3
|
class Railtie < ::Rails::Railtie
|
4
|
-
|
5
|
-
|
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
|
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -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 {
|
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:
|
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
|
-
|
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::
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
data/lib/good_job/version.rb
CHANGED
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.
|
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:
|
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.
|
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
|
data/lib/good_job/performer.rb
DELETED
@@ -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
|