good_job 1.8.0 → 1.9.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 +111 -11
- data/README.md +15 -12
- data/engine/app/controllers/good_job/dashboards_controller.rb +7 -5
- data/engine/app/views/shared/_chart.erb +3 -2
- data/exe/good_job +3 -2
- data/lib/active_job/queue_adapters/good_job_adapter.rb +0 -4
- data/lib/good_job.rb +26 -8
- data/lib/good_job/adapter.rb +35 -17
- data/lib/good_job/cli.rb +17 -5
- data/lib/good_job/configuration.rb +26 -34
- data/lib/good_job/current_execution.rb +0 -1
- data/lib/good_job/daemon.rb +6 -0
- data/lib/good_job/execution_result.rb +20 -0
- data/lib/good_job/job.rb +47 -37
- data/lib/good_job/job_performer.rb +2 -2
- data/lib/good_job/lockable.rb +4 -7
- data/lib/good_job/log_subscriber.rb +15 -14
- data/lib/good_job/multi_scheduler.rb +10 -1
- data/lib/good_job/notifier.rb +7 -6
- data/lib/good_job/poller.rb +10 -6
- data/lib/good_job/scheduler.rb +55 -20
- data/lib/good_job/version.rb +1 -1
- metadata +4 -17
data/lib/good_job/adapter.rb
CHANGED
@@ -4,22 +4,24 @@ module GoodJob
|
|
4
4
|
#
|
5
5
|
class Adapter
|
6
6
|
# Valid execution modes.
|
7
|
-
EXECUTION_MODES = [:async, :external, :inline].freeze
|
7
|
+
EXECUTION_MODES = [:async, :async_server, :external, :inline].freeze
|
8
8
|
|
9
|
-
# @param execution_mode [
|
9
|
+
# @param execution_mode [Symbol, nil] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
|
10
10
|
#
|
11
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
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
|
-
# - +:
|
13
|
+
# - +:async_server+ executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because 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
|
+
# When not in the Rails webserver, jobs will execute in +:external+ mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
|
15
|
+
# - +:async+ executes jobs in any Rails process.
|
14
16
|
#
|
15
17
|
# The default value depends on the Rails environment:
|
16
18
|
#
|
17
19
|
# - +development+ and +test+: +:inline+
|
18
20
|
# - +production+ and all other environments: +:external+
|
19
21
|
#
|
20
|
-
# @param max_threads [
|
21
|
-
# @param queues [
|
22
|
-
# @param poll_interval [
|
22
|
+
# @param max_threads [Integer, nil] 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+.
|
23
|
+
# @param queues [String, nil] 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 +"*"+.
|
24
|
+
# @param poll_interval [Integer, nil] 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
25
|
def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil)
|
24
26
|
if caller[0..4].find { |c| c.include?("/config/application.rb") || c.include?("/config/environments/") }
|
25
27
|
ActiveSupport::Deprecation.warn(<<~DEPRECATION)
|
@@ -46,8 +48,7 @@ module GoodJob
|
|
46
48
|
poll_interval: poll_interval,
|
47
49
|
}
|
48
50
|
)
|
49
|
-
|
50
|
-
raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(@configuration.execution_mode)
|
51
|
+
@configuration.validate!
|
51
52
|
|
52
53
|
if execute_async? # rubocop:disable Style/GuardClause
|
53
54
|
@notifier = GoodJob::Notifier.new
|
@@ -69,7 +70,7 @@ module GoodJob
|
|
69
70
|
# Enqueues an ActiveJob job to be run at a specific time.
|
70
71
|
# For use by Rails; you should generally not call this directly.
|
71
72
|
# @param active_job [ActiveJob::Base] the job to be enqueued from +#perform_later+
|
72
|
-
# @param timestamp [Integer] the epoch time to perform the job
|
73
|
+
# @param timestamp [Integer, nil] the epoch time to perform the job
|
73
74
|
# @return [GoodJob::Job]
|
74
75
|
def enqueue_at(active_job, timestamp)
|
75
76
|
good_job = GoodJob::Job.enqueue(
|
@@ -96,22 +97,21 @@ module GoodJob
|
|
96
97
|
end
|
97
98
|
|
98
99
|
# Shut down the thread pool executors.
|
99
|
-
# @param timeout [nil, Numeric] Seconds to wait for active threads.
|
100
|
-
#
|
100
|
+
# @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
|
101
101
|
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
102
102
|
# * +-1+, the scheduler will wait until the shutdown is complete.
|
103
103
|
# * +0+, the scheduler will immediately shutdown and stop any threads.
|
104
104
|
# * A positive number will wait that many seconds before stopping any remaining active threads.
|
105
|
-
# @param wait [Boolean] Deprecated. Use +timeout:+ instead.
|
105
|
+
# @param wait [Boolean, nil] Deprecated. Use +timeout:+ instead.
|
106
106
|
# @return [void]
|
107
107
|
def shutdown(timeout: :default, wait: nil)
|
108
|
-
timeout = if wait.
|
108
|
+
timeout = if wait.nil?
|
109
|
+
timeout
|
110
|
+
else
|
109
111
|
ActiveSupport::Deprecation.warn(
|
110
112
|
"Using `GoodJob::Adapter.shutdown` with `wait:` kwarg is deprecated; use `timeout:` kwarg instead e.g. GoodJob::Adapter.shutdown(timeout: #{wait ? '-1' : 'nil'})"
|
111
113
|
)
|
112
114
|
wait ? -1 : nil
|
113
|
-
else
|
114
|
-
timeout
|
115
115
|
end
|
116
116
|
|
117
117
|
timeout = if timeout == :default
|
@@ -125,18 +125,36 @@ module GoodJob
|
|
125
125
|
end
|
126
126
|
|
127
127
|
# Whether in +:async+ execution mode.
|
128
|
+
# @return [Boolean]
|
128
129
|
def execute_async?
|
129
|
-
@configuration.execution_mode == :async
|
130
|
+
@configuration.execution_mode == :async ||
|
131
|
+
@configuration.execution_mode == :async_server && in_server_process?
|
130
132
|
end
|
131
133
|
|
132
134
|
# Whether in +:external+ execution mode.
|
135
|
+
# @return [Boolean]
|
133
136
|
def execute_externally?
|
134
|
-
@configuration.execution_mode == :external
|
137
|
+
@configuration.execution_mode == :external ||
|
138
|
+
@configuration.execution_mode == :async_server && !in_server_process?
|
135
139
|
end
|
136
140
|
|
137
141
|
# Whether in +:inline+ execution mode.
|
142
|
+
# @return [Boolean]
|
138
143
|
def execute_inline?
|
139
144
|
@configuration.execution_mode == :inline
|
140
145
|
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Whether running in a web server process.
|
150
|
+
# @return [Boolean, nil]
|
151
|
+
def in_server_process?
|
152
|
+
return @_in_server_process if defined? @_in_server_process
|
153
|
+
|
154
|
+
@_in_server_process = Rails.const_defined?('Server') ||
|
155
|
+
caller.grep(%r{config.ru}).any? || # EXAMPLE: config.ru:3:in `block in <main>' OR config.ru:3:in `new_from_string'
|
156
|
+
caller.grep(%{/rack/handler/}).any? || # EXAMPLE: iodine-0.7.44/lib/rack/handler/iodine.rb:13:in `start'
|
157
|
+
(Concurrent.on_jruby? && caller.grep(%r{jruby/rack/rails_booter}).any?) # EXAMPLE: uri:classloader:/jruby/rack/rails_booter.rb:83:in `load_environment'
|
158
|
+
end
|
141
159
|
end
|
142
160
|
end
|
data/lib/good_job/cli.rb
CHANGED
@@ -15,9 +15,21 @@ module GoodJob
|
|
15
15
|
# Requiring this loads the application's configuration and classes.
|
16
16
|
RAILS_ENVIRONMENT_RB = File.expand_path("config/environment.rb")
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
class << self
|
19
|
+
# Whether the CLI is running from the executable
|
20
|
+
# @return [Boolean, nil]
|
21
|
+
attr_accessor :within_exe
|
22
|
+
alias within_exe? within_exe
|
23
|
+
|
24
|
+
# Whether to log to STDOUT
|
25
|
+
# @return [Boolean, nil]
|
26
|
+
attr_accessor :log_to_stdout
|
27
|
+
alias log_to_stdout? log_to_stdout
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
def exit_on_failure?
|
31
|
+
true
|
32
|
+
end
|
21
33
|
end
|
22
34
|
|
23
35
|
# @!macro thor.desc
|
@@ -71,7 +83,7 @@ module GoodJob
|
|
71
83
|
|
72
84
|
notifier = GoodJob::Notifier.new
|
73
85
|
poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
74
|
-
scheduler = GoodJob::Scheduler.from_configuration(configuration)
|
86
|
+
scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
|
75
87
|
notifier.recipients << [scheduler, :create_thread]
|
76
88
|
poller.recipients << [scheduler, :create_thread]
|
77
89
|
|
@@ -136,7 +148,7 @@ module GoodJob
|
|
136
148
|
# Rails or from the application can be set up here.
|
137
149
|
def set_up_application!
|
138
150
|
require RAILS_ENVIRONMENT_RB
|
139
|
-
return unless
|
151
|
+
return unless GoodJob::CLI.log_to_stdout? && !ActiveSupport::Logger.logger_outputs_to?(GoodJob.logger, $stdout)
|
140
152
|
|
141
153
|
GoodJob::LogSubscriber.loggers << ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
|
142
154
|
GoodJob::LogSubscriber.reset_logger
|
@@ -5,6 +5,8 @@ module GoodJob
|
|
5
5
|
# set options to get the final values for each option.
|
6
6
|
#
|
7
7
|
class Configuration
|
8
|
+
# Valid execution modes.
|
9
|
+
EXECUTION_MODES = [:async, :async_server, :external, :inline].freeze
|
8
10
|
# Default number of threads to use per {Scheduler}
|
9
11
|
DEFAULT_MAX_THREADS = 5
|
10
12
|
# Default number of seconds between polls for jobs
|
@@ -13,7 +15,7 @@ module GoodJob
|
|
13
15
|
DEFAULT_MAX_CACHE = 10000
|
14
16
|
# Default number of seconds to preserve jobs for {CLI#cleanup_preserved_jobs}
|
15
17
|
DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO = 24 * 60 * 60
|
16
|
-
# Default to always wait for jobs to finish for {#shutdown}
|
18
|
+
# Default to always wait for jobs to finish for {Adapter#shutdown}
|
17
19
|
DEFAULT_SHUTDOWN_TIMEOUT = -1
|
18
20
|
|
19
21
|
# The options that were explicitly set when initializing +Configuration+.
|
@@ -35,40 +37,30 @@ module GoodJob
|
|
35
37
|
@env = env
|
36
38
|
end
|
37
39
|
|
40
|
+
def validate!
|
41
|
+
raise ArgumentError, "GoodJob execution mode must be one of #{EXECUTION_MODES.join(', ')}. It was '#{execution_mode}' which is not valid." unless execution_mode.in?(EXECUTION_MODES)
|
42
|
+
end
|
43
|
+
|
38
44
|
# Specifies how and where jobs should be executed. See {Adapter#initialize}
|
39
45
|
# for more details on possible values.
|
40
|
-
#
|
41
|
-
# When running inside a Rails app, you may want to use
|
42
|
-
# {#rails_execution_mode}, which takes the current Rails environment into
|
43
|
-
# account when determining the final value.
|
44
|
-
#
|
45
|
-
# @param default [Symbol]
|
46
|
-
# Value to use if none was specified in the configuration.
|
47
46
|
# @return [Symbol]
|
48
|
-
def execution_mode
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
else
|
58
|
-
default
|
59
|
-
end
|
60
|
-
end
|
47
|
+
def execution_mode
|
48
|
+
@_execution_mode ||= begin
|
49
|
+
mode = if GoodJob::CLI.within_exe?
|
50
|
+
:external
|
51
|
+
else
|
52
|
+
options[:execution_mode] ||
|
53
|
+
rails_config[:execution_mode] ||
|
54
|
+
env['GOOD_JOB_EXECUTION_MODE']
|
55
|
+
end
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
:inline
|
70
|
-
else
|
71
|
-
:external
|
57
|
+
if mode
|
58
|
+
mode.to_sym
|
59
|
+
elsif Rails.env.development? || Rails.env.test?
|
60
|
+
:inline
|
61
|
+
else
|
62
|
+
:external
|
63
|
+
end
|
72
64
|
end
|
73
65
|
end
|
74
66
|
|
@@ -92,9 +84,9 @@ module GoodJob
|
|
92
84
|
# on the format of this string.
|
93
85
|
# @return [String]
|
94
86
|
def queue_string
|
95
|
-
options[:queues] ||
|
96
|
-
rails_config[:queues] ||
|
97
|
-
env['GOOD_JOB_QUEUES'] ||
|
87
|
+
options[:queues].presence ||
|
88
|
+
rails_config[:queues].presence ||
|
89
|
+
env['GOOD_JOB_QUEUES'].presence ||
|
98
90
|
'*'
|
99
91
|
end
|
100
92
|
|
@@ -3,7 +3,6 @@ require 'active_support/core_ext/module/attribute_accessors_per_thread'
|
|
3
3
|
module GoodJob
|
4
4
|
# Thread-local attributes for passing values from Instrumentation.
|
5
5
|
# (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
|
6
|
-
|
7
6
|
module CurrentExecution
|
8
7
|
# @!attribute [rw] error_on_retry
|
9
8
|
# @!scope class
|
data/lib/good_job/daemon.rb
CHANGED
@@ -13,6 +13,7 @@ module GoodJob
|
|
13
13
|
end
|
14
14
|
|
15
15
|
# Daemonizes the current process and writes out a pidfile.
|
16
|
+
# @return [void]
|
16
17
|
def daemonize
|
17
18
|
check_pid
|
18
19
|
Process.daemon
|
@@ -21,6 +22,7 @@ module GoodJob
|
|
21
22
|
|
22
23
|
private
|
23
24
|
|
25
|
+
# @return [void]
|
24
26
|
def write_pid
|
25
27
|
File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY) { |f| f.write(Process.pid.to_s) }
|
26
28
|
at_exit { File.delete(pidfile) if File.exist?(pidfile) }
|
@@ -29,10 +31,12 @@ module GoodJob
|
|
29
31
|
retry
|
30
32
|
end
|
31
33
|
|
34
|
+
# @return [void]
|
32
35
|
def delete_pid
|
33
36
|
File.delete(pidfile) if File.exist?(pidfile)
|
34
37
|
end
|
35
38
|
|
39
|
+
# @return [void]
|
36
40
|
def check_pid
|
37
41
|
case pid_status(pidfile)
|
38
42
|
when :running, :not_owned
|
@@ -42,6 +46,8 @@ module GoodJob
|
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
49
|
+
# @param pidfile [Pathname, String]
|
50
|
+
# @return [Symbol]
|
45
51
|
def pid_status(pidfile)
|
46
52
|
return :exited unless File.exist?(pidfile)
|
47
53
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GoodJob
|
2
|
+
# Stores the results of job execution
|
3
|
+
class ExecutionResult
|
4
|
+
# @return [Object, nil]
|
5
|
+
attr_reader :value
|
6
|
+
# @return [Exception, nil]
|
7
|
+
attr_reader :handled_error
|
8
|
+
# @return [Exception, nil]
|
9
|
+
attr_reader :unhandled_error
|
10
|
+
|
11
|
+
# @param value [Object, nil]
|
12
|
+
# @param handled_error [Exception, nil]
|
13
|
+
# @param unhandled_error [Exception, nil]
|
14
|
+
def initialize(value:, handled_error: nil, unhandled_error: nil)
|
15
|
+
@value = value
|
16
|
+
@handled_error = handled_error
|
17
|
+
@unhandled_error = unhandled_error
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/good_job/job.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module GoodJob
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
class Job < ActiveRecord::Base
|
2
|
+
# ActiveRecord model that represents an +ActiveJob+ job.
|
3
|
+
# Parent class can be configured with +GoodJob.active_record_parent_class+.
|
4
|
+
# @!parse
|
5
|
+
# class Job < ActiveRecord::Base; end
|
6
|
+
class Job < Object.const_get(GoodJob.active_record_parent_class)
|
6
7
|
include Lockable
|
7
8
|
|
8
9
|
# Raised if something attempts to execute a previously completed Job again.
|
@@ -19,6 +20,7 @@ module GoodJob
|
|
19
20
|
|
20
21
|
# Parse a string representing a group of queues into a more readable data
|
21
22
|
# structure.
|
23
|
+
# @param string [String] Queue string
|
22
24
|
# @return [Hash]
|
23
25
|
# How to match a given queue. It can have the following keys and values:
|
24
26
|
# - +{ all: true }+ indicates that all queues match.
|
@@ -48,6 +50,14 @@ module GoodJob
|
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
53
|
+
# Get Jobs with given class name
|
54
|
+
# @!method with_job_class
|
55
|
+
# @!scope class
|
56
|
+
# @param string [String]
|
57
|
+
# Job class name
|
58
|
+
# @return [ActiveRecord::Relation]
|
59
|
+
scope :with_job_class, ->(job_class) { where("serialized_params->>'job_class' = ?", job_class) }
|
60
|
+
|
51
61
|
# Get Jobs that have not yet been completed.
|
52
62
|
# @!method unfinished
|
53
63
|
# @!scope class
|
@@ -92,6 +102,12 @@ module GoodJob
|
|
92
102
|
# @return [ActiveRecord::Relation]
|
93
103
|
scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
|
94
104
|
|
105
|
+
# Get Jobs that started but not finished yet.
|
106
|
+
# @!method running
|
107
|
+
# @!scope class
|
108
|
+
# @return [ActiveRecord::Relation]
|
109
|
+
scope :running, -> { where.not(performed_at: nil).where(finished_at: nil) }
|
110
|
+
|
95
111
|
# Get Jobs on queues that match the given queue string.
|
96
112
|
# @!method queue_string(string)
|
97
113
|
# @!scope class
|
@@ -134,29 +150,26 @@ module GoodJob
|
|
134
150
|
|
135
151
|
# Finds the next eligible Job, acquire an advisory lock related to it, and
|
136
152
|
# executes the job.
|
137
|
-
# @return [
|
153
|
+
# @return [ExecutionResult, nil]
|
138
154
|
# If a job was executed, returns an array with the {Job} record, the
|
139
155
|
# return value for the job's +#perform+ method, and the exception the job
|
140
156
|
# raised, if any (if the job raised, then the second array entry will be
|
141
157
|
# +nil+). If there were no jobs to execute, returns +nil+.
|
142
158
|
def self.perform_with_advisory_lock
|
143
|
-
good_job = nil
|
144
|
-
result = nil
|
145
|
-
error = nil
|
146
|
-
|
147
159
|
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
148
160
|
good_job = good_jobs.first
|
149
161
|
# TODO: Determine why some records are fetched without an advisory lock at all
|
150
162
|
break unless good_job&.executable?
|
151
163
|
|
152
|
-
|
164
|
+
good_job.perform
|
153
165
|
end
|
154
|
-
|
155
|
-
[good_job, result, error] if good_job
|
156
166
|
end
|
157
167
|
|
158
168
|
# Fetches the scheduled execution time of the next eligible Job(s).
|
159
|
-
# @
|
169
|
+
# @param after [DateTime]
|
170
|
+
# @param limit [Integer]
|
171
|
+
# @param now_limit [Integer, nil]
|
172
|
+
# @return [Array<DateTime>]
|
160
173
|
def self.next_scheduled_at(after: nil, limit: 100, now_limit: nil)
|
161
174
|
query = advisory_unlocked.unfinished.schedule_ordered
|
162
175
|
|
@@ -182,7 +195,6 @@ module GoodJob
|
|
182
195
|
# @return [Job]
|
183
196
|
# The new {Job} instance representing the queued ActiveJob job.
|
184
197
|
def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
|
185
|
-
good_job = nil
|
186
198
|
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|
|
187
199
|
good_job = GoodJob::Job.new(
|
188
200
|
queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
|
@@ -196,49 +208,37 @@ module GoodJob
|
|
196
208
|
|
197
209
|
good_job.save!
|
198
210
|
active_job.provider_job_id = good_job.id
|
199
|
-
end
|
200
211
|
|
201
|
-
|
212
|
+
good_job
|
213
|
+
end
|
202
214
|
end
|
203
215
|
|
204
216
|
# Execute the ActiveJob job this {Job} represents.
|
205
|
-
# @return [
|
217
|
+
# @return [ExecutionResult]
|
206
218
|
# An array of the return value of the job's +#perform+ method and the
|
207
219
|
# exception raised by the job, if any. If the job completed successfully,
|
208
220
|
# the second array entry (the exception) will be +nil+ and vice versa.
|
209
221
|
def perform
|
210
222
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
211
223
|
|
212
|
-
GoodJob::CurrentExecution.reset
|
213
|
-
|
214
224
|
self.performed_at = Time.current
|
215
225
|
save! if GoodJob.preserve_job_records
|
216
226
|
|
217
|
-
result
|
218
|
-
|
219
|
-
result_error = nil
|
220
|
-
if result.is_a?(Exception)
|
221
|
-
result_error = result
|
222
|
-
result = nil
|
223
|
-
end
|
224
|
-
|
225
|
-
job_error = unhandled_error ||
|
226
|
-
result_error ||
|
227
|
-
GoodJob::CurrentExecution.error_on_retry ||
|
228
|
-
GoodJob::CurrentExecution.error_on_discard
|
227
|
+
result = execute
|
229
228
|
|
229
|
+
job_error = result.handled_error || result.unhandled_error
|
230
230
|
self.error = "#{job_error.class}: #{job_error.message}" if job_error
|
231
231
|
|
232
|
-
if unhandled_error && GoodJob.retry_on_unhandled_error
|
232
|
+
if result.unhandled_error && GoodJob.retry_on_unhandled_error
|
233
233
|
save!
|
234
|
-
elsif GoodJob.preserve_job_records == true || (unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error)
|
234
|
+
elsif GoodJob.preserve_job_records == true || (result.unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error)
|
235
235
|
self.finished_at = Time.current
|
236
236
|
save!
|
237
237
|
else
|
238
238
|
destroy!
|
239
239
|
end
|
240
240
|
|
241
|
-
|
241
|
+
result
|
242
242
|
end
|
243
243
|
|
244
244
|
# Tests whether this job is safe to be executed by this thread.
|
@@ -249,16 +249,26 @@ module GoodJob
|
|
249
249
|
|
250
250
|
private
|
251
251
|
|
252
|
+
# @return [ExecutionResult]
|
252
253
|
def execute
|
253
254
|
params = serialized_params.merge(
|
254
255
|
"provider_job_id" => id
|
255
256
|
)
|
256
257
|
|
258
|
+
GoodJob::CurrentExecution.reset
|
257
259
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
258
|
-
|
260
|
+
value = ActiveJob::Base.execute(params)
|
261
|
+
|
262
|
+
if value.is_a?(Exception)
|
263
|
+
handled_error = value
|
264
|
+
value = nil
|
265
|
+
end
|
266
|
+
handled_error ||= GoodJob::CurrentExecution.error_on_retry || GoodJob::CurrentExecution.error_on_discard
|
267
|
+
|
268
|
+
ExecutionResult.new(value: value, handled_error: handled_error)
|
269
|
+
rescue StandardError => e
|
270
|
+
ExecutionResult.new(value: nil, unhandled_error: e)
|
259
271
|
end
|
260
|
-
rescue StandardError => e
|
261
|
-
[nil, e]
|
262
272
|
end
|
263
273
|
end
|
264
274
|
end
|