-
-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ensure Process records always exist when executing jobs and are refre…
…shed in background
- Loading branch information
1 parent
3c45787
commit e98c176
Showing
38 changed files
with
921 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'socket' | ||
|
||
module GoodJob # :nodoc: | ||
# Active Record model that represents a GoodJob capsule/process (either async or CLI). | ||
class CapsuleRecord < BaseRecord | ||
include AdvisoryLockable | ||
include AssignableConnection | ||
|
||
# Interval until the process record being updated | ||
STALE_INTERVAL = 30.seconds | ||
# Interval until the process record is treated as expired | ||
EXPIRED_INTERVAL = 5.minutes | ||
|
||
LOCK_TYPES = [ | ||
LOCK_TYPE_ADVISORY = 'advisory', | ||
].freeze | ||
|
||
LOCK_TYPE_ENUMS = { | ||
LOCK_TYPE_ADVISORY => 1, | ||
}.freeze | ||
|
||
self.table_name = 'good_job_processes' | ||
|
||
# Processes that are active and locked. | ||
# @!method active | ||
# @!scope class | ||
# @return [ActiveRecord::Relation] | ||
scope :active, (lambda do | ||
if lock_type_migrated? | ||
query = joins_advisory_locks | ||
query.where(lock_type: LOCK_TYPE_ENUMS[LOCK_TYPE_ADVISORY]).advisory_locked | ||
.or(query.where(lock_type: nil).where(arel_table[:updated_at].gt(EXPIRED_INTERVAL.ago))) | ||
else | ||
advisory_locked | ||
end | ||
end) | ||
|
||
# Processes that are inactive and unlocked (e.g. SIGKILLed) | ||
# @!method active | ||
# @!scope class | ||
# @return [ActiveRecord::Relation] | ||
scope :inactive, (lambda do | ||
if lock_type_migrated? | ||
query = joins_advisory_locks | ||
query.where(lock_type: LOCK_TYPE_ENUMS[LOCK_TYPE_ADVISORY]).advisory_unlocked | ||
.or(query.where(lock_type: nil).where(arel_table[:updated_at].lt(EXPIRED_INTERVAL.ago))) | ||
else | ||
advisory_unlocked | ||
end | ||
end) | ||
|
||
# Deletes all inactive process records. | ||
def self.cleanup | ||
inactive.find_each(&:cleanup) | ||
end | ||
|
||
# @return [Boolean] | ||
def self.lock_type_migrated? | ||
columns_hash["lock_type"].present? | ||
end | ||
|
||
def self.create_record(id:, with_advisory_lock: false) | ||
attributes = { | ||
id: id, | ||
state: process_state, | ||
} | ||
if with_advisory_lock | ||
attributes[:create_with_advisory_lock] = true | ||
attributes[:lock_type] = LOCK_TYPE_ADVISORY if lock_type_migrated? | ||
end | ||
create!(attributes) | ||
end | ||
|
||
def self.process_state | ||
{ | ||
hostname: Socket.gethostname, | ||
pid: ::Process.pid, | ||
proctitle: $PROGRAM_NAME, | ||
preserve_job_records: GoodJob.preserve_job_records, | ||
retry_on_unhandled_error: GoodJob.retry_on_unhandled_error, | ||
schedulers: GoodJob::Scheduler.instances.map(&:stats), | ||
cron_enabled: GoodJob.configuration.enable_cron?, | ||
total_succeeded_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:succeeded_executions_count) }, | ||
total_errored_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:errored_executions_count) }, | ||
database_connection_pool: { | ||
size: connection_pool.size, | ||
active: connection_pool.connections.count(&:in_use?), | ||
}, | ||
} | ||
end | ||
|
||
def refresh | ||
self.state = self.class.process_state | ||
reload.update(state: state, updated_at: Time.current) | ||
rescue ActiveRecord::RecordNotFound | ||
@new_record = true | ||
self.created_at = self.updated_at = nil | ||
state_will_change! | ||
save | ||
end | ||
|
||
def refresh_if_stale(cleanup: false) | ||
return unless stale? | ||
|
||
result = refresh | ||
self.class.cleanup if cleanup | ||
result | ||
end | ||
|
||
def cleanup | ||
GoodJob::Job.where(locked_by_id: id).update_all(locked_by_id: nil, locked_at: nil) if GoodJob::Job.process_lock_migrated? # rubocop:disable Rails/SkipsModelValidations | ||
delete | ||
end | ||
|
||
def state | ||
super || {} | ||
end | ||
|
||
def stale? | ||
updated_at < STALE_INTERVAL.ago | ||
end | ||
|
||
def expired? | ||
updated_at < EXPIRED_INTERVAL.ago | ||
end | ||
|
||
def basename | ||
File.basename(state.fetch("proctitle", "")) | ||
end | ||
|
||
def schedulers | ||
state.fetch("schedulers", []) | ||
end | ||
|
||
def lock_type | ||
return unless self.class.columns_hash['lock_type'] | ||
|
||
enum = super | ||
LOCK_TYPE_ENUMS.key(enum) if enum | ||
end | ||
|
||
def lock_type=(value) | ||
return unless self.class.columns_hash['lock_type'] | ||
|
||
enum = LOCK_TYPE_ENUMS[value] | ||
raise(ArgumentError, "Invalid error_event: #{value}") if value && !enum | ||
|
||
super(enum) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.