fix: exponential backoff before prompting reauthorization
This commit is contained in:
@@ -6,26 +6,30 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
|
||||
def perform(channel, interval = 1)
|
||||
return unless should_fetch_email?(channel)
|
||||
|
||||
key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id)
|
||||
|
||||
with_lock(key, 5.minutes) do
|
||||
process_email_for_channel(channel, interval)
|
||||
end
|
||||
rescue *ExceptionList::IMAP_EXCEPTIONS => e
|
||||
Rails.logger.error "Authorization error for email channel - #{channel.inbox.id} : #{e.message}"
|
||||
rescue EOFError, OpenSSL::SSL::SSLError, Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, Net::IMAP::InvalidResponseError,
|
||||
Net::IMAP::ResponseParseError, Net::IMAP::ResponseReadError, Net::IMAP::ResponseTooLargeError => e
|
||||
Rails.logger.error "Error for email channel - #{channel.inbox.id} : #{e.message}"
|
||||
rescue LockAcquisitionError
|
||||
Rails.logger.error "Lock failed for #{channel.inbox.id}"
|
||||
fetch_emails_with_backoff(channel, interval)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e, account: channel.account).capture_exception
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_emails_with_backoff(channel, interval)
|
||||
key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id)
|
||||
with_lock(key, 5.minutes) { process_email_for_channel(channel, interval) }
|
||||
channel.clear_imap_backoff!
|
||||
rescue *ExceptionList::IMAP_EXCEPTIONS => e
|
||||
Rails.logger.error "Authorization error for email channel - #{channel.inbox.id} : #{e.message}"
|
||||
channel.apply_imap_backoff!
|
||||
rescue EOFError, OpenSSL::SSL::SSLError, Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, Net::IMAP::InvalidResponseError,
|
||||
Net::IMAP::ResponseParseError, Net::IMAP::ResponseReadError, Net::IMAP::ResponseTooLargeError => e
|
||||
Rails.logger.error "Error for email channel - #{channel.inbox.id} : #{e.message}"
|
||||
channel.apply_imap_backoff!
|
||||
rescue LockAcquisitionError
|
||||
Rails.logger.error "Lock failed for #{channel.inbox.id}"
|
||||
end
|
||||
|
||||
def should_fetch_email?(channel)
|
||||
channel.imap_enabled? && !channel.reauthorization_required?
|
||||
channel.imap_enabled? && !channel.reauthorization_required? && !channel.in_backoff?
|
||||
end
|
||||
|
||||
def process_email_for_channel(channel, interval)
|
||||
|
||||
@@ -39,6 +39,8 @@ class Channel::Email < ApplicationRecord
|
||||
include Reauthorizable
|
||||
|
||||
AUTHORIZATION_ERROR_THRESHOLD = 10
|
||||
MAX_IMAP_RETRIES = 5
|
||||
IMAP_BACKOFF_BASE_SECONDS = 60
|
||||
|
||||
# TODO: Remove guard once encryption keys become mandatory (target 3-4 releases out).
|
||||
if Chatwoot.encryption_configured?
|
||||
@@ -72,6 +74,27 @@ class Channel::Email < ApplicationRecord
|
||||
imap_enabled && imap_address == 'imap.gmail.com'
|
||||
end
|
||||
|
||||
def in_backoff?
|
||||
imap_retry_after.present? && imap_retry_after > Time.current
|
||||
end
|
||||
|
||||
def apply_imap_backoff!
|
||||
new_count = imap_retry_count + 1
|
||||
if new_count >= MAX_IMAP_RETRIES
|
||||
update!(imap_retry_count: 0, imap_retry_after: nil)
|
||||
prompt_reauthorization!
|
||||
else
|
||||
backoff_seconds = IMAP_BACKOFF_BASE_SECONDS * (2**new_count)
|
||||
update!(imap_retry_count: new_count, imap_retry_after: backoff_seconds.seconds.from_now)
|
||||
end
|
||||
end
|
||||
|
||||
def clear_imap_backoff!
|
||||
return unless imap_retry_count.positive? || imap_retry_after.present?
|
||||
|
||||
update!(imap_retry_count: 0, imap_retry_after: nil)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_forward_to_email
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
class AddImapBackoffToChannelEmail < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :channel_email, :imap_retry_count, :integer, default: 0, null: false
|
||||
add_column :channel_email, :imap_retry_after, :datetime
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user