good_job 1.0.3 → 1.1.4
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 +76 -11
- data/README.md +174 -32
- data/exe/good_job +1 -0
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -12
- data/lib/good_job.rb +28 -2
- data/lib/good_job/adapter.rb +33 -15
- data/lib/good_job/cli.rb +27 -43
- data/lib/good_job/configuration.rb +55 -0
- data/lib/good_job/current_execution.rb +35 -0
- data/lib/good_job/job.rb +40 -5
- data/lib/good_job/log_subscriber.rb +167 -0
- data/lib/good_job/multi_scheduler.rb +34 -0
- data/lib/good_job/notifier.rb +116 -0
- data/lib/good_job/performer.rb +11 -1
- data/lib/good_job/railtie.rb +11 -0
- data/lib/good_job/scheduler.rb +141 -26
- data/lib/good_job/version.rb +1 -1
- metadata +81 -7
- data/lib/good_job/logging.rb +0 -70
@@ -0,0 +1,34 @@
|
|
1
|
+
module GoodJob
|
2
|
+
class MultiScheduler
|
3
|
+
attr_reader :schedulers
|
4
|
+
|
5
|
+
def initialize(schedulers)
|
6
|
+
@schedulers = schedulers
|
7
|
+
end
|
8
|
+
|
9
|
+
def shutdown(wait: true)
|
10
|
+
schedulers.each { |s| s.shutdown(wait: wait) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def shutdown?
|
14
|
+
schedulers.all?(&:shutdown?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def restart(wait: true)
|
18
|
+
schedulers.each { |s| s.restart(wait: wait) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_thread(state = nil)
|
22
|
+
results = []
|
23
|
+
any_true = schedulers.any? do |scheduler|
|
24
|
+
scheduler.create_thread(state).tap { |result| results << result }
|
25
|
+
end
|
26
|
+
|
27
|
+
if any_true
|
28
|
+
true
|
29
|
+
else
|
30
|
+
results.any? { |result| result == false } ? false : nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'concurrent/atomic/atomic_boolean'
|
2
|
+
|
3
|
+
module GoodJob # :nodoc:
|
4
|
+
#
|
5
|
+
# Wrapper for Postgres LISTEN/NOTIFY
|
6
|
+
#
|
7
|
+
class Notifier
|
8
|
+
CHANNEL = 'good_job'.freeze
|
9
|
+
POOL_OPTIONS = {
|
10
|
+
min_threads: 0,
|
11
|
+
max_threads: 1,
|
12
|
+
auto_terminate: true,
|
13
|
+
idletime: 60,
|
14
|
+
max_queue: 1,
|
15
|
+
fallback_policy: :discard,
|
16
|
+
}.freeze
|
17
|
+
WAIT_INTERVAL = 1
|
18
|
+
|
19
|
+
# @!attribute [r] instances
|
20
|
+
# @!scope class
|
21
|
+
# @return [array<GoodJob:Adapter>] the instances of +GoodJob::Notifier+
|
22
|
+
cattr_reader :instances, default: [], instance_reader: false
|
23
|
+
|
24
|
+
def self.notify(message)
|
25
|
+
connection = ActiveRecord::Base.connection
|
26
|
+
connection.exec_query <<~SQL
|
27
|
+
NOTIFY #{CHANNEL}, #{connection.quote(message.to_json)}
|
28
|
+
SQL
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :recipients
|
32
|
+
|
33
|
+
def initialize(*recipients)
|
34
|
+
@recipients = Concurrent::Array.new(recipients)
|
35
|
+
@listening = Concurrent::AtomicBoolean.new(false)
|
36
|
+
|
37
|
+
self.class.instances << self
|
38
|
+
|
39
|
+
create_pool
|
40
|
+
listen
|
41
|
+
end
|
42
|
+
|
43
|
+
def listening?
|
44
|
+
@listening.true?
|
45
|
+
end
|
46
|
+
|
47
|
+
def restart(wait: true)
|
48
|
+
shutdown(wait: wait)
|
49
|
+
create_pool
|
50
|
+
listen
|
51
|
+
end
|
52
|
+
|
53
|
+
def shutdown(wait: true)
|
54
|
+
return unless @pool.running?
|
55
|
+
|
56
|
+
@pool.shutdown
|
57
|
+
@pool.wait_for_termination if wait
|
58
|
+
end
|
59
|
+
|
60
|
+
def shutdown?
|
61
|
+
!@pool.running?
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def create_pool
|
67
|
+
@pool = Concurrent::ThreadPoolExecutor.new(POOL_OPTIONS)
|
68
|
+
end
|
69
|
+
|
70
|
+
def listen
|
71
|
+
future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
|
72
|
+
Rails.application.reloader.wrap do
|
73
|
+
conn = ActiveRecord::Base.connection.raw_connection
|
74
|
+
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
75
|
+
conn.async_exec "LISTEN #{CHANNEL}"
|
76
|
+
end
|
77
|
+
|
78
|
+
begin
|
79
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
80
|
+
while pool.running?
|
81
|
+
listening.make_true
|
82
|
+
conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
|
83
|
+
listening.make_false
|
84
|
+
next unless channel == CHANNEL
|
85
|
+
|
86
|
+
ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
|
87
|
+
parsed_payload = JSON.parse(payload, symbolize_names: true)
|
88
|
+
recipients.each do |recipient|
|
89
|
+
target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
|
90
|
+
target.send(method_name, parsed_payload)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
listening.make_false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
rescue StandardError => e
|
97
|
+
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
|
98
|
+
raise
|
99
|
+
ensure
|
100
|
+
@listening.make_false
|
101
|
+
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
102
|
+
conn.async_exec "UNLISTEN *"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
future.add_observer(self, :listen_observer)
|
109
|
+
future.execute
|
110
|
+
end
|
111
|
+
|
112
|
+
def listen_observer(_time, _result, _thread_error)
|
113
|
+
listen unless shutdown?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/good_job/performer.rb
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
module GoodJob
|
2
2
|
class Performer
|
3
|
-
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(target, method_name, name: nil, filter: nil)
|
4
6
|
@target = target
|
5
7
|
@method_name = method_name
|
8
|
+
@name = name
|
9
|
+
@filter = filter
|
6
10
|
end
|
7
11
|
|
8
12
|
def next
|
9
13
|
@target.public_send(@method_name)
|
10
14
|
end
|
15
|
+
|
16
|
+
def next?(state = {})
|
17
|
+
return true unless @filter.respond_to?(:call)
|
18
|
+
|
19
|
+
@filter.call(state)
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
data/lib/good_job/railtie.rb
CHANGED
@@ -2,6 +2,17 @@ module GoodJob
|
|
2
2
|
class Railtie < ::Rails::Railtie
|
3
3
|
initializer "good_job.logger" do
|
4
4
|
ActiveSupport.on_load(:good_job) { self.logger = ::Rails.logger }
|
5
|
+
GoodJob::LogSubscriber.attach_to :good_job
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer "good_job.active_job_notifications" do
|
9
|
+
ActiveSupport::Notifications.subscribe "enqueue_retry.active_job" do |event|
|
10
|
+
GoodJob::CurrentExecution.error_on_retry = event.payload[:error]
|
11
|
+
end
|
12
|
+
|
13
|
+
ActiveSupport::Notifications.subscribe "discard.active_job" do |event|
|
14
|
+
GoodJob::CurrentExecution.error_on_discard = event.payload[:error]
|
15
|
+
end
|
5
16
|
end
|
6
17
|
end
|
7
18
|
end
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -2,16 +2,25 @@ require "concurrent/executor/thread_pool_executor"
|
|
2
2
|
require "concurrent/timer_task"
|
3
3
|
require "concurrent/utility/processor_counter"
|
4
4
|
|
5
|
-
module GoodJob
|
5
|
+
module GoodJob # :nodoc:
|
6
|
+
#
|
7
|
+
# Schedulers are generic thread execution pools that are responsible for
|
8
|
+
# periodically checking for available execution tasks, executing tasks in a
|
9
|
+
# bounded thread-pool, and efficiently scaling execution threads.
|
10
|
+
#
|
11
|
+
# Schedulers are "generic" in the sense that they delegate task execution
|
12
|
+
# details to a "Performer" object that responds to #next.
|
13
|
+
#
|
6
14
|
class Scheduler
|
15
|
+
# Defaults for instance of Concurrent::TimerTask
|
7
16
|
DEFAULT_TIMER_OPTIONS = {
|
8
17
|
execution_interval: 1,
|
9
18
|
timeout_interval: 1,
|
10
19
|
run_now: true,
|
11
20
|
}.freeze
|
12
21
|
|
22
|
+
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
13
23
|
DEFAULT_POOL_OPTIONS = {
|
14
|
-
name: 'good_job',
|
15
24
|
min_threads: 0,
|
16
25
|
max_threads: Concurrent.processor_count,
|
17
26
|
auto_terminate: true,
|
@@ -20,44 +29,113 @@ module GoodJob
|
|
20
29
|
fallback_policy: :discard,
|
21
30
|
}.freeze
|
22
31
|
|
32
|
+
# @!attribute [r] instances
|
33
|
+
# @!scope class
|
34
|
+
# All instantiated Schedulers in the current process.
|
35
|
+
# @return [array<GoodJob:Scheduler>]
|
36
|
+
cattr_reader :instances, default: [], instance_reader: false
|
37
|
+
|
38
|
+
# Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
|
39
|
+
# @param configuration [GoodJob::Configuration]
|
40
|
+
# @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
|
41
|
+
def self.from_configuration(configuration)
|
42
|
+
schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
|
43
|
+
queue_string, max_threads = queue_string_and_max_threads.split(':')
|
44
|
+
max_threads = (max_threads || configuration.max_threads).to_i
|
45
|
+
|
46
|
+
job_query = GoodJob::Job.queue_string(queue_string)
|
47
|
+
parsed = GoodJob::Job.queue_parser(queue_string)
|
48
|
+
job_filter = proc do |state|
|
49
|
+
if parsed[:exclude]
|
50
|
+
!parsed[:exclude].include? state[:queue_name]
|
51
|
+
elsif parsed[:include]
|
52
|
+
parsed[:include].include? state[:queue_name]
|
53
|
+
else
|
54
|
+
true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string, filter: job_filter)
|
58
|
+
|
59
|
+
timer_options = {}
|
60
|
+
timer_options[:execution_interval] = configuration.poll_interval
|
61
|
+
|
62
|
+
pool_options = {
|
63
|
+
max_threads: max_threads,
|
64
|
+
}
|
65
|
+
|
66
|
+
GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
|
67
|
+
end
|
68
|
+
|
69
|
+
if schedulers.size > 1
|
70
|
+
GoodJob::MultiScheduler.new(schedulers)
|
71
|
+
else
|
72
|
+
schedulers.first
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param performer [GoodJob::Performer]
|
77
|
+
# @param timer_options [Hash] Options to instantiate a Concurrent::TimerTask
|
78
|
+
# @param pool_options [Hash] Options to instantiate a Concurrent::ThreadPoolExecutor
|
23
79
|
def initialize(performer, timer_options: {}, pool_options: {})
|
24
80
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
25
81
|
|
82
|
+
self.class.instances << self
|
83
|
+
|
26
84
|
@performer = performer
|
27
|
-
@
|
28
|
-
@
|
29
|
-
create_thread
|
30
|
-
end
|
31
|
-
@timer.add_observer(self, :timer_observer)
|
32
|
-
@timer.execute
|
33
|
-
end
|
85
|
+
@pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
|
86
|
+
@timer_options = DEFAULT_TIMER_OPTIONS.merge(timer_options)
|
34
87
|
|
35
|
-
|
88
|
+
@pool_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@pool_options[:max_threads]} poll_interval=#{@timer_options[:execution_interval]})"
|
89
|
+
|
90
|
+
create_pools
|
36
91
|
end
|
37
92
|
|
93
|
+
# Shut down the Scheduler.
|
94
|
+
# @param wait [Boolean] Wait for actively executing jobs to finish
|
95
|
+
# @return [void]
|
38
96
|
def shutdown(wait: true)
|
39
97
|
@_shutdown = true
|
40
98
|
|
41
|
-
|
42
|
-
|
43
|
-
if @timer
|
99
|
+
instrument("scheduler_shutdown_start", { wait: wait })
|
100
|
+
instrument("scheduler_shutdown", { wait: wait }) do
|
101
|
+
if @timer&.running?
|
44
102
|
@timer.shutdown
|
45
103
|
@timer.wait_for_termination if wait
|
46
104
|
end
|
47
105
|
|
48
|
-
if @pool
|
106
|
+
if @pool&.running?
|
49
107
|
@pool.shutdown
|
50
108
|
@pool.wait_for_termination if wait
|
51
109
|
end
|
52
110
|
end
|
53
111
|
end
|
54
112
|
|
113
|
+
# True when the Scheduler is shutdown.
|
114
|
+
# @return [true, false, nil]
|
55
115
|
def shutdown?
|
56
116
|
@_shutdown
|
57
117
|
end
|
58
118
|
|
59
|
-
|
60
|
-
|
119
|
+
# Restart the Scheduler. When shutdown, start; or shutdown and start.
|
120
|
+
# @param wait [Boolean] Wait for actively executing jobs to finish
|
121
|
+
# @return [void]
|
122
|
+
def restart(wait: true)
|
123
|
+
instrument("scheduler_restart_pools") do
|
124
|
+
shutdown(wait: wait) unless shutdown?
|
125
|
+
create_pools
|
126
|
+
@_shutdown = false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Triggers a Performer execution, if an execution thread is available.
|
131
|
+
# @param state [nil, Object] Allows Performer#next? to accept or reject the execution
|
132
|
+
# @return [nil, Boolean] if the thread was created
|
133
|
+
def create_thread(state = nil)
|
134
|
+
return nil unless @pool.running? && @pool.ready_worker_count.positive?
|
135
|
+
|
136
|
+
if state
|
137
|
+
return false unless @performer.next?(state)
|
138
|
+
end
|
61
139
|
|
62
140
|
future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
|
63
141
|
output = nil
|
@@ -66,26 +144,63 @@ module GoodJob
|
|
66
144
|
end
|
67
145
|
future.add_observer(self, :task_observer)
|
68
146
|
future.execute
|
147
|
+
|
148
|
+
true
|
69
149
|
end
|
70
150
|
|
151
|
+
# Invoked on completion of TimerTask task.
|
152
|
+
# @!visibility private
|
153
|
+
# @return [void]
|
71
154
|
def timer_observer(time, executed_task, thread_error)
|
72
|
-
|
155
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
156
|
+
instrument("finished_timer_task", { result: executed_task, error: thread_error, time: time })
|
73
157
|
end
|
74
158
|
|
159
|
+
# Invoked on completion of ThreadPoolExecutor task
|
160
|
+
# @!visibility private
|
161
|
+
# @return [void]
|
75
162
|
def task_observer(time, output, thread_error)
|
76
|
-
|
163
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
164
|
+
instrument("finished_job_task", { result: output, error: thread_error, time: time })
|
77
165
|
create_thread if output
|
78
166
|
end
|
79
167
|
|
80
|
-
|
81
|
-
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
82
|
-
def ready_worker_count
|
83
|
-
synchronize do
|
84
|
-
workers_still_to_be_created = @max_length - @pool.length
|
85
|
-
workers_created_but_waiting = @ready.length
|
168
|
+
private
|
86
169
|
|
87
|
-
|
88
|
-
|
170
|
+
# @return [void]
|
171
|
+
def create_pools
|
172
|
+
instrument("scheduler_create_pools", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval] }) do
|
173
|
+
@pool = ThreadPoolExecutor.new(@pool_options)
|
174
|
+
next unless @timer_options[:execution_interval].positive?
|
175
|
+
|
176
|
+
@timer = Concurrent::TimerTask.new(@timer_options) { create_thread }
|
177
|
+
@timer.add_observer(self, :timer_observer)
|
178
|
+
@timer.execute
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def instrument(name, payload = {}, &block)
|
183
|
+
payload = payload.reverse_merge({
|
184
|
+
scheduler: self,
|
185
|
+
process_id: GoodJob::CurrentExecution.process_id,
|
186
|
+
thread_name: GoodJob::CurrentExecution.thread_name,
|
187
|
+
})
|
188
|
+
|
189
|
+
ActiveSupport::Notifications.instrument("#{name}.good_job", payload, &block)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Slightly customized sub-class of Concurrent::ThreadPoolExecutor
|
194
|
+
class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
|
195
|
+
# Number of idle or potential threads available to execute tasks
|
196
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
197
|
+
# @return [Integer]
|
198
|
+
def ready_worker_count
|
199
|
+
synchronize do
|
200
|
+
workers_still_to_be_created = @max_length - @pool.length
|
201
|
+
workers_created_but_waiting = @ready.length
|
202
|
+
|
203
|
+
workers_still_to_be_created + workers_created_but_waiting
|
89
204
|
end
|
90
205
|
end
|
91
206
|
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.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: dotenv
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: foreman
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,7 +151,35 @@ dependencies:
|
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
|
-
name: pry
|
154
|
+
name: pry-rails
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: puma
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rbtrace
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
142
184
|
requirements:
|
143
185
|
- - ">="
|
@@ -220,6 +262,34 @@ dependencies:
|
|
220
262
|
- - ">="
|
221
263
|
- !ruby/object:Gem::Version
|
222
264
|
version: '0'
|
265
|
+
- !ruby/object:Gem::Dependency
|
266
|
+
name: sigdump
|
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: yard
|
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'
|
223
293
|
description: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
224
294
|
email:
|
225
295
|
- bensheldon@gmail.com
|
@@ -241,9 +311,13 @@ files:
|
|
241
311
|
- lib/good_job.rb
|
242
312
|
- lib/good_job/adapter.rb
|
243
313
|
- lib/good_job/cli.rb
|
314
|
+
- lib/good_job/configuration.rb
|
315
|
+
- lib/good_job/current_execution.rb
|
244
316
|
- lib/good_job/job.rb
|
245
317
|
- lib/good_job/lockable.rb
|
246
|
-
- lib/good_job/
|
318
|
+
- lib/good_job/log_subscriber.rb
|
319
|
+
- lib/good_job/multi_scheduler.rb
|
320
|
+
- lib/good_job/notifier.rb
|
247
321
|
- lib/good_job/performer.rb
|
248
322
|
- lib/good_job/pg_locks.rb
|
249
323
|
- lib/good_job/railtie.rb
|
@@ -258,7 +332,7 @@ metadata:
|
|
258
332
|
documentation_uri: https://rdoc.info/github/bensheldon/good_job
|
259
333
|
homepage_uri: https://github.com/bensheldon/good_job
|
260
334
|
source_code_uri: https://github.com/bensheldon/good_job
|
261
|
-
post_install_message:
|
335
|
+
post_install_message:
|
262
336
|
rdoc_options:
|
263
337
|
- "--title"
|
264
338
|
- GoodJob - a multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
@@ -281,7 +355,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
281
355
|
version: '0'
|
282
356
|
requirements: []
|
283
357
|
rubygems_version: 3.0.3
|
284
|
-
signing_key:
|
358
|
+
signing_key:
|
285
359
|
specification_version: 4
|
286
360
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
287
361
|
test_files: []
|