diff --git a/README.md b/README.md index 73a3a32..9b54dd2 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ To deploy to Kubernetes, see [kubernetes/README.md](kubernetes/README.md). A Docker image is available on [Docker Hub](https://hub.docker.com/r/stefansundin/rssbox) and [Amazon ECR](https://gallery.ecr.aws/stefansundin/rssbox). +**Note:** Redis is now an optional dependency! It is only used for the URL resolution feature (turned off on the public Heroku instance). + #### Heroku If you need to re-provision redis, or you didn't use the deploy button above to provision the app initially, then you need to make sure to set the maxmemory policy: diff --git a/app.rb b/app.rb index 2156817..2d18c13 100644 --- a/app.rb +++ b/app.rb @@ -1562,11 +1562,7 @@ end get "/health" do - if $redis.ping != "PONG" - return [500, "Redis error"] - end -rescue Redis::CannotConnectError => e - return [500, "Redis connection error"] + return [200, ""] end if ENV["GOOGLE_VERIFICATION_TOKEN"] diff --git a/app/url.rb b/app/url.rb index a07f58d..2220b77 100644 --- a/app/url.rb +++ b/app/url.rb @@ -2,7 +2,7 @@ module App class URL - URL_RESOLUTION_DISABLED = (ENV["URL_RESOLUTION_DISABLED"] == "true") + URL_RESOLUTION_DISABLED = ($redis.nil? || ENV["URL_RESOLUTION_DISABLED"] == "true") @@cache = {} @@ -51,6 +51,8 @@ def self.resolve(urls, force=false) end threads.map(&:join) return nil + rescue Redis::BaseConnectionError + return nil end private diff --git a/config/initializers/20-redis.rb b/config/initializers/20-redis.rb index d1246cc..322e08b 100644 --- a/config/initializers/20-redis.rb +++ b/config/initializers/20-redis.rb @@ -1,7 +1,46 @@ # frozen_string_literal: true -begin - $redis = Redis.new -rescue - puts "Failed to connect to redis!" +# Monkeypatch redis to throttle it from attempting to connect more often than once a second + +class Redis + class ThrottledConnectError < BaseConnectionError + end + + class Client + @@last_connect_error = nil + + # https://github.com/redis/redis-rb/blob/v4.6.0/lib/redis/client.rb#L379-L399 + def establish_connection + if @@last_connect_error && Time.now < @@last_connect_error + 1 + raise ThrottledConnectError, "Throttled connection attempt to Redis on #{location}" + end + + server = @connector.resolve.dup + + @options[:host] = server[:host] + @options[:port] = Integer(server[:port]) if server.include?(:port) + + @connection = @options[:driver].connect(@options) + @pending_reads = 0 + rescue TimeoutError, + SocketError, + Errno::EADDRNOTAVAIL, + Errno::ECONNREFUSED, + Errno::EHOSTDOWN, + Errno::EHOSTUNREACH, + Errno::ENETUNREACH, + Errno::ENOENT, + Errno::ETIMEDOUT, + Errno::EINVAL => error + + @@last_connect_error = Time.now + raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})" + end + end +end + +if ENV.has_key?("REDIS_URL") + $redis = Redis.new({ + reconnect_attempts: 0, + }) end