74 lines
1.4 KiB
Ruby
74 lines
1.4 KiB
Ruby
require "net/smtp"
|
|
require "openssl"
|
|
|
|
class Mailings::SmtpGate
|
|
MUTEX = Mutex.new
|
|
RETRYABLE = [
|
|
EOFError,
|
|
IOError,
|
|
Errno::ECONNRESET,
|
|
Errno::EPIPE,
|
|
Errno::ETIMEDOUT,
|
|
Net::OpenTimeout,
|
|
Net::ReadTimeout,
|
|
OpenSSL::SSL::SSLError,
|
|
Net::SMTPServerBusy
|
|
].freeze
|
|
|
|
class << self
|
|
attr_accessor :min_gap, :max_attempts, :backoff_base
|
|
|
|
def deliver
|
|
MUTEX.synchronize { deliver_locked { yield } }
|
|
end
|
|
|
|
def reset!
|
|
MUTEX.synchronize { @last_monotonic = nil }
|
|
end
|
|
|
|
private
|
|
|
|
def deliver_locked
|
|
attempts = 0
|
|
begin
|
|
wait_min_gap
|
|
result = yield
|
|
stamp!
|
|
result
|
|
rescue *RETRYABLE => e
|
|
attempts += 1
|
|
stamp!
|
|
Rails.logger.warn("[smtp-gate] #{e.class}: #{e.message} attempt=#{attempts}/#{max_attempts}")
|
|
raise if attempts >= max_attempts
|
|
|
|
sleep_wait(backoff_base * (2**(attempts - 1)))
|
|
retry
|
|
end
|
|
end
|
|
|
|
def wait_min_gap
|
|
return if min_gap.to_f <= 0 || @last_monotonic.nil?
|
|
|
|
elapsed = now - @last_monotonic
|
|
remaining = min_gap - elapsed
|
|
sleep_wait(remaining) if remaining.positive?
|
|
end
|
|
|
|
def stamp!
|
|
@last_monotonic = now
|
|
end
|
|
|
|
def now
|
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
end
|
|
|
|
def sleep_wait(seconds)
|
|
sleep(seconds) if seconds.to_f.positive?
|
|
end
|
|
end
|
|
|
|
self.min_gap = 0.0
|
|
self.max_attempts = 1
|
|
self.backoff_base = 0.0
|
|
end
|