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