64 lines
1.4 KiB
Ruby
64 lines
1.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class TournamentSyncQueueEntry < ApplicationRecord
|
|
LOCK_TTL = 2.minutes
|
|
MAX_BACKOFF = 5.minutes
|
|
|
|
belongs_to :tournament
|
|
|
|
validates :snapshot_json, presence: true
|
|
validates :status, presence: true
|
|
|
|
scope :due, -> { where(status: 'pending').where('next_attempt_at <= ?', Time.current) }
|
|
scope :unlocked, lambda {
|
|
where(locked_at: nil).or(where('locked_at < ?', Time.current - LOCK_TTL))
|
|
}
|
|
|
|
def snapshot
|
|
JSON.parse(snapshot_json, symbolize_names: true)
|
|
end
|
|
|
|
def schedule_retry!(error_message)
|
|
update!(
|
|
status: 'pending',
|
|
last_error: error_message,
|
|
attempts: attempts + 1,
|
|
last_attempt_at: Time.current,
|
|
next_attempt_at: Time.current + retry_delay,
|
|
locked_at: nil,
|
|
lock_token: nil
|
|
)
|
|
end
|
|
|
|
def mark_synced!
|
|
update!(
|
|
status: 'synced',
|
|
last_error: nil,
|
|
last_attempt_at: Time.current,
|
|
next_attempt_at: Time.current,
|
|
attempts: 0,
|
|
locked_at: nil,
|
|
lock_token: nil
|
|
)
|
|
end
|
|
|
|
def acquire_lock!
|
|
token = SecureRandom.hex(8)
|
|
updated = self.class
|
|
.where(id:)
|
|
.where(locked_at: nil)
|
|
.or(self.class.where(id:).where('locked_at < ?', Time.current - LOCK_TTL))
|
|
.update_all(locked_at: Time.current, lock_token: token)
|
|
return nil if updated.zero?
|
|
|
|
reload
|
|
token
|
|
end
|
|
|
|
private
|
|
|
|
def retry_delay
|
|
[2**attempts, MAX_BACKOFF].min.seconds
|
|
end
|
|
end
|