good_job 1.0.0 → 1.1.1
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 +109 -4
- data/README.md +223 -56
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -12
- data/lib/generators/good_job/install_generator.rb +24 -0
- data/lib/generators/good_job/templates/migration.rb.erb +20 -0
- data/lib/good_job.rb +6 -2
- data/lib/good_job/adapter.rb +30 -16
- data/lib/good_job/cli.rb +18 -48
- data/lib/good_job/configuration.rb +55 -0
- data/lib/good_job/job.rb +30 -25
- data/lib/good_job/log_subscriber.rb +110 -0
- data/lib/good_job/multi_scheduler.rb +25 -0
- data/lib/good_job/performer.rb +4 -1
- data/lib/good_job/railtie.rb +1 -0
- data/lib/good_job/scheduler.rb +83 -16
- data/lib/good_job/version.rb +1 -1
- metadata +38 -6
- data/lib/good_job/logging.rb +0 -70
@@ -0,0 +1,25 @@
|
|
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
|
22
|
+
schedulers.all?(&:create_thread)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/good_job/performer.rb
CHANGED
data/lib/good_job/railtie.rb
CHANGED
data/lib/good_job/scheduler.rb
CHANGED
@@ -15,38 +15,61 @@ module GoodJob
|
|
15
15
|
min_threads: 0,
|
16
16
|
max_threads: Concurrent.processor_count,
|
17
17
|
auto_terminate: true,
|
18
|
-
idletime:
|
19
|
-
max_queue:
|
20
|
-
fallback_policy: :
|
18
|
+
idletime: 60,
|
19
|
+
max_queue: -1,
|
20
|
+
fallback_policy: :discard,
|
21
21
|
}.freeze
|
22
22
|
|
23
|
+
cattr_reader :instances, default: [], instance_reader: false
|
24
|
+
|
25
|
+
def self.from_configuration(configuration)
|
26
|
+
schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
|
27
|
+
queue_string, max_threads = queue_string_and_max_threads.split(':')
|
28
|
+
max_threads = (max_threads || configuration.max_threads).to_i
|
29
|
+
|
30
|
+
job_query = GoodJob::Job.queue_string(queue_string)
|
31
|
+
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string)
|
32
|
+
|
33
|
+
timer_options = {}
|
34
|
+
timer_options[:execution_interval] = configuration.poll_interval if configuration.poll_interval.positive?
|
35
|
+
|
36
|
+
pool_options = {
|
37
|
+
max_threads: max_threads,
|
38
|
+
}
|
39
|
+
|
40
|
+
GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
|
41
|
+
end
|
42
|
+
|
43
|
+
if schedulers.size > 1
|
44
|
+
GoodJob::MultiScheduler.new(schedulers)
|
45
|
+
else
|
46
|
+
schedulers.first
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
23
50
|
def initialize(performer, timer_options: {}, pool_options: {})
|
24
51
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
25
52
|
|
53
|
+
self.class.instances << self
|
54
|
+
|
26
55
|
@performer = performer
|
27
|
-
@
|
28
|
-
@
|
29
|
-
idle_threads = @pool.max_length - @pool.length
|
30
|
-
create_thread if idle_threads.positive?
|
31
|
-
end
|
32
|
-
@timer.add_observer(self, :timer_observer)
|
33
|
-
@timer.execute
|
34
|
-
end
|
56
|
+
@pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
|
57
|
+
@timer_options = DEFAULT_TIMER_OPTIONS.merge(timer_options)
|
35
58
|
|
36
|
-
|
59
|
+
create_pools
|
37
60
|
end
|
38
61
|
|
39
62
|
def shutdown(wait: true)
|
40
63
|
@_shutdown = true
|
41
64
|
|
42
|
-
ActiveSupport::Notifications.instrument("
|
43
|
-
ActiveSupport::Notifications.instrument("scheduler_shutdown.good_job", { wait: wait }) do
|
44
|
-
if @timer
|
65
|
+
ActiveSupport::Notifications.instrument("scheduler_shutdown_start.good_job", { wait: wait, process_id: process_id })
|
66
|
+
ActiveSupport::Notifications.instrument("scheduler_shutdown.good_job", { wait: wait, process_id: process_id }) do
|
67
|
+
if @timer&.running?
|
45
68
|
@timer.shutdown
|
46
69
|
@timer.wait_for_termination if wait
|
47
70
|
end
|
48
71
|
|
49
|
-
if @pool
|
72
|
+
if @pool&.running?
|
50
73
|
@pool.shutdown
|
51
74
|
@pool.wait_for_termination if wait
|
52
75
|
end
|
@@ -57,7 +80,16 @@ module GoodJob
|
|
57
80
|
@_shutdown
|
58
81
|
end
|
59
82
|
|
83
|
+
def restart(wait: true)
|
84
|
+
ActiveSupport::Notifications.instrument("scheduler_restart_pools.good_job", { process_id: process_id }) do
|
85
|
+
shutdown(wait: wait) unless shutdown?
|
86
|
+
create_pools
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
60
90
|
def create_thread
|
91
|
+
return false unless @pool.ready_worker_count.positive?
|
92
|
+
|
61
93
|
future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
|
62
94
|
output = nil
|
63
95
|
Rails.application.executor.wrap { output = performer.next }
|
@@ -68,12 +100,47 @@ module GoodJob
|
|
68
100
|
end
|
69
101
|
|
70
102
|
def timer_observer(time, executed_task, thread_error)
|
103
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
71
104
|
ActiveSupport::Notifications.instrument("finished_timer_task.good_job", { result: executed_task, error: thread_error, time: time })
|
72
105
|
end
|
73
106
|
|
74
107
|
def task_observer(time, output, thread_error)
|
108
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
75
109
|
ActiveSupport::Notifications.instrument("finished_job_task.good_job", { result: output, error: thread_error, time: time })
|
76
110
|
create_thread if output
|
77
111
|
end
|
112
|
+
|
113
|
+
class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
|
114
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
115
|
+
def ready_worker_count
|
116
|
+
synchronize do
|
117
|
+
workers_still_to_be_created = @max_length - @pool.length
|
118
|
+
workers_created_but_waiting = @ready.length
|
119
|
+
|
120
|
+
workers_still_to_be_created + workers_created_but_waiting
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def create_pools
|
128
|
+
ActiveSupport::Notifications.instrument("scheduler_create_pools.good_job", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval], process_id: process_id }) do
|
129
|
+
@pool = ThreadPoolExecutor.new(@pool_options)
|
130
|
+
next unless @timer_options[:execution_interval].positive?
|
131
|
+
|
132
|
+
@timer = Concurrent::TimerTask.new(@timer_options) { create_thread }
|
133
|
+
@timer.add_observer(self, :timer_observer)
|
134
|
+
@timer.execute
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def process_id
|
139
|
+
Process.pid
|
140
|
+
end
|
141
|
+
|
142
|
+
def thread_name
|
143
|
+
Thread.current.name || Thread.current.object_id
|
144
|
+
end
|
78
145
|
end
|
79
146
|
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.1
|
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-12 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
|
@@ -150,6 +164,20 @@ dependencies:
|
|
150
164
|
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
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'
|
153
181
|
- !ruby/object:Gem::Dependency
|
154
182
|
name: rspec-rails
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -236,12 +264,16 @@ files:
|
|
236
264
|
- README.md
|
237
265
|
- exe/good_job
|
238
266
|
- lib/active_job/queue_adapters/good_job_adapter.rb
|
267
|
+
- lib/generators/good_job/install_generator.rb
|
268
|
+
- lib/generators/good_job/templates/migration.rb.erb
|
239
269
|
- lib/good_job.rb
|
240
270
|
- lib/good_job/adapter.rb
|
241
271
|
- lib/good_job/cli.rb
|
272
|
+
- lib/good_job/configuration.rb
|
242
273
|
- lib/good_job/job.rb
|
243
274
|
- lib/good_job/lockable.rb
|
244
|
-
- lib/good_job/
|
275
|
+
- lib/good_job/log_subscriber.rb
|
276
|
+
- lib/good_job/multi_scheduler.rb
|
245
277
|
- lib/good_job/performer.rb
|
246
278
|
- lib/good_job/pg_locks.rb
|
247
279
|
- lib/good_job/railtie.rb
|
@@ -256,7 +288,7 @@ metadata:
|
|
256
288
|
documentation_uri: https://rdoc.info/github/bensheldon/good_job
|
257
289
|
homepage_uri: https://github.com/bensheldon/good_job
|
258
290
|
source_code_uri: https://github.com/bensheldon/good_job
|
259
|
-
post_install_message:
|
291
|
+
post_install_message:
|
260
292
|
rdoc_options:
|
261
293
|
- "--title"
|
262
294
|
- GoodJob - a multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
@@ -279,7 +311,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
279
311
|
version: '0'
|
280
312
|
requirements: []
|
281
313
|
rubygems_version: 3.0.3
|
282
|
-
signing_key:
|
314
|
+
signing_key:
|
283
315
|
specification_version: 4
|
284
316
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
285
317
|
test_files: []
|
data/lib/good_job/logging.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
module GoodJob
|
2
|
-
module Logging
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
7
|
-
|
8
|
-
def self.tag_logger(*tags)
|
9
|
-
if logger.respond_to?(:tagged)
|
10
|
-
tags.unshift "GoodJob" unless logger.formatter.current_tags.include?("GoodJob")
|
11
|
-
logger.tagged(*tags) { yield }
|
12
|
-
else
|
13
|
-
yield
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
19
|
-
def create(event)
|
20
|
-
good_job = event.payload[:good_job]
|
21
|
-
|
22
|
-
info do
|
23
|
-
"Created GoodJob resource with id #{good_job.id}"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def timer_task_finished(event)
|
28
|
-
exception = event.payload[:error]
|
29
|
-
return unless exception
|
30
|
-
|
31
|
-
error do
|
32
|
-
"ERROR: #{exception}\n #{exception.backtrace}"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def job_finished(event)
|
37
|
-
exception = event.payload[:error]
|
38
|
-
return unless exception
|
39
|
-
|
40
|
-
error do
|
41
|
-
"ERROR: #{exception}\n #{exception.backtrace}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def scheduler_start_shutdown(_event)
|
46
|
-
info do
|
47
|
-
"Shutting down scheduler..."
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def scheduler_shutdown(_event)
|
52
|
-
info do
|
53
|
-
"Scheduler is shut down."
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def logger
|
60
|
-
GoodJob.logger
|
61
|
-
end
|
62
|
-
|
63
|
-
def thread_name
|
64
|
-
Thread.current.name || Thread.current.object_id
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
GoodJob::Logging::LogSubscriber.attach_to :good_job
|