2011-07-07 05:43:19 +08:00
|
|
|
module Canvas::Redis
|
2011-11-04 05:51:51 +08:00
|
|
|
# try to grab a lock in Redis, returning false if the lock can't be held. If
|
|
|
|
# the lock is grabbed and `ttl` is given, it'll be set to expire after `ttl`
|
|
|
|
# seconds.
|
|
|
|
def self.lock(key, ttl = nil)
|
|
|
|
return true unless Canvas.redis_enabled?
|
|
|
|
full_key = lock_key(key)
|
|
|
|
if Canvas.redis.setnx(full_key, 1)
|
|
|
|
Canvas.redis.expire(full_key, ttl.to_i) if ttl
|
|
|
|
true
|
|
|
|
else
|
|
|
|
# key is already used
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# unlock a previously grabbed Redis lock. This doesn't do anything to verify
|
|
|
|
# that this process took the lock.
|
|
|
|
def self.unlock(key)
|
|
|
|
Canvas.redis.del(lock_key(key))
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.lock_key(key)
|
|
|
|
"lock:#{key}"
|
|
|
|
end
|
|
|
|
|
2012-09-11 05:05:21 +08:00
|
|
|
def self.ignore_redis_failures?
|
2013-10-05 04:02:49 +08:00
|
|
|
Setting.get('ignore_redis_failures', 'true') == 'true'
|
2012-09-11 05:05:21 +08:00
|
|
|
end
|
|
|
|
|
2013-07-26 03:34:09 +08:00
|
|
|
def self.redis_failure?(redis_name)
|
|
|
|
return false unless last_redis_failure[redis_name]
|
2012-01-17 05:28:01 +08:00
|
|
|
# i feel this dangling rescue is justifiable, given the try-to-be-failsafe nature of this code
|
2013-10-05 04:02:49 +08:00
|
|
|
return (Time.now - last_redis_failure[redis_name]) < (Setting.get('redis_failure_time', '300').to_i rescue 300)
|
2013-07-26 03:34:09 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.last_redis_failure
|
|
|
|
@last_redis_failure ||= {}
|
2012-01-17 05:28:01 +08:00
|
|
|
end
|
2011-07-07 05:43:19 +08:00
|
|
|
|
2012-01-17 05:28:01 +08:00
|
|
|
def self.reset_redis_failure
|
2013-07-26 03:34:09 +08:00
|
|
|
@last_redis_failure = {}
|
2012-01-17 05:28:01 +08:00
|
|
|
end
|
2011-07-07 05:43:19 +08:00
|
|
|
|
2012-09-11 05:05:21 +08:00
|
|
|
def self.handle_redis_failure(failure_retval, redis_name)
|
2013-07-26 03:34:09 +08:00
|
|
|
return failure_retval if redis_failure?(redis_name)
|
2012-01-17 05:28:01 +08:00
|
|
|
yield
|
2012-06-13 00:17:36 +08:00
|
|
|
rescue Redis::BaseConnectionError => e
|
2012-09-11 05:05:21 +08:00
|
|
|
Canvas::Statsd.increment("redis.errors.all")
|
2013-07-30 22:56:56 +08:00
|
|
|
Canvas::Statsd.increment("redis.errors.#{Canvas::Statsd.escape(redis_name)}")
|
2012-09-11 05:05:21 +08:00
|
|
|
Rails.logger.error "Failure handling redis command on #{redis_name}: #{e.inspect}"
|
|
|
|
if self.ignore_redis_failures?
|
|
|
|
ErrorReport.log_exception(:redis, e)
|
2013-07-26 03:34:09 +08:00
|
|
|
last_redis_failure[redis_name] = Time.now
|
2012-09-11 05:05:21 +08:00
|
|
|
failure_retval
|
|
|
|
else
|
|
|
|
raise
|
|
|
|
end
|
2012-01-17 05:28:01 +08:00
|
|
|
end
|
2011-07-07 05:43:19 +08:00
|
|
|
|
2012-01-17 05:28:01 +08:00
|
|
|
def self.patch
|
|
|
|
return if @redis_patched
|
|
|
|
Redis::Client.class_eval do
|
|
|
|
def process_with_conn_error(commands, *a, &b)
|
|
|
|
# try to return the type of value the command would expect, for some
|
|
|
|
# specific commands that we know can cause problems if we just return
|
|
|
|
# nil
|
|
|
|
#
|
|
|
|
# this isn't fool-proof, and in some situations it would probably
|
|
|
|
# actually be nicer to raise the exception and let the app handle it,
|
|
|
|
# but it's what we're going with for now
|
|
|
|
#
|
|
|
|
# for instance, Rails.cache.delete_matched will error out if the 'keys' command returns nil instead of []
|
|
|
|
last_command = commands.try(:last)
|
|
|
|
failure_val = case (last_command.respond_to?(:first) ? last_command.first : last_command).to_s
|
2014-01-14 04:16:41 +08:00
|
|
|
when 'keys', 'hmget'
|
2012-01-17 05:28:01 +08:00
|
|
|
[]
|
2014-01-14 04:16:41 +08:00
|
|
|
when 'del'
|
|
|
|
0
|
2012-01-17 05:28:01 +08:00
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2011-07-07 05:43:19 +08:00
|
|
|
|
2012-09-11 05:05:21 +08:00
|
|
|
Canvas::Redis.handle_redis_failure(failure_val, self.location) do
|
2012-01-17 05:28:01 +08:00
|
|
|
process_without_conn_error(commands, *a, &b)
|
|
|
|
end
|
2011-07-07 05:43:19 +08:00
|
|
|
end
|
2012-01-17 05:28:01 +08:00
|
|
|
alias_method_chain :process, :conn_error
|
2011-07-07 05:43:19 +08:00
|
|
|
end
|
2012-01-17 05:28:01 +08:00
|
|
|
@redis_patched = true
|
2011-07-07 05:43:19 +08:00
|
|
|
end
|
|
|
|
end
|