Class: GoodJob::Job

Inherits:
BaseExecution show all
Defined in:
app/models/good_job/job.rb

Overview

Active Record model that represents an ActiveJob job.

Constant Summary collapse

ActionForStateMismatchError =

Raised when an inappropriate action is applied to a Job based on its state.

Class.new(StandardError)
AdapterNotGoodJobError =

Raised when GoodJob is not configured as the Active Job Queue Adapter

Class.new(StandardError)
DiscardJobError =

Attached to a Job’s Execution when the Job is discarded.

Class.new(StandardError)
ActiveJobDeserializationError =

Raised when Active Job data cannot be deserialized

Class.new(StandardError)

Constants inherited from BaseExecution

BaseExecution::DEFAULT_PRIORITY, BaseExecution::DEFAULT_QUEUE_NAME, BaseExecution::ERROR_MESSAGE_SEPARATOR, BaseExecution::PreviouslyPerformedError

Constants included from ErrorEvents

ErrorEvents::ERROR_EVENTS, ErrorEvents::ERROR_EVENT_ENUMS

Constants included from AdvisoryLockable

AdvisoryLockable::RecordAlreadyAdvisoryLockedError

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseExecution

#active_job, active_job_id, build_for_enqueue, coalesce_scheduled_at_created_at, creation_ordered, dequeueing_ordered, enqueue, enqueue_args, #executable?, finished, format_error, job_class, #job_state, json_string, next_scheduled_at, #number, only_scheduled, params_execution_count, params_job_class, #perform, perform_with_advisory_lock, priority_ordered, #queue_latency, queue_ordered, queue_parser, queue_string, running, #runtime_latency, schedule_ordered, unfinished

Methods included from Reportable

#last_status_at, #status

Methods included from ErrorEvents

#error_event, #error_event=

Methods included from AdvisoryLockable

#advisory_lock, #advisory_lock!, #advisory_locked?, #advisory_unlock, #advisory_unlock!, #advisory_unlocked?, #lockable_column_key, #lockable_key, #owns_advisory_lock?, #with_advisory_lock

Methods inherited from BaseRecord

bind_value, migrated?, migration_pending_warning!, with_logger_silenced

Class Method Details

.finished_before(timestamp) ⇒ ActiveRecord::Relation

Get Jobs finished before the given timestamp.

Parameters:

  • timestamp (DateTime, Time)

Returns:

  • (ActiveRecord::Relation)


32
# File 'app/models/good_job/job.rb', line 32

scope :finished_before, ->(timestamp) { where(arel_table['finished_at'].lteq(bind_value('finished_at', timestamp, ActiveRecord::Type::DateTime))) }

Instance Method Details

#_execution_idString

Utility method to determine which execution record is used to represent this job

Returns:

  • (String)


198
199
200
# File 'app/models/good_job/job.rb', line 198

def _execution_id
  attributes['id']
end

#destroy_jobvoid

This method returns an undefined value.

Destroy all of a discarded or finished job’s executions from the database so that it will no longer appear on the dashboard.



188
189
190
191
192
193
194
# File 'app/models/good_job/job.rb', line 188

def destroy_job
  with_advisory_lock do
    raise ActionForStateMismatchError if finished_at.blank?

    destroy
  end
end

#discard_job(message) ⇒ void

This method returns an undefined value.

Discard a job so that it will not be executed further. This action will add a DiscardJobError to the job’s Execution and mark it as finished.



161
162
163
164
165
# File 'app/models/good_job/job.rb', line 161

def discard_job(message)
  with_advisory_lock do
    _discard_job(message)
  end
end

#discarded?Boolean

Tests whether the job has finished but with an error.

Returns:

  • (Boolean)


109
110
111
# File 'app/models/good_job/job.rb', line 109

def discarded?
  finished? && error.present?
end

#display_errorString

Errors for the job to be displayed in the Dashboard.

Returns:

  • (String)


64
65
66
67
68
69
70
# File 'app/models/good_job/job.rb', line 64

def display_error
  return error if error.present?

  serialized_params.fetch('exception_executions', {}).map do |exception, count|
    "#{exception}: #{count}"
  end.join(', ')
end

#display_nameString

Used when displaying this job in the GoodJob dashboard.

Returns:

  • (String)


82
83
84
# File 'app/models/good_job/job.rb', line 82

def display_name
  job_class
end

#display_serialized_paramsHash

Return formatted serialized_params for display in the dashboard

Returns:

  • (Hash)


74
75
76
77
78
# File 'app/models/good_job/job.rb', line 74

def display_serialized_params
  serialized_params.merge({
                            _good_job: attributes.except('serialized_params', 'locktype', 'owns_advisory_lock'),
                          })
end

#executions_countObject



86
87
88
# File 'app/models/good_job/job.rb', line 86

def executions_count
  super || 0
end

#finished?Boolean

Tests whether the job has finished (succeeded or discarded).

Returns:

  • (Boolean)


103
104
105
# File 'app/models/good_job/job.rb', line 103

def finished?
  finished_at.present? && retried_good_job_id.nil?
end

#force_discard_job(message) ⇒ Object

Force discard a job so that it will not be executed further. Force discard allows discarding a running job. This action will add a DiscardJobError to the job’s Execution and mark it as finished.



170
171
172
# File 'app/models/good_job/job.rb', line 170

def force_discard_job(message)
  _discard_job(message)
end

#recent_errorString

The most recent error message. If the job has been retried, the error will be fetched from the previous Execution record.

Returns:

  • (String)


58
59
60
# File 'app/models/good_job/job.rb', line 58

def recent_error
  error || executions[-2]&.error
end

#reschedule_job(scheduled_at = Time.current) ⇒ void

This method returns an undefined value.

Reschedule a scheduled job so that it executes immediately (or later) by the next available execution thread.

Parameters:

  • scheduled_at (DateTime, Time) (defaults to: Time.current)

    When to reschedule the job



177
178
179
180
181
182
183
184
# File 'app/models/good_job/job.rb', line 177

def reschedule_job(scheduled_at = Time.current)
  with_advisory_lock do
    reload
    raise ActionForStateMismatchError if finished_at.present?

    update(scheduled_at: scheduled_at)
  end
end

#retry_jobActiveJob::Base

Retry a job that has errored and been discarded. This action will create a new Execution record for the job.

Returns:

  • (ActiveJob::Base)


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'app/models/good_job/job.rb', line 122

def retry_job
  with_advisory_lock do
    reload
    active_job = self.active_job(ignore_deserialization_errors: true)

    raise ActiveJobDeserializationError if active_job.nil?
    raise AdapterNotGoodJobError unless active_job.class.queue_adapter.is_a? GoodJob::Adapter
    raise ActionForStateMismatchError if finished_at.blank? || error.blank?

    # Update the executions count because the previous execution will not have been preserved
    # Do not update `exception_executions` because that comes from rescue_from's arguments
    active_job.executions = (active_job.executions || 0) + 1

    begin
      error_class, error_message = error.split(ERROR_MESSAGE_SEPARATOR).map(&:strip)
      error = error_class.constantize.new(error_message)
    rescue StandardError
      error = StandardError.new(error)
    end

    new_active_job = nil
    GoodJob::CurrentThread.within do |current_thread|
      current_thread.job = self
      current_thread.retry_now = true

      self.class.transaction(joinable: false, requires_new: true) do
        new_active_job = active_job.retry_job(wait: 0, error: error)
        self.error_event = ERROR_EVENT_RETRIED if error
        save!
      end
    end

    new_active_job
  end
end

#running?Boolean

Tests whether the job is being executed right now.

Returns:

  • (Boolean)


92
93
94
95
96
97
98
99
# File 'app/models/good_job/job.rb', line 92

def running?
  # Avoid N+1 Query: `.includes_advisory_locks`
  if has_attribute?(:locktype)
    self['locktype'].present?
  else
    advisory_locked?
  end
end

#succeeded?Boolean

Tests whether the job has finished without error

Returns:

  • (Boolean)


115
116
117
# File 'app/models/good_job/job.rb', line 115

def succeeded?
  finished? && !discarded?
end