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
@@ -1,5 +1,6 @@
|
|
1
|
-
module ActiveJob
|
2
|
-
module QueueAdapters
|
1
|
+
module ActiveJob # :nodoc:
|
2
|
+
module QueueAdapters # :nodoc:
|
3
|
+
# See {GoodJob::Adapter} for details.
|
3
4
|
class GoodJobAdapter < GoodJob::Adapter
|
4
5
|
def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil, inline: false)
|
5
6
|
configuration = GoodJob::Configuration.new({ execution_mode: execution_mode }, env: ENV)
|
@@ -2,6 +2,13 @@ require 'rails/generators'
|
|
2
2
|
require 'rails/generators/active_record'
|
3
3
|
|
4
4
|
module GoodJob
|
5
|
+
#
|
6
|
+
# Implements the Rails generator used for setting up GoodJob in a Rails
|
7
|
+
# application. Run it with +bin/rails g good_job:install+ in your console.
|
8
|
+
#
|
9
|
+
# This generator is primarily dedicated to stubbing out a migration that adds
|
10
|
+
# a table to hold GoodJob's queued jobs in your database.
|
11
|
+
#
|
5
12
|
class InstallGenerator < Rails::Generators::Base
|
6
13
|
include Rails::Generators::Migration
|
7
14
|
|
@@ -11,6 +18,7 @@ module GoodJob
|
|
11
18
|
|
12
19
|
source_paths << File.join(File.dirname(__FILE__), "templates")
|
13
20
|
|
21
|
+
# Generates the actual migration file and places it on disk.
|
14
22
|
def create_migration_file
|
15
23
|
migration_template 'migration.rb.erb', 'db/migrate/create_good_jobs.rb', migration_version: migration_version
|
16
24
|
end
|
data/lib/good_job.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
require "rails"
|
2
|
-
require 'good_job/railtie'
|
3
2
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require 'good_job/lockable'
|
7
|
-
require 'good_job/job'
|
8
|
-
require 'good_job/scheduler'
|
9
|
-
require 'good_job/multi_scheduler'
|
10
|
-
require 'good_job/adapter'
|
11
|
-
require 'good_job/performer'
|
12
|
-
require 'good_job/current_execution'
|
13
|
-
require 'good_job/notifier'
|
3
|
+
require "active_job"
|
4
|
+
require "active_job/queue_adapters"
|
14
5
|
|
15
|
-
require
|
6
|
+
require "zeitwerk"
|
7
|
+
|
8
|
+
loader = Zeitwerk::Loader.for_gem
|
9
|
+
loader.inflector.inflect(
|
10
|
+
'cli' => "CLI"
|
11
|
+
)
|
12
|
+
loader.push_dir(File.join(__dir__, ["generators"]))
|
13
|
+
loader.setup
|
14
|
+
|
15
|
+
require "good_job/railtie"
|
16
16
|
|
17
17
|
# GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
|
18
18
|
#
|
@@ -20,48 +20,64 @@ require 'active_job/queue_adapters/good_job_adapter'
|
|
20
20
|
module GoodJob
|
21
21
|
# @!attribute [rw] logger
|
22
22
|
# @!scope class
|
23
|
-
# The logger used by GoodJob
|
23
|
+
# The logger used by GoodJob (default: +Rails.logger+).
|
24
|
+
# Use this to redirect logs to a special location or file.
|
24
25
|
# @return [Logger]
|
25
|
-
|
26
|
+
# @example Output GoodJob logs to a file:
|
27
|
+
# GoodJob.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new("log/my_logs.log"))
|
28
|
+
mattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
|
26
29
|
|
27
30
|
# @!attribute [rw] preserve_job_records
|
28
31
|
# @!scope class
|
29
|
-
# Whether to preserve job records in the database after they have finished
|
32
|
+
# Whether to preserve job records in the database after they have finished (default: +false+).
|
33
|
+
# By default, GoodJob deletes job records after the job is completed successfully.
|
34
|
+
# If you want to preserve jobs for latter inspection, set this to +true+.
|
35
|
+
# If +true+, you will need to clean out jobs using the +good_job cleanup_preserved_jobs+ CLI command.
|
30
36
|
# @return [Boolean]
|
31
37
|
mattr_accessor :preserve_job_records, default: false
|
32
38
|
|
33
39
|
# @!attribute [rw] reperform_jobs_on_standard_error
|
34
40
|
# @!scope class
|
35
|
-
# Whether to re-perform a job when a type of +StandardError+ is raised
|
41
|
+
# Whether to re-perform a job when a type of +StandardError+ is raised to GoodJob (default: +true+).
|
42
|
+
# If +true+, causes jobs to be re-queued and retried if they raise an instance of +StandardError+.
|
43
|
+
# If +false+, jobs will be discarded or marked as finished if they raise an instance of +StandardError+.
|
44
|
+
# Instances of +Exception+, like +SIGINT+, will *always* be retried, regardless of this attribute's value.
|
36
45
|
# @return [Boolean]
|
37
46
|
mattr_accessor :reperform_jobs_on_standard_error, default: true
|
38
47
|
|
39
48
|
# @!attribute [rw] on_thread_error
|
40
49
|
# @!scope class
|
41
|
-
#
|
50
|
+
# This callable will be called when an exception reaches GoodJob (default: +nil+).
|
51
|
+
# It can be useful for logging errors to bug tracking services, like Sentry or Airbrake.
|
42
52
|
# @example Send errors to Sentry
|
43
53
|
# # config/initializers/good_job.rb
|
44
|
-
#
|
45
|
-
# # With Sentry (or Bugsnag, Airbrake, Honeybadger, etc.)
|
46
54
|
# GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
|
47
55
|
# @return [#call, nil]
|
48
56
|
mattr_accessor :on_thread_error, default: nil
|
49
57
|
|
50
|
-
#
|
58
|
+
# Stop executing jobs.
|
59
|
+
# GoodJob does its work in pools of background threads.
|
60
|
+
# When forking processes you should shut down these background threads before forking, and restart them after forking.
|
61
|
+
# For example, you should use +shutdown+ and +restart+ when using async execution mode with Puma.
|
62
|
+
# See the {file:README.md#executing-jobs-async--in-process} for more explanation and examples.
|
51
63
|
# @param wait [Boolean] whether to wait for shutdown
|
52
64
|
# @return [void]
|
53
65
|
def self.shutdown(wait: true)
|
54
|
-
Notifier.instances.each { |
|
66
|
+
Notifier.instances.each { |notifier| notifier.shutdown(wait: wait) }
|
55
67
|
Scheduler.instances.each { |scheduler| scheduler.shutdown(wait: wait) }
|
56
68
|
end
|
57
69
|
|
58
|
-
# Tests
|
59
|
-
# @return [Boolean] whether
|
70
|
+
# Tests whether jobs have stopped executing.
|
71
|
+
# @return [Boolean] whether background threads are shut down
|
60
72
|
def self.shutdown?
|
61
73
|
Notifier.instances.all?(&:shutdown?) && Scheduler.instances.all?(&:shutdown?)
|
62
74
|
end
|
63
75
|
|
64
|
-
#
|
76
|
+
# Stops and restarts executing jobs.
|
77
|
+
# GoodJob does its work in pools of background threads.
|
78
|
+
# When forking processes you should shut down these background threads before forking, and restart them after forking.
|
79
|
+
# For example, you should use +shutdown+ and +restart+ when using async execution mode with Puma.
|
80
|
+
# See the {file:README.md#executing-jobs-async--in-process} for more explanation and examples.
|
65
81
|
# @return [void]
|
66
82
|
def self.restart
|
67
83
|
Notifier.instances.each(&:restart)
|
data/lib/good_job/adapter.rb
CHANGED
@@ -1,7 +1,28 @@
|
|
1
1
|
module GoodJob
|
2
|
+
#
|
3
|
+
# ActiveJob Adapter.
|
4
|
+
#
|
2
5
|
class Adapter
|
6
|
+
# Valid execution modes.
|
3
7
|
EXECUTION_MODES = [:async, :external, :inline].freeze
|
4
8
|
|
9
|
+
# @param execution_mode [nil, Symbol] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
|
10
|
+
#
|
11
|
+
# - +:inline+ executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
|
12
|
+
# - +:external+ causes the adapter to enqueue jobs, but not execute them. When using this option (the default for production environments), you'll need to use the command-line tool to actually execute your jobs.
|
13
|
+
# - +:async+ causes the adapter to execute you jobs in separate threads in whatever process queued them (usually the web process). This is akin to running the command-line tool's code inside your web server. It can be more economical for small workloads (you don't need a separate machine or environment for running your jobs), but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead.
|
14
|
+
#
|
15
|
+
# The default value depends on the Rails environment:
|
16
|
+
#
|
17
|
+
# - +development+ and +test+: +:inline+
|
18
|
+
# - +production+ and all other environments: +:external+
|
19
|
+
#
|
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
|
+
# @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
|
+
# @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
|
+
# @param scheduler [nil, Scheduler] (deprecated) a scheduler to be managed by the adapter
|
24
|
+
# @param notifier [nil, Notifier] (deprecated) a notifier to be managed by the adapter
|
25
|
+
# @param inline [nil, Boolean] (deprecated) whether to run in inline execution mode
|
5
26
|
def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil, scheduler: nil, notifier: nil, inline: false)
|
6
27
|
if inline && execution_mode.nil?
|
7
28
|
ActiveSupport::Deprecation.warn('GoodJob::Adapter#new(inline: true) is deprecated; use GoodJob::Adapter.new(execution_mode: :inline) instead')
|
@@ -27,10 +48,19 @@ module GoodJob
|
|
27
48
|
end
|
28
49
|
end
|
29
50
|
|
51
|
+
# Enqueues the ActiveJob job to be performed.
|
52
|
+
# For use by Rails; you should generally not call this directly.
|
53
|
+
# @param active_job [ActiveJob::Base] the job to be enqueued from +#perform_later+
|
54
|
+
# @return [GoodJob::Job]
|
30
55
|
def enqueue(active_job)
|
31
56
|
enqueue_at(active_job, nil)
|
32
57
|
end
|
33
58
|
|
59
|
+
# Enqueues an ActiveJob job to be run at a specific time.
|
60
|
+
# For use by Rails; you should generally not call this directly.
|
61
|
+
# @param active_job [ActiveJob::Base] the job to be enqueued from +#perform_later+
|
62
|
+
# @param timestamp [Integer] the epoch time to perform the job
|
63
|
+
# @return [GoodJob::Job]
|
34
64
|
def enqueue_at(active_job, timestamp)
|
35
65
|
good_job = GoodJob::Job.enqueue(
|
36
66
|
active_job,
|
@@ -52,23 +82,31 @@ module GoodJob
|
|
52
82
|
good_job
|
53
83
|
end
|
54
84
|
|
85
|
+
# Gracefully stop processing jobs.
|
86
|
+
# Waits for termination by default.
|
87
|
+
# @param wait [Boolean] Whether to wait for shut down.
|
88
|
+
# @return [void]
|
55
89
|
def shutdown(wait: true)
|
56
90
|
@notifier&.shutdown(wait: wait)
|
57
91
|
@scheduler&.shutdown(wait: wait)
|
58
92
|
end
|
59
93
|
|
94
|
+
# Whether in +:async+ execution mode.
|
60
95
|
def execute_async?
|
61
96
|
@execution_mode == :async
|
62
97
|
end
|
63
98
|
|
99
|
+
# Whether in +:external+ execution mode.
|
64
100
|
def execute_externally?
|
65
101
|
@execution_mode == :external
|
66
102
|
end
|
67
103
|
|
104
|
+
# Whether in +:inline+ execution mode.
|
68
105
|
def execute_inline?
|
69
106
|
@execution_mode == :inline
|
70
107
|
end
|
71
108
|
|
109
|
+
# (deprecated) Whether in +:inline+ execution mode.
|
72
110
|
def inline?
|
73
111
|
ActiveSupport::Deprecation.warn('GoodJob::Adapter::inline? is deprecated; use GoodJob::Adapter::execute_inline? instead')
|
74
112
|
execute_inline?
|
data/lib/good_job/cli.rb
CHANGED
@@ -1,17 +1,33 @@
|
|
1
1
|
require 'thor'
|
2
2
|
|
3
3
|
module GoodJob
|
4
|
+
#
|
5
|
+
# Implements the +good_job+ command-line tool, which executes jobs and
|
6
|
+
# provides other utilities. The actual entry point is in +exe/good_job+, but
|
7
|
+
# it just sets up and calls this class.
|
8
|
+
#
|
9
|
+
# The +good_job+ command-line tool is based on Thor, a CLI framework for
|
10
|
+
# Ruby. For more on general usage, see http://whatisthor.com/ and
|
11
|
+
# https://github.com/erikhuda/thor/wiki.
|
12
|
+
#
|
4
13
|
class CLI < Thor
|
14
|
+
# Path to the local Rails application's environment configuration.
|
15
|
+
# Requiring this loads the application's configuration and classes.
|
5
16
|
RAILS_ENVIRONMENT_RB = File.expand_path("config/environment.rb")
|
6
17
|
|
7
|
-
|
18
|
+
# @!macro thor.desc
|
19
|
+
# @!method $1
|
20
|
+
# @return [void]
|
21
|
+
# The +good_job $1+ command. $2
|
22
|
+
desc :start, "Executes queued jobs."
|
23
|
+
long_desc <<~DESCRIPTION
|
8
24
|
Executes queued jobs.
|
9
25
|
|
10
|
-
All options can be configured with environment variables.
|
26
|
+
All options can be configured with environment variables.
|
11
27
|
See option descriptions for the matching environment variable name.
|
12
28
|
|
13
29
|
== Configuring queues
|
14
|
-
|
30
|
+
\x5Separate multiple queues with commas; exclude queues with a leading minus;
|
15
31
|
separate isolated execution pools with semicolons and threads with colons.
|
16
32
|
|
17
33
|
DESCRIPTION
|
@@ -51,8 +67,10 @@ module GoodJob
|
|
51
67
|
|
52
68
|
default_task :start
|
53
69
|
|
54
|
-
|
55
|
-
|
70
|
+
# @!macro thor.desc
|
71
|
+
desc :cleanup_preserved_jobs, "Deletes preserved job records."
|
72
|
+
long_desc <<~DESCRIPTION
|
73
|
+
Deletes preserved job records.
|
56
74
|
|
57
75
|
By default, GoodJob deletes job records when the job is performed and this
|
58
76
|
command is not necessary.
|
@@ -87,11 +105,16 @@ module GoodJob
|
|
87
105
|
end
|
88
106
|
|
89
107
|
no_commands do
|
108
|
+
# Load the current Rails application and configuration that the good_job
|
109
|
+
# command-line tool should be working within.
|
110
|
+
#
|
111
|
+
# GoodJob components that need access to constants, classes, etc. from
|
112
|
+
# Rails or from the application can be set up here.
|
90
113
|
def set_up_application!
|
91
114
|
require RAILS_ENVIRONMENT_RB
|
92
|
-
return unless defined?(GOOD_JOB_LOG_TO_STDOUT) && GOOD_JOB_LOG_TO_STDOUT && !ActiveSupport::Logger.logger_outputs_to?(GoodJob.logger,
|
115
|
+
return unless defined?(GOOD_JOB_LOG_TO_STDOUT) && GOOD_JOB_LOG_TO_STDOUT && !ActiveSupport::Logger.logger_outputs_to?(GoodJob.logger, $stdout)
|
93
116
|
|
94
|
-
GoodJob::LogSubscriber.loggers << ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(
|
117
|
+
GoodJob::LogSubscriber.loggers << ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
|
95
118
|
GoodJob::LogSubscriber.reset_logger
|
96
119
|
end
|
97
120
|
end
|
@@ -1,12 +1,40 @@
|
|
1
1
|
module GoodJob
|
2
|
+
#
|
3
|
+
# +GoodJob::Configuration+ provides normalized configuration information to
|
4
|
+
# the rest of GoodJob. It combines environment information with explicitly
|
5
|
+
# set options to get the final values for each option.
|
6
|
+
#
|
2
7
|
class Configuration
|
8
|
+
# @!attribute [r] options
|
9
|
+
# The options that were explicitly set when initializing +Configuration+.
|
10
|
+
# @return [Hash]
|
11
|
+
#
|
12
|
+
# @!attribute [r] env
|
13
|
+
# The environment from which to read GoodJob's environment variables. By
|
14
|
+
# default, this is the current process's environment, but it can be set
|
15
|
+
# to something else in {#initialize}.
|
16
|
+
# @return [Hash]
|
3
17
|
attr_reader :options, :env
|
4
18
|
|
19
|
+
# @param options [Hash] Any explicitly specified configuration options to
|
20
|
+
# use. Keys are symbols that match the various methods on this class.
|
21
|
+
# @param env [Hash] A +Hash+ from which to read environment variables that
|
22
|
+
# might specify additional configuration values.
|
5
23
|
def initialize(options, env: ENV)
|
6
24
|
@options = options
|
7
25
|
@env = env
|
8
26
|
end
|
9
27
|
|
28
|
+
# Specifies how and where jobs should be executed. See {Adapter#initialize}
|
29
|
+
# for more details on possible values.
|
30
|
+
#
|
31
|
+
# When running inside a Rails app, you may want to use
|
32
|
+
# {#rails_execution_mode}, which takes the current Rails environment into
|
33
|
+
# account when determining the final value.
|
34
|
+
#
|
35
|
+
# @param default [Symbol]
|
36
|
+
# Value to use if none was specified in the configuration.
|
37
|
+
# @return [Symbol]
|
10
38
|
def execution_mode(default: :external)
|
11
39
|
if options[:execution_mode]
|
12
40
|
options[:execution_mode]
|
@@ -17,6 +45,9 @@ module GoodJob
|
|
17
45
|
end
|
18
46
|
end
|
19
47
|
|
48
|
+
# Like {#execution_mode}, but takes the current Rails environment into
|
49
|
+
# account (e.g. in the +test+ environment, it falls back to +:inline+).
|
50
|
+
# @return [Symbol]
|
20
51
|
def rails_execution_mode
|
21
52
|
if execution_mode(default: nil)
|
22
53
|
execution_mode
|
@@ -29,6 +60,10 @@ module GoodJob
|
|
29
60
|
end
|
30
61
|
end
|
31
62
|
|
63
|
+
# Indicates the number of threads to use per {Scheduler}. Note that
|
64
|
+
# {#queue_string} may provide more specific thread counts to use with
|
65
|
+
# individual schedulers.
|
66
|
+
# @return [Integer]
|
32
67
|
def max_threads
|
33
68
|
(
|
34
69
|
options[:max_threads] ||
|
@@ -38,12 +73,21 @@ module GoodJob
|
|
38
73
|
).to_i
|
39
74
|
end
|
40
75
|
|
76
|
+
# Describes which queues to execute jobs from and how those queues should
|
77
|
+
# be grouped into {Scheduler} instances. See
|
78
|
+
# {file:README.md#optimize-queues-threads-and-processes} for more details
|
79
|
+
# on the format of this string.
|
80
|
+
# @return [String]
|
41
81
|
def queue_string
|
42
82
|
options[:queues] ||
|
43
83
|
env['GOOD_JOB_QUEUES'] ||
|
44
84
|
'*'
|
45
85
|
end
|
46
86
|
|
87
|
+
# The number of seconds between polls for jobs. GoodJob will execute jobs
|
88
|
+
# on queues continuously until a queue is empty, at which point it will
|
89
|
+
# poll (using this interval) for new queued jobs to execute.
|
90
|
+
# @return [Integer]
|
47
91
|
def poll_interval
|
48
92
|
(
|
49
93
|
options[:poll_interval] ||
|
data/lib/good_job/job.rb
CHANGED
@@ -1,14 +1,32 @@
|
|
1
1
|
module GoodJob
|
2
|
+
#
|
3
|
+
# Represents a request to perform an +ActiveJob+ job.
|
4
|
+
#
|
2
5
|
class Job < ActiveRecord::Base
|
3
6
|
include Lockable
|
4
7
|
|
8
|
+
# Raised if something attempts to execute a previously completed Job again.
|
5
9
|
PreviouslyPerformedError = Class.new(StandardError)
|
6
10
|
|
11
|
+
# ActiveJob jobs without a +queue_name+ attribute are placed on this queue.
|
7
12
|
DEFAULT_QUEUE_NAME = 'default'.freeze
|
13
|
+
# ActiveJob jobs without a +priority+ attribute are given this priority.
|
8
14
|
DEFAULT_PRIORITY = 0
|
9
15
|
|
10
16
|
self.table_name = 'good_jobs'.freeze
|
11
17
|
|
18
|
+
# Parse a string representing a group of queues into a more readable data
|
19
|
+
# structure.
|
20
|
+
# @return [Hash]
|
21
|
+
# How to match a given queue. It can have the following keys and values:
|
22
|
+
# - +{ all: true }+ indicates that all queues match.
|
23
|
+
# - +{ exclude: Array<String> }+ indicates the listed queue names should
|
24
|
+
# not match.
|
25
|
+
# - +{ include: Array<String> }+ indicates the listed queue names should
|
26
|
+
# match.
|
27
|
+
# @example
|
28
|
+
# GoodJob::Job.queue_parser('-queue1,queue2')
|
29
|
+
# => { exclude: [ 'queue1', 'queue2' ] }
|
12
30
|
def self.queue_parser(string)
|
13
31
|
string = string.presence || '*'
|
14
32
|
|
@@ -28,6 +46,10 @@ module GoodJob
|
|
28
46
|
end
|
29
47
|
end
|
30
48
|
|
49
|
+
# Get Jobs that have not yet been completed.
|
50
|
+
# @!method unfinished
|
51
|
+
# @!scope class
|
52
|
+
# @return [ActiveRecord::Relation]
|
31
53
|
scope :unfinished, (lambda do
|
32
54
|
if column_names.include?('finished_at')
|
33
55
|
where(finished_at: nil)
|
@@ -36,9 +58,42 @@ module GoodJob
|
|
36
58
|
nil
|
37
59
|
end
|
38
60
|
end)
|
61
|
+
|
62
|
+
# Get Jobs that are not scheduled for a later time than now (i.e. jobs that
|
63
|
+
# are not scheduled or scheduled for earlier than the current time).
|
64
|
+
# @!method only_scheduled
|
65
|
+
# @!scope class
|
66
|
+
# @return [ActiveRecord::Relation]
|
39
67
|
scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(Time.current)).or(where(scheduled_at: nil)) }
|
68
|
+
|
69
|
+
# Order jobs by priority (highest priority first).
|
70
|
+
# @!method priority_ordered
|
71
|
+
# @!scope class
|
72
|
+
# @return [ActiveRecord::Relation]
|
40
73
|
scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
|
74
|
+
|
75
|
+
# Get Jobs were completed before the given timestamp. If no timestamp is
|
76
|
+
# provided, get all jobs that have been completed. By default, GoodJob
|
77
|
+
# deletes jobs after they are completed and this will find no jobs.
|
78
|
+
# However, if you have changed {GoodJob.preserve_job_records}, this may
|
79
|
+
# find completed Jobs.
|
80
|
+
# @!method finished(timestamp = nil)
|
81
|
+
# @!scope class
|
82
|
+
# @param timestamp (Float)
|
83
|
+
# Get jobs that finished before this time (in epoch time).
|
84
|
+
# @return [ActiveRecord::Relation]
|
41
85
|
scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
|
86
|
+
|
87
|
+
# Get Jobs on queues that match the given queue string.
|
88
|
+
# @!method queue_string(string)
|
89
|
+
# @!scope class
|
90
|
+
# @param string [String]
|
91
|
+
# A string expression describing what queues to select. See
|
92
|
+
# {Job.queue_parser} or
|
93
|
+
# {file:README.md#optimize-queues-threads-and-processes} for more details
|
94
|
+
# on the format of the string. Note this only handles individual
|
95
|
+
# semicolon-separated segments of that string format.
|
96
|
+
# @return [ActiveRecord::Relation]
|
42
97
|
scope :queue_string, (lambda do |string|
|
43
98
|
parsed = queue_parser(string)
|
44
99
|
|
@@ -51,6 +106,31 @@ module GoodJob
|
|
51
106
|
end
|
52
107
|
end)
|
53
108
|
|
109
|
+
# Get Jobs in display order with optional keyset pagination.
|
110
|
+
# @!method display_all(after_scheduled_at: nil, after_id: nil)
|
111
|
+
# @!scope class
|
112
|
+
# @param after_scheduled_at [DateTime, String, nil]
|
113
|
+
# Display records scheduled after this time for keyset pagination
|
114
|
+
# @param after_id [Numeric, String, nil]
|
115
|
+
# Display records after this ID for keyset pagination
|
116
|
+
# @return [ActiveRecord::Relation]
|
117
|
+
scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
|
118
|
+
query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
|
119
|
+
if after_scheduled_at.present? && after_id.present?
|
120
|
+
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
|
121
|
+
elsif after_scheduled_at.present?
|
122
|
+
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
|
123
|
+
end
|
124
|
+
query
|
125
|
+
end)
|
126
|
+
|
127
|
+
# Finds the next eligible Job, acquire an advisory lock related to it, and
|
128
|
+
# executes the job.
|
129
|
+
# @return [Array<(GoodJob::Job, Object, Exception)>, nil]
|
130
|
+
# If a job was executed, returns an array with the {Job} record, the
|
131
|
+
# return value for the job's +#perform+ method, and the exception the job
|
132
|
+
# raised, if any (if the job raised, then the second array entry will be
|
133
|
+
# +nil+). If there were no jobs to execute, returns +nil+.
|
54
134
|
def self.perform_with_advisory_lock
|
55
135
|
good_job = nil
|
56
136
|
result = nil
|
@@ -67,6 +147,15 @@ module GoodJob
|
|
67
147
|
[good_job, result, error] if good_job
|
68
148
|
end
|
69
149
|
|
150
|
+
# Places an ActiveJob job on a queue by creating a new {Job} record.
|
151
|
+
# @param active_job [ActiveJob::Base]
|
152
|
+
# The job to enqueue.
|
153
|
+
# @param scheduled_at [Float]
|
154
|
+
# Epoch timestamp when the job should be executed.
|
155
|
+
# @param create_with_advisory_lock [Boolean]
|
156
|
+
# Whether to establish a lock on the {Job} record after it is created.
|
157
|
+
# @return [Job]
|
158
|
+
# The new {Job} instance representing the queued ActiveJob job.
|
70
159
|
def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
|
71
160
|
good_job = nil
|
72
161
|
ActiveSupport::Notifications.instrument("enqueue_job.good_job", { active_job: active_job, scheduled_at: scheduled_at, create_with_advisory_lock: create_with_advisory_lock }) do |instrument_payload|
|
@@ -87,32 +176,25 @@ module GoodJob
|
|
87
176
|
good_job
|
88
177
|
end
|
89
178
|
|
90
|
-
|
179
|
+
# Execute the ActiveJob job this {Job} represents.
|
180
|
+
# @return [Array<(Object, Exception)>]
|
181
|
+
# An array of the return value of the job's +#perform+ method and the
|
182
|
+
# exception raised by the job, if any. If the job completed successfully,
|
183
|
+
# the second array entry (the exception) will be +nil+ and vice versa.
|
184
|
+
def perform
|
91
185
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
92
186
|
|
93
187
|
GoodJob::CurrentExecution.reset
|
94
|
-
result = nil
|
95
|
-
rescued_error = nil
|
96
|
-
error = nil
|
97
188
|
|
98
189
|
self.performed_at = Time.current
|
99
|
-
save!
|
190
|
+
save! if GoodJob.preserve_job_records
|
100
191
|
|
101
|
-
|
102
|
-
"provider_job_id" => id
|
103
|
-
)
|
104
|
-
|
105
|
-
begin
|
106
|
-
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
107
|
-
result = ActiveJob::Base.execute(params)
|
108
|
-
end
|
109
|
-
rescue StandardError => e
|
110
|
-
rescued_error = e
|
111
|
-
end
|
192
|
+
result, rescued_error = execute
|
112
193
|
|
113
194
|
retry_or_discard_error = GoodJob::CurrentExecution.error_on_retry ||
|
114
195
|
GoodJob::CurrentExecution.error_on_discard
|
115
196
|
|
197
|
+
error = nil
|
116
198
|
if rescued_error
|
117
199
|
error = rescued_error
|
118
200
|
elsif result.is_a?(Exception)
|
@@ -125,19 +207,33 @@ module GoodJob
|
|
125
207
|
error_message = "#{error.class}: #{error.message}" if error
|
126
208
|
self.error = error_message
|
127
209
|
|
128
|
-
if rescued_error &&
|
210
|
+
if rescued_error && GoodJob.reperform_jobs_on_standard_error
|
129
211
|
save!
|
130
212
|
else
|
131
213
|
self.finished_at = Time.current
|
132
214
|
|
133
|
-
if
|
134
|
-
destroy!
|
135
|
-
else
|
215
|
+
if GoodJob.preserve_job_records
|
136
216
|
save!
|
217
|
+
else
|
218
|
+
destroy!
|
137
219
|
end
|
138
220
|
end
|
139
221
|
|
140
222
|
[result, error]
|
141
223
|
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def execute
|
228
|
+
params = serialized_params.merge(
|
229
|
+
"provider_job_id" => id
|
230
|
+
)
|
231
|
+
|
232
|
+
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
233
|
+
[ActiveJob::Base.execute(params), nil]
|
234
|
+
end
|
235
|
+
rescue StandardError => e
|
236
|
+
[nil, e]
|
237
|
+
end
|
142
238
|
end
|
143
239
|
end
|