<commit_before>require 'lock_and_cache/version'
require 'timeout'
require 'digest/md5'
require 'base64'
require 'zlib'
require 'redis'
require 'redlock'
require 'active_support'
require 'active_support/core_ext'

# Lock and cache methods using redis!
#
# I bet you're caching, but are you locking?
module LockAndCache
  DEFAULT_MAX_LOCK_WAIT = 60 * 60 * 24 # 1 day in seconds

  # @private
  LOCK_HEARTBEAT_EXPIRES = 2

  # @private
  LOCK_HEARTBEAT_PERIOD = 1

  class TimeoutWaitingForLock < StandardError; end

  # @param redis_connection [Redis] A redis connection to be used for lock and cached value storage
  def LockAndCache.storage=(redis_connection)
    raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
    @storage = redis_connection
    @lock_manager = Redlock::Client.new [redis_connection], retry_count: 1
  end

  # @return [Redis] The redis connection used for lock and cached value storage
  def LockAndCache.storage
    @storage
  end

  # Flush LockAndCache's storage
  #
  # @note If you are sharing a redis database, it will clear it...
  def LockAndCache.flush
    storage.flushdb
  end

  # @param seconds [Numeric] Maximum wait time to get a lock
  #
  # @note Can be overridden by putting `max_lock_wait:` in your call to `#lock_and_cache`
  def LockAndCache.max_lock_wait=(seconds)
    @max_lock_wait = seconds.to_f
  end

  # @private
  def LockAndCache.max_lock_wait
    @max_lock_wait || DEFAULT_MAX_LOCK_WAIT
  end

  # @private
  def LockAndCache.lock_manager
    @lock_manager
  end

  # @private
  class Key
    attr_reader :obj
    attr_reader :method_id

    def initialize(obj, method_id, parts)
      @obj = obj
      @method_id = method_id.to_sym
      @_parts = parts
    end

    # A (non-cryptographic) digest of the key parts for use as the cache key
    def digest
      @digest ||= ::Zlib::Deflate.deflate(::Marshal.dump(key), ::Zlib::BEST_SPEED)
    end

    # A (non-cryptographic) digest of the key parts for use as the lock key
    def lock_digest
      @lock_digest ||= 'lock/' + digest
    end

    # A human-readable representation of the key parts
    def key
      @key ||= [obj_class_name, method_id, parts]
    end

    alias debug key

    # An array of the parts we use for the key
    def parts
      @parts ||= @_parts.map do |v|
        case v
        when ::String, ::Symbol, ::Hash, ::Array
          v
        else
          v.respond_to?(:lock_and_cache_key) ? v.lock_and_cache_key : v.id<commit_msg>make sure keys are always the same length<commit_after>require 'lock_and_cache/version'
require 'timeout'
require 'digest/sha1'
require 'base64'
require 'redis'
require 'redlock'
require 'active_support'
require 'active_support/core_ext'

# Lock and cache methods using redis!
#
# I bet you're caching, but are you locking?
module LockAndCache
  DEFAULT_MAX_LOCK_WAIT = 60 * 60 * 24 # 1 day in seconds

  # @private
  LOCK_HEARTBEAT_EXPIRES = 2

  # @private
  LOCK_HEARTBEAT_PERIOD = 1

  class TimeoutWaitingForLock < StandardError; end

  # @param redis_connection [Redis] A redis connection to be used for lock and cached value storage
  def LockAndCache.storage=(redis_connection)
    raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
    @storage = redis_connection
    @lock_manager = Redlock::Client.new [redis_connection], retry_count: 1
  end

  # @return [Redis] The redis connection used for lock and cached value storage
  def LockAndCache.storage
    @storage
  end

  # Flush LockAndCache's storage
  #
  # @note If you are sharing a redis database, it will clear it...
  def LockAndCache.flush
    storage.flushdb
  end

  # @param seconds [Numeric] Maximum wait time to get a lock
  #
  # @note Can be overridden by putting `max_lock_wait:` in your call to `#lock_and_cache`
  def LockAndCache.max_lock_wait=(seconds)
    @max_lock_wait = seconds.to_f
  end

  # @private
  def LockAndCache.max_lock_wait
    @max_lock_wait || DEFAULT_MAX_LOCK_WAIT
  end

  # @private
  def LockAndCache.lock_manager
    @lock_manager
  end

  # @private
  class Key
    attr_reader :obj
    attr_reader :method_id

    def initialize(obj, method_id, parts)
      @obj = obj
      @method_id = method_id.to_sym
      @_parts = parts
    end

    # A (non-cryptographic) digest of the key parts for use as the cache key
    def digest
      @digest ||= ::Digest::SHA1.hexdigest ::Marshal.dump(key)
    end

    # A (non-cryptographic) digest of the key parts for use as the lock key
    def lock_digest
      @lock_digest ||= 'lock/' + digest
    end

    # A human-readable representation of the key parts
    def key
      @key ||= [obj_class_name, method_id, parts]
    end

    alias debug key

    # An array of the parts we use for the key
    def parts
      @parts ||= @_parts.map do |v|
        case v
        when ::String, ::Symbol, ::Hash, ::Array
          v
        else
          v.respond_to?(:lock_and_cache_key) ? v.lock_and_cache_key : v.id
{'fraction_non_alphanumeric': 0.08189134808853119, 'fraction_numerical': 0.005432595573440644, 'mean_word_length': 3.2127118644067796, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5792543', 'n_tokens_mistral': 1689, 'n_tokens_neox': 1603, 'n_words': 522}