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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -7
  3. data/README.md +13 -10
  4. data/engine/app/controllers/good_job/active_jobs_controller.rb +8 -0
  5. data/engine/app/controllers/good_job/base_controller.rb +5 -0
  6. data/engine/app/controllers/good_job/dashboards_controller.rb +50 -0
  7. data/engine/app/helpers/good_job/application_helper.rb +4 -0
  8. data/engine/app/views/assets/_style.css.erb +16 -0
  9. data/engine/app/views/good_job/active_jobs/show.html.erb +1 -0
  10. data/engine/app/views/good_job/dashboards/index.html.erb +19 -0
  11. data/engine/app/views/layouts/good_job/base.html.erb +50 -0
  12. data/engine/app/views/shared/_chart.erb +51 -0
  13. data/engine/app/views/shared/_jobs_table.erb +26 -0
  14. data/engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb +1662 -0
  15. data/engine/app/views/vendor/bootstrap/_bootstrap.css.erb +10258 -0
  16. data/engine/app/views/vendor/chartist/_chartist.css.erb +613 -0
  17. data/engine/app/views/vendor/chartist/_chartist.js.erb +4516 -0
  18. data/engine/config/routes.rb +4 -0
  19. data/engine/lib/good_job/engine.rb +5 -0
  20. data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -2
  21. data/lib/generators/good_job/install_generator.rb +8 -0
  22. data/lib/good_job.rb +40 -24
  23. data/lib/good_job/adapter.rb +38 -0
  24. data/lib/good_job/cli.rb +30 -7
  25. data/lib/good_job/configuration.rb +44 -0
  26. data/lib/good_job/job.rb +116 -20
  27. data/lib/good_job/lockable.rb +119 -6
  28. data/lib/good_job/log_subscriber.rb +70 -4
  29. data/lib/good_job/multi_scheduler.rb +6 -0
  30. data/lib/good_job/notifier.rb +55 -29
  31. data/lib/good_job/performer.rb +38 -0
  32. data/lib/good_job/railtie.rb +1 -0
  33. data/lib/good_job/scheduler.rb +33 -20
  34. data/lib/good_job/version.rb +2 -1
  35. metadata +96 -9
@@ -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
 
@@ -1,4 +1,5 @@
1
1
  module GoodJob
2
+ # Ruby on Rails integration.
2
3
  class Railtie < ::Rails::Railtie
3
4
  initializer "good_job.logger" do
4
5
  ActiveSupport.on_load(:good_job) { self.logger = ::Rails.logger }
@@ -4,15 +4,18 @@ require "concurrent/utility/processor_counter"
4
4
 
5
5
  module GoodJob # :nodoc:
6
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.
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
- # Schedulers are "generic" in the sense that they delegate task execution
12
- # details to a "Performer" object that responds to #next.
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
- # All instantiated Schedulers in the current process.
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 Scheduler.
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
- # True when the Scheduler is shutdown.
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. When shutdown, start; or shutdown and start.
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
- # Triggers a Performer execution, if an execution thread is available.
132
- # @param state [nil, Object] Allows Performer#next? to accept or reject the execution
133
- # @return [nil, Boolean] if the thread was created
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
- # Slightly customized sub-class of Concurrent::ThreadPoolExecutor
206
+ # Custom sub-class of +Concurrent::ThreadPoolExecutor+ to add additional worker status.
207
+ # @private
195
208
  class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
196
- # Number of idle or potential threads available to execute tasks
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
@@ -1,3 +1,4 @@
1
1
  module GoodJob
2
- VERSION = '1.2.4'.freeze
2
+ # GoodJob gem version.
3
+ VERSION = '1.2.5'.freeze
3
4
  end
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
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-01 00:00:00.000000000 Z
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.1.0
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.1.0
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.1.0
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.1.0
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.1.0
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.1.0
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.4.0
506
+ version: 2.5.0
420
507
  required_rubygems_version: !ruby/object:Gem::Requirement
421
508
  requirements:
422
509
  - - ">="