lib/services/modules/uniqueness_checker.rb in services-0.2.8 vs lib/services/modules/uniqueness_checker.rb in services-0.2.9
- old
+ new
@@ -4,45 +4,90 @@
KEY_PREFIX = %w(
services
uniqueness
).join(':')
+ ON_ERROR = %i(
+ fail
+ ignore
+ reschedule
+ )
+
+ MAX_RETRIES = 10
+ ONE_HOUR = 60 * 60
+
def self.prepended(mod)
mod.const_set :NotUniqueError, Class.new(mod::Error)
end
- def check_uniqueness!(*args)
- if args.empty?
- raise 'Could not find uniqueness args' unless defined?(@uniqueness_args)
- args = @uniqueness_args
- end
- new_uniqueness_key = uniqueness_key(args)
- if similar_service_id = Services.configuration.redis.get(new_uniqueness_key)
- raise self.class::NotUniqueError, "Service #{self.class} with uniqueness args #{args} is not unique, a similar service is already running: #{similar_service_id}"
+ def check_uniqueness!(*args, on_error: :fail)
+ raise "on_error must be one of #{ON_ERROR.join(', ')}, but was #{on_error}" unless ON_ERROR.include?(on_error.to_sym)
+ raise 'Service args not found.' if @service_args.nil?
+ @uniqueness_args = args.empty? ? @service_args : args
+ new_uniqueness_key = uniqueness_key(@uniqueness_args)
+ raise "A uniqueness key with args #{@uniqueness_args.inspect} already exists." if @uniqueness_keys && @uniqueness_keys.include?(new_uniqueness_key)
+ if @similar_service_id = Services.configuration.redis.get(new_uniqueness_key)
+ case on_error.to_sym
+ when :ignore
+ false
+ when :fail
+ raise_non_unique_error
+ when :reschedule
+ error_count = (Services.configuration.redis.get(error_count_key) || 0).to_i
+ if error_count >= MAX_RETRIES
+ raise_non_unique_error
+ else
+ error_count += 1
+ self.class.perform_in retry_delay(error_count), @service_args
+ Services.configuration.redis.setex error_count_key, retry_delay(error_count) + ONE_HOUR, error_count
+ false
+ end
+ end
else
@uniqueness_keys ||= []
- raise "A uniqueness key with args #{args.inspect} already exists." if @uniqueness_keys.include?(new_uniqueness_key)
@uniqueness_keys << new_uniqueness_key
- Services.configuration.redis.setex new_uniqueness_key, 60 * 60, @id
+ Services.configuration.redis.setex new_uniqueness_key, ONE_HOUR, @id
+ true
end
end
def call(*args)
- @uniqueness_args = args
+ @service_args = args
super
ensure
Services.configuration.redis.del @uniqueness_keys unless @uniqueness_keys.nil? || @uniqueness_keys.empty?
+ Services.configuration.redis.del error_count_key
end
private
+ def raise_non_unique_error(retried = false)
+ message = "Service #{self.class} #{@id} with uniqueness args #{@uniqueness_args} is not unique, a similar service is already running: #{@similar_service_id}."
+ message << " The service has been retried #{MAX_RETRIES} times."
+ raise self.class::NotUniqueError, message
+ end
+
def uniqueness_key(args)
[
KEY_PREFIX,
self.class.to_s
].tap do |key|
key << Digest::MD5.hexdigest(args.to_s) unless args.empty?
end.join(':')
+ end
+
+ def error_count_key
+ [
+ KEY_PREFIX,
+ 'errors',
+ self.class.to_s
+ ].tap do |key|
+ key << Digest::MD5.hexdigest(@service_args.to_s) unless @service_args.empty?
+ end.join(':')
+ end
+
+ def retry_delay(error_count)
+ (error_count ** 3) + 5
end
end
end
end