good_job 1.2.4 → 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -7
- data/README.md +13 -10
- data/engine/app/controllers/good_job/active_jobs_controller.rb +8 -0
- data/engine/app/controllers/good_job/base_controller.rb +5 -0
- data/engine/app/controllers/good_job/dashboards_controller.rb +50 -0
- data/engine/app/helpers/good_job/application_helper.rb +4 -0
- data/engine/app/views/assets/_style.css.erb +16 -0
- data/engine/app/views/good_job/active_jobs/show.html.erb +1 -0
- data/engine/app/views/good_job/dashboards/index.html.erb +19 -0
- data/engine/app/views/layouts/good_job/base.html.erb +50 -0
- data/engine/app/views/shared/_chart.erb +51 -0
- data/engine/app/views/shared/_jobs_table.erb +26 -0
- data/engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb +1662 -0
- data/engine/app/views/vendor/bootstrap/_bootstrap.css.erb +10258 -0
- data/engine/app/views/vendor/chartist/_chartist.css.erb +613 -0
- data/engine/app/views/vendor/chartist/_chartist.js.erb +4516 -0
- data/engine/config/routes.rb +4 -0
- data/engine/lib/good_job/engine.rb +5 -0
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -2
- data/lib/generators/good_job/install_generator.rb +8 -0
- data/lib/good_job.rb +40 -24
- data/lib/good_job/adapter.rb +38 -0
- data/lib/good_job/cli.rb +30 -7
- data/lib/good_job/configuration.rb +44 -0
- data/lib/good_job/job.rb +116 -20
- data/lib/good_job/lockable.rb +119 -6
- data/lib/good_job/log_subscriber.rb +70 -4
- data/lib/good_job/multi_scheduler.rb +6 -0
- data/lib/good_job/notifier.rb +55 -29
- data/lib/good_job/performer.rb +38 -0
- data/lib/good_job/railtie.rb +1 -0
- data/lib/good_job/scheduler.rb +33 -20
- data/lib/good_job/version.rb +2 -1
- metadata +96 -9
data/lib/good_job/performer.rb
CHANGED
@@ -1,7 +1,33 @@
|
|
1
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
|
+
#
|
2
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"+.
|
3
17
|
attr_reader :name
|
4
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.
|
5
31
|
def initialize(target, method_name, name: nil, filter: nil)
|
6
32
|
@target = target
|
7
33
|
@method_name = method_name
|
@@ -9,10 +35,22 @@ module GoodJob
|
|
9
35
|
@filter = filter
|
10
36
|
end
|
11
37
|
|
38
|
+
# Find and perform any eligible jobs.
|
12
39
|
def next
|
13
40
|
@target.public_send(@method_name)
|
14
41
|
end
|
15
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.
|
16
54
|
def next?(state = {})
|
17
55
|
return true unless @filter.respond_to?(:call)
|
18
56
|
|
data/lib/good_job/railtie.rb
CHANGED
data/lib/good_job/scheduler.rb
CHANGED
@@ -4,15 +4,18 @@ require "concurrent/utility/processor_counter"
|
|
4
4
|
|
5
5
|
module GoodJob # :nodoc:
|
6
6
|
#
|
7
|
-
# Schedulers are generic thread
|
8
|
-
# periodically checking for available
|
9
|
-
#
|
7
|
+
# Schedulers are generic thread pools that are responsible for
|
8
|
+
# periodically checking for available tasks, executing tasks within a thread,
|
9
|
+
# and efficiently scaling active threads.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# Every scheduler has a single {Performer} that will execute tasks.
|
12
|
+
# The scheduler is responsible for calling its performer efficiently across threads managed by an instance of +Concurrent::ThreadPoolExecutor+.
|
13
|
+
# If a performer does not have work, the thread will go to sleep.
|
14
|
+
# The scheduler maintains an instance of +Concurrent::TimerTask+, which wakes sleeping threads and causes them to check whether the performer has new work.
|
13
15
|
#
|
14
16
|
class Scheduler
|
15
|
-
# Defaults for instance of Concurrent::TimerTask
|
17
|
+
# Defaults for instance of Concurrent::TimerTask.
|
18
|
+
# The timer controls how and when sleeping threads check for new work.
|
16
19
|
DEFAULT_TIMER_OPTIONS = {
|
17
20
|
execution_interval: 1,
|
18
21
|
timeout_interval: 1,
|
@@ -20,6 +23,7 @@ module GoodJob # :nodoc:
|
|
20
23
|
}.freeze
|
21
24
|
|
22
25
|
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
26
|
+
# The thread pool is where work is performed.
|
23
27
|
DEFAULT_POOL_OPTIONS = {
|
24
28
|
name: name,
|
25
29
|
min_threads: 0,
|
@@ -32,11 +36,12 @@ module GoodJob # :nodoc:
|
|
32
36
|
|
33
37
|
# @!attribute [r] instances
|
34
38
|
# @!scope class
|
35
|
-
#
|
39
|
+
# List of all instantiated Schedulers in the current process.
|
36
40
|
# @return [array<GoodJob:Scheduler>]
|
37
41
|
cattr_reader :instances, default: [], instance_reader: false
|
38
42
|
|
39
43
|
# Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
|
44
|
+
# TODO: move this to GoodJob::Configuration
|
40
45
|
# @param configuration [GoodJob::Configuration]
|
41
46
|
# @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
|
42
47
|
def self.from_configuration(configuration)
|
@@ -78,6 +83,7 @@ module GoodJob # :nodoc:
|
|
78
83
|
# @param timer_options [Hash] Options to instantiate a Concurrent::TimerTask
|
79
84
|
# @param pool_options [Hash] Options to instantiate a Concurrent::ThreadPoolExecutor
|
80
85
|
def initialize(performer, timer_options: {}, pool_options: {})
|
86
|
+
# TODO: Replace `timer_options` and `pool_options` with only `poll_interval` and `max_threads`
|
81
87
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
82
88
|
|
83
89
|
self.class.instances << self
|
@@ -91,7 +97,11 @@ module GoodJob # :nodoc:
|
|
91
97
|
create_pools
|
92
98
|
end
|
93
99
|
|
94
|
-
# Shut down the
|
100
|
+
# Shut down the scheduler.
|
101
|
+
# This stops all threads in the pool.
|
102
|
+
# If +wait+ is +true+, the scheduler will wait for any active tasks to finish.
|
103
|
+
# If +wait+ is +false+, this method will return immediately even though threads may still be running.
|
104
|
+
# Use {#shutdown?} to determine whether threads have stopped.
|
95
105
|
# @param wait [Boolean] Wait for actively executing jobs to finish
|
96
106
|
# @return [void]
|
97
107
|
def shutdown(wait: true)
|
@@ -102,22 +112,25 @@ module GoodJob # :nodoc:
|
|
102
112
|
if @timer&.running?
|
103
113
|
@timer.shutdown
|
104
114
|
@timer.wait_for_termination if wait
|
115
|
+
# TODO: Should be killed if wait is not true
|
105
116
|
end
|
106
117
|
|
107
118
|
if @pool&.running?
|
108
119
|
@pool.shutdown
|
109
120
|
@pool.wait_for_termination if wait
|
121
|
+
# TODO: Should be killed if wait is not true
|
110
122
|
end
|
111
123
|
end
|
112
124
|
end
|
113
125
|
|
114
|
-
#
|
126
|
+
# Tests whether the scheduler is shutdown.
|
115
127
|
# @return [true, false, nil]
|
116
128
|
def shutdown?
|
117
129
|
@_shutdown
|
118
130
|
end
|
119
131
|
|
120
|
-
# Restart the Scheduler.
|
132
|
+
# Restart the Scheduler.
|
133
|
+
# When shutdown, start; or shutdown and start.
|
121
134
|
# @param wait [Boolean] Wait for actively executing jobs to finish
|
122
135
|
# @return [void]
|
123
136
|
def restart(wait: true)
|
@@ -128,15 +141,15 @@ module GoodJob # :nodoc:
|
|
128
141
|
end
|
129
142
|
end
|
130
143
|
|
131
|
-
#
|
132
|
-
# @param state [nil, Object]
|
133
|
-
# @return [nil, Boolean]
|
144
|
+
# Wakes a thread to allow the performer to execute a task.
|
145
|
+
# @param state [nil, Object] Contextual information for the performer. See {Performer#next?}.
|
146
|
+
# @return [nil, Boolean] Whether work was started.
|
147
|
+
# Returns +nil+ if the scheduler is unable to take new work, for example if the thread pool is shut down or at capacity.
|
148
|
+
# Returns +true+ if the performer started executing work.
|
149
|
+
# Returns +false+ if the performer decides not to attempt to execute a task based on the +state+ that is passed to it.
|
134
150
|
def create_thread(state = nil)
|
135
151
|
return nil unless @pool.running? && @pool.ready_worker_count.positive?
|
136
|
-
|
137
|
-
if state
|
138
|
-
return false unless @performer.next?(state)
|
139
|
-
end
|
152
|
+
return false if state && !@performer.next?(state)
|
140
153
|
|
141
154
|
future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
|
142
155
|
output = nil
|
@@ -168,7 +181,6 @@ module GoodJob # :nodoc:
|
|
168
181
|
|
169
182
|
private
|
170
183
|
|
171
|
-
# @return [void]
|
172
184
|
def create_pools
|
173
185
|
instrument("scheduler_create_pools", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval] }) do
|
174
186
|
@pool = ThreadPoolExecutor.new(@pool_options)
|
@@ -191,9 +203,10 @@ module GoodJob # :nodoc:
|
|
191
203
|
end
|
192
204
|
end
|
193
205
|
|
194
|
-
#
|
206
|
+
# Custom sub-class of +Concurrent::ThreadPoolExecutor+ to add additional worker status.
|
207
|
+
# @private
|
195
208
|
class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
|
196
|
-
# Number of
|
209
|
+
# Number of inactive threads available to execute tasks.
|
197
210
|
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
198
211
|
# @return [Integer]
|
199
212
|
def ready_worker_count
|
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.2.
|
4
|
+
version: 1.2.5
|
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-09-
|
11
|
+
date: 2020-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.
|
19
|
+
version: 5.2.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.
|
26
|
+
version: 5.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 5.
|
33
|
+
version: 5.2.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 5.
|
40
|
+
version: 5.2.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: concurrent-ruby
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 5.
|
75
|
+
version: 5.2.0
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 5.
|
82
|
+
version: 5.2.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: thor
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 0.14.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: zeitwerk
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: appraisal
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,20 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: capybara
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: database_cleaner
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +164,20 @@ dependencies:
|
|
136
164
|
- - ">="
|
137
165
|
- !ruby/object:Gem::Version
|
138
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: erb_lint
|
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'
|
139
181
|
- !ruby/object:Gem::Dependency
|
140
182
|
name: foreman
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -332,6 +374,20 @@ dependencies:
|
|
332
374
|
- - ">="
|
333
375
|
- !ruby/object:Gem::Version
|
334
376
|
version: '0'
|
377
|
+
- !ruby/object:Gem::Dependency
|
378
|
+
name: selenium-webdriver
|
379
|
+
requirement: !ruby/object:Gem::Requirement
|
380
|
+
requirements:
|
381
|
+
- - ">="
|
382
|
+
- !ruby/object:Gem::Version
|
383
|
+
version: '0'
|
384
|
+
type: :development
|
385
|
+
prerelease: false
|
386
|
+
version_requirements: !ruby/object:Gem::Requirement
|
387
|
+
requirements:
|
388
|
+
- - ">="
|
389
|
+
- !ruby/object:Gem::Version
|
390
|
+
version: '0'
|
335
391
|
- !ruby/object:Gem::Dependency
|
336
392
|
name: sigdump
|
337
393
|
requirement: !ruby/object:Gem::Requirement
|
@@ -360,6 +416,20 @@ dependencies:
|
|
360
416
|
- - ">="
|
361
417
|
- !ruby/object:Gem::Version
|
362
418
|
version: '0'
|
419
|
+
- !ruby/object:Gem::Dependency
|
420
|
+
name: yard-activesupport-concern
|
421
|
+
requirement: !ruby/object:Gem::Requirement
|
422
|
+
requirements:
|
423
|
+
- - ">="
|
424
|
+
- !ruby/object:Gem::Version
|
425
|
+
version: '0'
|
426
|
+
type: :development
|
427
|
+
prerelease: false
|
428
|
+
version_requirements: !ruby/object:Gem::Requirement
|
429
|
+
requirements:
|
430
|
+
- - ">="
|
431
|
+
- !ruby/object:Gem::Version
|
432
|
+
version: '0'
|
363
433
|
description: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
364
434
|
email:
|
365
435
|
- bensheldon@gmail.com
|
@@ -374,6 +444,22 @@ files:
|
|
374
444
|
- CHANGELOG.md
|
375
445
|
- LICENSE.txt
|
376
446
|
- README.md
|
447
|
+
- engine/app/controllers/good_job/active_jobs_controller.rb
|
448
|
+
- engine/app/controllers/good_job/base_controller.rb
|
449
|
+
- engine/app/controllers/good_job/dashboards_controller.rb
|
450
|
+
- engine/app/helpers/good_job/application_helper.rb
|
451
|
+
- engine/app/views/assets/_style.css.erb
|
452
|
+
- engine/app/views/good_job/active_jobs/show.html.erb
|
453
|
+
- engine/app/views/good_job/dashboards/index.html.erb
|
454
|
+
- engine/app/views/layouts/good_job/base.html.erb
|
455
|
+
- engine/app/views/shared/_chart.erb
|
456
|
+
- engine/app/views/shared/_jobs_table.erb
|
457
|
+
- engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb
|
458
|
+
- engine/app/views/vendor/bootstrap/_bootstrap.css.erb
|
459
|
+
- engine/app/views/vendor/chartist/_chartist.css.erb
|
460
|
+
- engine/app/views/vendor/chartist/_chartist.js.erb
|
461
|
+
- engine/config/routes.rb
|
462
|
+
- engine/lib/good_job/engine.rb
|
377
463
|
- exe/good_job
|
378
464
|
- lib/active_job/queue_adapters/good_job_adapter.rb
|
379
465
|
- lib/generators/good_job/install_generator.rb
|
@@ -412,11 +498,12 @@ rdoc_options:
|
|
412
498
|
- "--quiet"
|
413
499
|
require_paths:
|
414
500
|
- lib
|
501
|
+
- engine/lib
|
415
502
|
required_ruby_version: !ruby/object:Gem::Requirement
|
416
503
|
requirements:
|
417
504
|
- - ">="
|
418
505
|
- !ruby/object:Gem::Version
|
419
|
-
version: 2.
|
506
|
+
version: 2.5.0
|
420
507
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
421
508
|
requirements:
|
422
509
|
- - ">="
|