require 'sc-lock' require_relative '../errors/resource_locked_error' module ResourceManager extend ScLock SERVICE_NAME = "undef_service" if (defined? SERVICE_NAME).nil? NAMESPACE = "APL" + '-' + SERVICE_NAME # Resources module RES INVOICE = "Invoice" PAYMENT = "Payment" APPLICATION = "Application" end # Namespaces module NS EXCLUSIVE = "EXCLUSIVE" I2P = "I2P" I2I = "I2I" end # hold lock only for the 'block' and delete from database once done. def self.locked_block namespace, resource, params, wait_time=1000.0, retry_count=0, &block error = nil begin lock(namespace, resource, params, nil, 600, wait_time, retry_count, &block) rescue Exception => e error = e # delete lock and raise it again resource_lock = ScLock::Lock.find_by_resource_name_and_key("#{namespace}-#{resource}", params.sort.to_s) resource_lock.delete if resource_lock end raise error if error end def self.lock_resource(resource_name, lock_params, time_to_live=300, retry_count = 5, lock_wait = 1000, &block) begin return_value = nil lock(NAMESPACE, resource_name, lock_params, nil, time_to_live, lock_wait, retry_count) do return_value = yield end return_value rescue ScLock::LockNotAvailable raise ResourceLockedError.new end end # Tries to lock on a block of objects -> if 1 fails the whole block fails def self.lock_resources(resource_name, lock_param_array, time_to_live=300, retry_count=5, &block) if lock_param_array.empty? return elsif (lock_param_array.size == 1) ResourceManager.lock_resource(resource_name, lock_param_array.pop, time_to_live, retry_count) do yield if block end else ResourceManager.lock_resource(resource_name, lock_param_array.pop, time_to_live, retry_count) do lock_resources(resource_name, lock_param_array, time_to_live, retry_count, &block) end end end # Supposed to be used as a common method across all services for exclusive locking of accounting entities. # Supposed to be used for short duration hence lots of quick retries ( 100 each spanning 200ms) def self.ex_lock resource, type, client_ref_id, ttl=300, wait=200.0, retry_count=200, &block # resource is an accounting entity unless (RES.constants.map { |c| RES.const_get(c)}).include? resource raise InvalidDataError.new(:message => "Invalid resource locked: #{resource}") end # so that everything is in naagin case type = type.to_s.underscore client_ref_id = client_ref_id.to_s key = type + "-" + client_ref_id lock(NS::EXCLUSIVE, resource, { :key => key }, nil, ttl, wait, retry_count, &block) end # Recursively take locks on all entities before executing original block def self.ex_lock_all resource_type_crids, ttl=300, wait=200.0, retry_count=20, &block resource, type, client_ref_id = resource_type_crids.shift if resource_type_crids.size == 0 ex_lock resource, type, client_ref_id, ttl, wait, retry_count, &block return end new_block = Proc.new do ex_lock_all resource_type_crids, ttl, wait, retry_count, &block end ex_lock resource, type, client_ref_id, ttl, wait, retry_count, &new_block end # Tries to lock on possible entities given and # returns the list of entities for which it was able to acquire lock on def self.lock_for_each name_space, resource_name, entities, key_name=:id, expiry=nil reserved_entities = [] entities.each do |entity| begin expiry = 24 * 60 * 60 if expiry.nil? # Acquire lock for a day by default lock(name_space, resource_name, {resource_name.to_sym => entity[key_name]}, nil, expiry) reserved_entities << entity # TODO Need to check with platform team (This is hack to get off re-entrant locks) remove_caller_key("#{name_space}-#{resource_name}", {resource_name.to_sym => entity[key_name]}.sort.to_s) rescue ScLock::LockNotAvailable => le logger.info("Skipped entity : #{entity}, as unable to get lock on it, Error: #{le.message}") end end reserved_entities end # Releases lock for a block of entities locked def self.release_for_all name_space, resource_name, entities, key_name=:id entities.each do |entity| lock_on_resource = ScLock::Lock.find_by_resource_name_and_key("#{name_space}-#{resource_name}", {resource_name.to_sym => entity[key_name]}.sort.to_s) if lock_on_resource.blank? logger.error "Unable to release the lock, No lock found on resource #{entity[key_name]}" next end lock_on_resource.delete end end def self.update_expiry_date_for_all name_space, resource_name, entities, expiry_date_time, key_name=:id entities.each do |entity| lock_on_resource = ScLock::Lock.find_by_resource_name_and_key("#{name_space}-#{resource_name}", {resource_name.to_sym => entity[key_name]}.sort.to_s) if lock_on_resource.blank? logger.error "Unable to update the expiry date of lock, No lock found on resource #{entity[key_name]}" next end lock_on_resource.update_attribute(:lock_expiry_time, expiry_date_time) end end def self.remove_caller_key(resource_name, key) Thread.current[:locks].delete({resource_name => resource_name, :key => key}) if Thread.current[:locks] end end