module Suo module Client class Base DEFAULT_OPTIONS = { retry_count: 3, retry_delay: 0.01, stale_lock_expiration: 3600 }.freeze def initialize(options = {}) @options = self.class.merge_defaults(options).merge(_initialized: true) end def lock(key, resources = 1, options = {}) options = self.class.merge_defaults(@options.merge(options)) token = self.class.lock(key, resources, options) if token begin yield if block_given? ensure self.class.unlock(key, token, options) end true else false end end def locked?(key, resources = 1) self.class.locked?(key, resources, @options) end class << self def lock(key, resources = 1, options = {}) # rubocop:disable Lint/UnusedMethodArgument fail NotImplementedError end def locked?(key, resources = 1, options = {}) options = merge_defaults(options) client = options[:client] locks = deserialize_locks(client.get(key)) locks.size >= resources end def locks(key, options) options = merge_defaults(options) client = options[:client] locks = deserialize_locks(client.get(key)) locks.size end def refresh(key, acquisition_token, options = {}) # rubocop:disable Lint/UnusedMethodArgument fail NotImplementedError end def unlock(key, acquisition_token, options = {}) # rubocop:disable Lint/UnusedMethodArgument fail NotImplementedError end def clear(key, options = {}) # rubocop:disable Lint/UnusedMethodArgument fail NotImplementedError end def merge_defaults(options = {}) unless options[:_initialized] options = self::DEFAULT_OPTIONS.merge(options) fail "Client required" unless options[:client] end if options[:retry_timeout] options[:retry_count] = (options[:retry_timeout] / options[:retry_delay].to_f).floor end options end private def serialize_locks(locks) locks.map { |time, token| [time.to_f, token].join(":") }.join(",") end def deserialize_locks(str) str.split(",").map do |s| time, token = s.split(":", 2) [Time.at(time.to_f), token] end end def clear_expired_locks(locks, options) expired = Time.now - options[:stale_lock_expiration] locks.reject { |time, _| time < expired } end def add_lock(locks, token) locks << [Time.now.to_f, token] end def remove_lock(locks, acquisition_token) lock = locks.find { |_, token| token == acquisition_token } locks.delete(lock) end def refresh_lock(locks, acquisition_token) remove_lock(locks, acquisition_token) add_lock(locks, token) end end end end end