good_job 1.3.4 → 1.5.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 -11
- data/README.md +56 -11
- data/engine/app/{views/assets/_style.css.erb → assets/style.css} +0 -0
- data/engine/app/{views/vendor/bootstrap/_bootstrap-native.js.erb → assets/vendor/bootstrap/bootstrap-native.js} +0 -0
- data/engine/app/{views/vendor/bootstrap/_bootstrap.css.erb → assets/vendor/bootstrap/bootstrap.css} +0 -0
- data/engine/app/{views/vendor/chartist/_chartist.css.erb → assets/vendor/chartist/chartist.css} +0 -0
- data/engine/app/{views/vendor/chartist/_chartist.js.erb → assets/vendor/chartist/chartist.js} +0 -0
- 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 +6 -6
- 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/configuration.rb +16 -3
- data/lib/good_job/job.rb +7 -1
- data/lib/good_job/job_performer.rb +63 -0
- data/lib/good_job/lockable.rb +56 -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 +10 -19
- data/lib/good_job/version.rb +1 -1
- metadata +9 -135
- data/lib/good_job/performer.rb +0 -60
@@ -2,9 +2,9 @@ module ActiveJob # :nodoc:
|
|
2
2
|
module QueueAdapters # :nodoc:
|
3
3
|
# See {GoodJob::Adapter} for details.
|
4
4
|
class GoodJobAdapter < GoodJob::Adapter
|
5
|
-
def initialize(
|
6
|
-
configuration = GoodJob::Configuration.new(
|
7
|
-
super(execution_mode: configuration.rails_execution_mode
|
5
|
+
def initialize(**options)
|
6
|
+
configuration = GoodJob::Configuration.new(options, env: ENV)
|
7
|
+
super(**options.merge(execution_mode: configuration.rails_execution_mode))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
data/lib/good_job.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
require "rails"
|
2
|
-
|
3
2
|
require "active_job"
|
4
3
|
require "active_job/queue_adapters"
|
5
4
|
|
6
5
|
require "zeitwerk"
|
7
|
-
|
8
|
-
loader
|
9
|
-
|
10
|
-
|
11
|
-
)
|
12
|
-
loader.
|
13
|
-
|
6
|
+
Zeitwerk::Loader.for_gem.tap do |loader|
|
7
|
+
loader.inflector.inflect({
|
8
|
+
"cli" => "CLI",
|
9
|
+
})
|
10
|
+
loader.ignore(File.join(File.dirname(__FILE__), "generators"))
|
11
|
+
loader.setup
|
12
|
+
end
|
14
13
|
|
15
14
|
require "good_job/railtie"
|
16
15
|
|
data/lib/good_job/adapter.rb
CHANGED
@@ -20,13 +20,22 @@ module GoodJob
|
|
20
20
|
# @param max_threads [nil, Integer] sets the number of threads per scheduler to use when +execution_mode+ is set to +:async+. The +queues+ parameter can specify a number of threads for each group of queues which will override this value. You can also set this with the environment variable +GOOD_JOB_MAX_THREADS+. Defaults to +5+.
|
21
21
|
# @param queues [nil, String] determines which queues to execute jobs from when +execution_mode+ is set to +:async+. See {file:README.md#optimize-queues-threads-and-processes} for more details on the format of this string. You can also set this with the environment variable +GOOD_JOB_QUEUES+. Defaults to +"*"+.
|
22
22
|
# @param poll_interval [nil, Integer] sets the number of seconds between polls for jobs when +execution_mode+ is set to +:async+. You can also set this with the environment variable +GOOD_JOB_POLL_INTERVAL+. Defaults to +1+.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil)
|
24
|
+
if caller[0..4].find { |c| c.include?("/config/application.rb") || c.include?("/config/environments/") }
|
25
|
+
ActiveSupport::Deprecation.warn(<<~DEPRECATION)
|
26
|
+
GoodJob no longer recommends creating a GoodJob::Adapter instance:
|
27
|
+
|
28
|
+
config.active_job.queue_adapter = GoodJob::Adapter.new...
|
29
|
+
|
30
|
+
Instead, configure GoodJob through configuration:
|
31
|
+
|
32
|
+
config.active_job.queue_adapter = :good_job
|
33
|
+
config.good_job.execution_mode = :#{execution_mode}
|
34
|
+
config.good_job.max_threads = #{max_threads}
|
35
|
+
config.good_job.poll_interval = #{poll_interval}
|
36
|
+
# etc...
|
37
|
+
|
38
|
+
DEPRECATION
|
30
39
|
end
|
31
40
|
|
32
41
|
configuration = GoodJob::Configuration.new(
|
@@ -42,9 +51,9 @@ module GoodJob
|
|
42
51
|
raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(@execution_mode)
|
43
52
|
|
44
53
|
if @execution_mode == :async # rubocop:disable Style/GuardClause
|
45
|
-
@notifier =
|
54
|
+
@notifier = GoodJob::Notifier.new
|
46
55
|
@poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
47
|
-
@scheduler =
|
56
|
+
@scheduler = GoodJob::Scheduler.from_configuration(configuration)
|
48
57
|
@notifier.recipients << [@scheduler, :create_thread]
|
49
58
|
@poller.recipients << [@scheduler, :create_thread]
|
50
59
|
end
|
@@ -108,11 +117,5 @@ module GoodJob
|
|
108
117
|
def execute_inline?
|
109
118
|
@execution_mode == :inline
|
110
119
|
end
|
111
|
-
|
112
|
-
# (deprecated) Whether in +:inline+ execution mode.
|
113
|
-
def inline?
|
114
|
-
ActiveSupport::Deprecation.warn('GoodJob::Adapter::inline? is deprecated; use GoodJob::Adapter::execute_inline? instead')
|
115
|
-
execute_inline?
|
116
|
-
end
|
117
120
|
end
|
118
121
|
end
|
@@ -45,6 +45,8 @@ module GoodJob
|
|
45
45
|
def execution_mode(default: :external)
|
46
46
|
if options[:execution_mode]
|
47
47
|
options[:execution_mode]
|
48
|
+
elsif rails_config[:execution_mode]
|
49
|
+
rails_config[:execution_mode]
|
48
50
|
elsif env['GOOD_JOB_EXECUTION_MODE'].present?
|
49
51
|
env['GOOD_JOB_EXECUTION_MODE'].to_sym
|
50
52
|
else
|
@@ -58,9 +60,7 @@ module GoodJob
|
|
58
60
|
def rails_execution_mode
|
59
61
|
if execution_mode(default: nil)
|
60
62
|
execution_mode
|
61
|
-
elsif Rails.env.development?
|
62
|
-
:inline
|
63
|
-
elsif Rails.env.test?
|
63
|
+
elsif Rails.env.development? || Rails.env.test?
|
64
64
|
:inline
|
65
65
|
else
|
66
66
|
:external
|
@@ -74,6 +74,7 @@ module GoodJob
|
|
74
74
|
def max_threads
|
75
75
|
(
|
76
76
|
options[:max_threads] ||
|
77
|
+
rails_config[:max_threads] ||
|
77
78
|
env['GOOD_JOB_MAX_THREADS'] ||
|
78
79
|
env['RAILS_MAX_THREADS'] ||
|
79
80
|
DEFAULT_MAX_THREADS
|
@@ -87,6 +88,7 @@ module GoodJob
|
|
87
88
|
# @return [String]
|
88
89
|
def queue_string
|
89
90
|
options[:queues] ||
|
91
|
+
rails_config[:queues] ||
|
90
92
|
env['GOOD_JOB_QUEUES'] ||
|
91
93
|
'*'
|
92
94
|
end
|
@@ -98,17 +100,28 @@ module GoodJob
|
|
98
100
|
def poll_interval
|
99
101
|
(
|
100
102
|
options[:poll_interval] ||
|
103
|
+
rails_config[:poll_interval] ||
|
101
104
|
env['GOOD_JOB_POLL_INTERVAL'] ||
|
102
105
|
DEFAULT_POLL_INTERVAL
|
103
106
|
).to_i
|
104
107
|
end
|
105
108
|
|
109
|
+
# Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
|
110
|
+
# This configuration is only used when {GoodJob.preserve_job_records} is +true+.
|
111
|
+
# @return [Boolean]
|
106
112
|
def cleanup_preserved_jobs_before_seconds_ago
|
107
113
|
(
|
108
114
|
options[:before_seconds_ago] ||
|
115
|
+
rails_config[:cleanup_preserved_jobs_before_seconds_ago] ||
|
109
116
|
env['GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO'] ||
|
110
117
|
DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO
|
111
118
|
).to_i
|
112
119
|
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def rails_config
|
124
|
+
Rails.application.config.good_job
|
125
|
+
end
|
113
126
|
end
|
114
127
|
end
|
data/lib/good_job/job.rb
CHANGED
@@ -139,7 +139,7 @@ module GoodJob
|
|
139
139
|
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
140
140
|
good_job = good_jobs.first
|
141
141
|
# TODO: Determine why some records are fetched without an advisory lock at all
|
142
|
-
break unless good_job&.
|
142
|
+
break unless good_job&.executable?
|
143
143
|
|
144
144
|
result, error = good_job.perform
|
145
145
|
end
|
@@ -216,6 +216,12 @@ module GoodJob
|
|
216
216
|
[result, job_error]
|
217
217
|
end
|
218
218
|
|
219
|
+
# Tests whether this job is safe to be executed by this thread.
|
220
|
+
# @return [Boolean]
|
221
|
+
def executable?
|
222
|
+
self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
|
223
|
+
end
|
224
|
+
|
219
225
|
private
|
220
226
|
|
221
227
|
def execute
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'concurrent/delay'
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
#
|
5
|
+
# JobPerformer queries the database for jobs and performs them on behalf of a
|
6
|
+
# {Scheduler}. It mainly functions as glue between a {Scheduler} and the jobs
|
7
|
+
# it should be executing.
|
8
|
+
#
|
9
|
+
# The JobPerformer must be safe to execute across multiple threads.
|
10
|
+
#
|
11
|
+
class JobPerformer
|
12
|
+
# @param queue_string [String] Queues to execute jobs from
|
13
|
+
def initialize(queue_string)
|
14
|
+
@queue_string = queue_string
|
15
|
+
|
16
|
+
@job_query = Concurrent::Delay.new { GoodJob::Job.queue_string(queue_string) }
|
17
|
+
@parsed_queues = Concurrent::Delay.new { GoodJob::Job.queue_parser(queue_string) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# A meaningful name to identify the performer in logs and for debugging.
|
21
|
+
# @return [String] The queues from which Jobs are worked
|
22
|
+
def name
|
23
|
+
@queue_string
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform the next eligible job
|
27
|
+
# @return [nil, Object] Returns job result or +nil+ if no job was found
|
28
|
+
def next
|
29
|
+
job_query.perform_with_advisory_lock
|
30
|
+
end
|
31
|
+
|
32
|
+
# Tests whether this performer should be used in GoodJob's current state.
|
33
|
+
#
|
34
|
+
# For example, state will be a LISTEN/NOTIFY message that is passed down
|
35
|
+
# from the Notifier to the Scheduler. The Scheduler is able to ask
|
36
|
+
# its performer "does this message relate to you?", and if not, ignore it
|
37
|
+
# to minimize thread wake-ups, database queries, and thundering herds.
|
38
|
+
#
|
39
|
+
# @return [Boolean] whether the performer's {#next} method should be
|
40
|
+
# called in the current state.
|
41
|
+
def next?(state = {})
|
42
|
+
if parsed_queues[:exclude]
|
43
|
+
parsed_queues[:exclude].exclude?(state[:queue_name])
|
44
|
+
elsif parsed_queues[:include]
|
45
|
+
parsed_queues[:include].include?(state[:queue_name])
|
46
|
+
else
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :queue_string
|
54
|
+
|
55
|
+
def job_query
|
56
|
+
@job_query.value
|
57
|
+
end
|
58
|
+
|
59
|
+
def parsed_queues
|
60
|
+
@parsed_queues.value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/good_job/lockable.rb
CHANGED
@@ -32,11 +32,18 @@ module GoodJob
|
|
32
32
|
original_query = self
|
33
33
|
|
34
34
|
cte_table = Arel::Table.new(:rows)
|
35
|
-
|
35
|
+
cte_query = original_query.select(primary_key).except(:limit)
|
36
|
+
cte_type = if supports_cte_materialization_specifiers?
|
37
|
+
'MATERIALIZED'
|
38
|
+
else
|
39
|
+
''
|
40
|
+
end
|
41
|
+
|
42
|
+
composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::SqlLiteral.new([cte_type, "(", cte_query.to_sql, ")"].join(' ')))
|
36
43
|
|
37
44
|
query = cte_table.project(cte_table[:id])
|
38
|
-
|
39
|
-
|
45
|
+
.with(composed_cte)
|
46
|
+
.where(Arel.sql(sanitize_sql_for_conditions(["pg_try_advisory_lock(('x' || substr(md5(:table_name || #{connection.quote_table_name(cte_table.name)}.#{quoted_primary_key}::text), 1, 16))::bit(64)::bigint)", { table_name: table_name }])))
|
40
47
|
|
41
48
|
limit = original_query.arel.ast.limit
|
42
49
|
query.limit = limit.value if limit.present?
|
@@ -132,6 +139,12 @@ module GoodJob
|
|
132
139
|
records.each(&:advisory_unlock)
|
133
140
|
end
|
134
141
|
end
|
142
|
+
|
143
|
+
def supports_cte_materialization_specifiers?
|
144
|
+
return @_supports_cte_materialization_specifiers if defined?(@_supports_cte_materialization_specifiers)
|
145
|
+
|
146
|
+
@_supports_cte_materialization_specifiers = ActiveRecord::Base.connection.postgresql_version >= 120000
|
147
|
+
end
|
135
148
|
end
|
136
149
|
|
137
150
|
# Acquires an advisory lock on this record if it is not already locked by
|
@@ -140,10 +153,12 @@ module GoodJob
|
|
140
153
|
# all remaining locks).
|
141
154
|
# @return [Boolean] whether the lock was acquired.
|
142
155
|
def advisory_lock
|
143
|
-
|
144
|
-
|
156
|
+
query = <<~SQL.squish
|
157
|
+
SELECT 1 AS one
|
158
|
+
WHERE pg_try_advisory_lock(('x'||substr(md5($1 || $2::text), 1, 16))::bit(64)::bigint)
|
145
159
|
SQL
|
146
|
-
|
160
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
161
|
+
ActiveRecord::Base.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Lock', binds).any?
|
147
162
|
end
|
148
163
|
|
149
164
|
# Releases an advisory lock on this record if it is locked by this database
|
@@ -151,10 +166,12 @@ module GoodJob
|
|
151
166
|
# {#advisory_unlock} and {#advisory_lock} the same number of times.
|
152
167
|
# @return [Boolean] whether the lock was released.
|
153
168
|
def advisory_unlock
|
154
|
-
|
155
|
-
|
169
|
+
query = <<~SQL.squish
|
170
|
+
SELECT 1 AS one
|
171
|
+
WHERE pg_advisory_unlock(('x'||substr(md5($1 || $2::text), 1, 16))::bit(64)::bigint)
|
156
172
|
SQL
|
157
|
-
|
173
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
174
|
+
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Unlock', binds).any?
|
158
175
|
end
|
159
176
|
|
160
177
|
# Acquires an advisory lock on this record or raises
|
@@ -191,13 +208,32 @@ module GoodJob
|
|
191
208
|
# Tests whether this record has an advisory lock on it.
|
192
209
|
# @return [Boolean]
|
193
210
|
def advisory_locked?
|
194
|
-
|
211
|
+
query = <<~SQL.squish
|
212
|
+
SELECT 1 AS one
|
213
|
+
FROM pg_locks
|
214
|
+
WHERE pg_locks.locktype = 'advisory'
|
215
|
+
AND pg_locks.objsubid = 1
|
216
|
+
AND pg_locks.classid = ('x' || substr(md5($1 || $2::text), 1, 16))::bit(32)::int
|
217
|
+
AND pg_locks.objid = (('x' || substr(md5($3 || $4::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
218
|
+
SQL
|
219
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)], [nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
220
|
+
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Locked?', binds).any?
|
195
221
|
end
|
196
222
|
|
197
223
|
# Tests whether this record is locked by the current database session.
|
198
224
|
# @return [Boolean]
|
199
225
|
def owns_advisory_lock?
|
200
|
-
|
226
|
+
query = <<~SQL.squish
|
227
|
+
SELECT 1 AS one
|
228
|
+
FROM pg_locks
|
229
|
+
WHERE pg_locks.locktype = 'advisory'
|
230
|
+
AND pg_locks.objsubid = 1
|
231
|
+
AND pg_locks.classid = ('x' || substr(md5($1 || $2::text), 1, 16))::bit(32)::int
|
232
|
+
AND pg_locks.objid = (('x' || substr(md5($3 || $4::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
233
|
+
AND pg_locks.pid = pg_backend_pid()
|
234
|
+
SQL
|
235
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)], [nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
236
|
+
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Owns Advisory Lock?', binds).any?
|
201
237
|
end
|
202
238
|
|
203
239
|
# Releases all advisory locks on the record that are held by the current
|
@@ -213,5 +249,14 @@ module GoodJob
|
|
213
249
|
# Made public in Rails 5.2
|
214
250
|
self.class.send(:sanitize_sql_for_conditions, *args)
|
215
251
|
end
|
252
|
+
|
253
|
+
def pg_or_jdbc_query(query)
|
254
|
+
if Concurrent.on_jruby?
|
255
|
+
# Replace $1 bind parameters with ?
|
256
|
+
query.gsub(/\$\d*/, '?')
|
257
|
+
else
|
258
|
+
query
|
259
|
+
end
|
260
|
+
end
|
216
261
|
end
|
217
262
|
end
|
@@ -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!
|