├── .ruby-version ├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ ├── typecheck-static.yml │ ├── typecheck-runtime.yml │ └── tests.yml ├── .rspec ├── sig ├── redis_queued_locks │ ├── version.rbs │ ├── acquier.rbs │ ├── data.rbs │ ├── swarm │ │ ├── swarm_element.rbs │ │ ├── acquirers.rbs │ │ ├── probe_hosts.rbs │ │ ├── flush_zombies.rbs │ │ ├── supervisor.rbs │ │ ├── redis_client_builder.rbs │ │ ├── zombie_info.rbs │ │ └── swarm_element │ │ │ ├── isolated.rbs │ │ │ └── threaded.rbs │ ├── instrument │ │ ├── void_notifier.rbs │ │ ├── active_support.rbs │ │ └── sampler.rbs │ ├── acquirer │ │ ├── is_locked.rbs │ │ ├── is_queued.rbs │ │ ├── keys.rbs │ │ ├── acquire_lock │ │ │ ├── delay_execution.rbs │ │ │ ├── with_acq_timeout.rbs │ │ │ ├── dequeue_from_lock_queue │ │ │ │ └── log_visitor.rbs │ │ │ ├── dequeue_from_lock_queue.rbs │ │ │ ├── yield_with_expire │ │ │ │ └── log_visitor.rbs │ │ │ ├── yield_expire.rbs │ │ │ ├── try_to_lock.rbs │ │ │ ├── instr_visitor.rbs │ │ │ ├── log_visitor.rbs │ │ │ └── try_to_lock │ │ │ │ └── log_visitor.rbs │ │ ├── lock_info.rbs │ │ ├── queue_info.rbs │ │ ├── queues.rbs │ │ ├── locks.rbs │ │ ├── extend_lock_ttl.rbs │ │ ├── clear_dead_requests.rbs │ │ ├── release_all_locks.rbs │ │ ├── release_lock.rbs │ │ ├── release_locks_of.rbs │ │ └── acquire_lock.rbs │ ├── logging │ │ ├── sampler.rbs │ │ └── void_logger.rbs │ ├── utilities │ │ └── lock.rbs │ ├── debugger │ │ └── interface.rbs │ ├── debugger.rbs │ ├── utilities.rbs │ ├── logging.rbs │ ├── instrument.rbs │ ├── config │ │ └── dsl.rbs │ ├── config.rbs │ ├── errors.rbs │ ├── resource.rbs │ └── swarm.rbs ├── vendor │ ├── semantic_logger.rbs │ ├── active_support.rbs │ └── redis_client.rbs ├── manifest.yml └── redis_queued_locks.rbs ├── bin ├── setup └── console ├── lib ├── redis_queued_locks │ ├── data.rb │ ├── version.rb │ ├── swarm │ │ ├── swarm_element.rb │ │ ├── acquirers.rb │ │ ├── redis_client_builder.rb │ │ ├── probe_hosts.rb │ │ ├── supervisor.rb │ │ ├── flush_zombies.rb │ │ ├── zombie_info.rb │ │ └── swarm_element │ │ │ └── isolated.rb │ ├── instrument │ │ ├── void_notifier.rb │ │ ├── active_support.rb │ │ └── sampler.rb │ ├── utilities │ │ └── lock.rb │ ├── acquirer │ │ ├── is_locked.rb │ │ ├── is_queued.rb │ │ ├── acquire_lock │ │ │ ├── delay_execution.rb │ │ │ ├── dequeue_from_lock_queue │ │ │ │ └── log_visitor.rb │ │ │ ├── dequeue_from_lock_queue.rb │ │ │ ├── yield_expire │ │ │ │ └── log_visitor.rb │ │ │ ├── with_acq_timeout.rb │ │ │ ├── instr_visitor.rb │ │ │ ├── yield_expire.rb │ │ │ └── log_visitor.rb │ │ ├── keys.rb │ │ ├── queue_info.rb │ │ ├── extend_lock_ttl.rb │ │ ├── clear_dead_requests.rb │ │ ├── queues.rb │ │ ├── lock_info.rb │ │ ├── locks.rb │ │ ├── release_all_locks.rb │ │ └── release_lock.rb │ ├── debugger │ │ └── interface.rb │ ├── acquirer.rb │ ├── logging │ │ ├── sampler.rb │ │ └── void_logger.rb │ ├── debugger.rb │ ├── errors.rb │ ├── config │ │ └── dsl.rb │ ├── utilities.rb │ ├── instrument.rb │ ├── logging.rb │ ├── swarm.rb │ └── resource.rb └── redis_queued_locks.rb ├── .gitignore ├── Steepfile ├── github_ci ├── ruby3.3.gemfile └── ruby3.3.gemfile.lock ├── rbs_collection.yaml ├── Gemfile ├── rbs_collection.lock.yaml ├── spec ├── setup_simplecov.rb └── spec_helper.rb ├── Rakefile ├── .rubocop.yml ├── LICENSE.txt ├── redis_queued_locks.gemspec ├── CODE_OF_CONDUCT.md └── Gemfile.lock /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.5 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 0exp 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format progress 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/version.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | VERSION: String 3 | end 4 | -------------------------------------------------------------------------------- /sig/vendor/semantic_logger.rbs: -------------------------------------------------------------------------------- 1 | module SemanticLogger 2 | class Logger 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquier.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Acquirer 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/data.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | class Data < Hash[untyped,untyped] 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /sig/manifest.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - timeout 3 | - redis-client 4 | - securerandom 5 | - logger 6 | - monitor 7 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 0.0.18 5 | class RedisQueuedLocks::Data < Hash 6 | end 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'redis_queued_locks' 6 | require 'pry' 7 | 8 | Pry.start 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | .rspec_status 10 | *.gem 11 | /.gem_rbs_collection/ 12 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/swarm_element.rbs: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | module SwarmElement 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedisQueuedLocks 4 | # @return [String] 5 | # 6 | # @api public 7 | # @since 0.0.1 8 | # @version 1.15.1 9 | VERSION = '1.15.1' 10 | end 11 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/instrument/void_notifier.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Instrument 3 | module VoidNotifier 4 | def self.notify: (String event, ?Hash[untyped,untyped] payload) -> void 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/instrument/active_support.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Instrument 3 | module ActiveSupport 4 | def self.notify: (String event, ?Hash[untyped,untyped] payload) -> void 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/is_locked.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module IsLocked 6 | def self.locked?: (RC::client redis_client, String lock_name) -> bool 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/is_queued.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module IsQueued 6 | def self.queued?: (RC::client redis_client, String lock_name) -> bool 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/swarm/swarm_element.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.9.0 5 | module RedisQueuedLocks::Swarm::SwarmElement 6 | require_relative 'swarm_element/isolated' 7 | require_relative 'swarm_element/threaded' 8 | end 9 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/logging/sampler.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Logging 3 | module Sampler 4 | SAMPLING_PERCENT_RANGE: Range[Integer] 5 | 6 | def self.sampling_happened?: (Integer sampling_percent) -> bool 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/utilities/lock.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Utilities 3 | class Lock 4 | @lock: Monitor 5 | 6 | def initialize: () -> void 7 | def synchronize: () { (?) -> untyped } -> untyped 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/instrument/sampler.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Instrument 3 | module Sampler 4 | SAMPLING_PERCENT_RANGE: Range[Integer] 5 | 6 | def self.sampling_happened?: (Integer sampling_percent) -> bool 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/debugger/interface.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Debugger 3 | module Interface 4 | def debug: (::String message) -> void 5 | def enable_debugger!: () -> void 6 | def disable_debugger!: () -> void 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/keys.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module Keys 6 | type keyList = Set[String] 7 | def self.keys: (RC::client redis_client, scan_size: Integer) -> keyList 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /sig/vendor/active_support.rbs: -------------------------------------------------------------------------------- 1 | module ActiveSupport 2 | module Notifications 3 | def self.instrument: (String event, Hash[untyped,untyped] payload) -> untyped 4 | end 5 | 6 | class BroadcastLogger 7 | def debug: (*untyped, **untyped) { (?) -> untyped } -> untyped 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/delay_execution.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Acquirer 3 | module AcquireLock 4 | module DelayExecution 5 | def delay_execution: (Integer retry_delay, Integer retry_jitter) -> Integer 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Steepfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | target :lib do 4 | signature 'sig' 5 | 6 | check 'lib' 7 | ignore 'spec' 8 | 9 | library 'timeout' 10 | library 'securerandom' 11 | library 'logger' 12 | library 'monitor' 13 | 14 | configure_code_diagnostics(Steep::Diagnostic::Ruby.strict) 15 | end 16 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/lock_info.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module LockInfo 6 | type lockInfo = Hash[String,String|Float|Integer] 7 | def self.lock_info: (RC::client redis_client, String lock_name) -> lockInfo? 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/acquirers.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | module Acquirers 6 | type acquirersList = Hash[String,{ zombie: bool, last_probe_time: Time, last_probe_score: Float }] 7 | def self.acquirers: (RC::client redis_client, Integer zombie_ttl) -> acquirersList 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/debugger.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Debugger 3 | DEBUG_ENABLED_METHOD: String 4 | DEBUG_DISABLED_MEHTOD: String 5 | 6 | self.@enabled: bool 7 | 8 | def self.enable!: () -> void 9 | def self.disable!: () -> void 10 | def self.enabled?: () -> bool 11 | def self.debug: (String message) -> void 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /github_ci/ruby3.3.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec path: '..' 6 | 7 | gem 'activesupport' 8 | gem 'armitage-rubocop' 9 | gem 'bundler' 10 | gem 'pry' 11 | gem 'rake' 12 | gem 'rbs' 13 | gem 'rspec' 14 | gem 'rspec-retry' # NOTE: temporary decision for non-refactored tests 15 | gem 'simplecov' 16 | gem 'simplecov-lcov' 17 | gem 'steep' 18 | -------------------------------------------------------------------------------- /rbs_collection.yaml: -------------------------------------------------------------------------------- 1 | sources: 2 | - type: git 3 | name: ruby/gem_rbs_collection 4 | remote: https://github.com/ruby/gem_rbs_collection.git 5 | revision: main 6 | repo_dir: gems 7 | 8 | path: .gem_rbs_collection 9 | 10 | gems: 11 | - name: redis-client 12 | - name: securerandom 13 | - name: timeout 14 | - name: redis-client 15 | - name: securerandom 16 | - name: logger 17 | - name: monitor 18 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/instrument/void_notifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Instrument::VoidNotifier 6 | class << self 7 | # @param event [String] 8 | # @param payload [Hash] 9 | # @return [void] 10 | # 11 | # @api public 12 | # @since 1.0.0 13 | def notify(event, payload = {}); end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/queue_info.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module QueueInfo 6 | type queueInfo = { 7 | 'lock_queue' => String, 8 | 'queue' => Array[{ 'acq_id' => String, 'score' => Integer|Float }] 9 | } 10 | def self.queue_info: (RC::client redis_client, String lock_name) -> queueInfo? 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sig/redis_queued_locks.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | interface _Loggable 3 | def debug: (?untyped? progname) { (?) -> untyped } -> untyped 4 | end 5 | 6 | interface _Instrumentable 7 | def notify: (String event, ?Hash[untyped,untyped] payload) -> void 8 | end 9 | 10 | type loggerObj = Object & _Loggable 11 | type instrObj = Object & _Instrumentable 12 | 13 | extend ::RedisQueuedLocks::Debugger::Interface 14 | end 15 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/probe_hosts.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | class ProbeHosts < SwarmElement::Threaded 6 | type livingHosts = { ok: bool, result: Hash[String,Float] } 7 | def self.probe_hosts: (RC::client redis_client, String uniq_identity) -> livingHosts 8 | 9 | def enabled?: () -> bool 10 | def spawn_main_loop!: () -> Thread 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/utilities.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Utilities 3 | RACTOR_LIVENESS_PATTERN: Regexp 4 | RACTOR_STATUS_PATTERN: Regexp 5 | 6 | def self?.run_non_critical: () { (?) -> untyped } -> untyped? 7 | def self?.ractor_status: (Ractor ractor) -> String 8 | def self?.ractor_alive?: (Ractor ractor) -> bool 9 | def self?.thread_state: (Thread thread) -> String 10 | def self?.clock_gettime: () -> Integer 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/instrument/active_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Instrument::ActiveSupport 6 | class << self 7 | # @param event [String] 8 | # @param payload [Hash] 9 | # @return [void] 10 | # 11 | # @api public 12 | # @since 1.0.0 13 | def notify(event, payload = {}) 14 | ::ActiveSupport::Notifications.instrument(event, payload) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/utilities/lock.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.9.0 5 | class RedisQueuedLocks::Utilities::Lock 6 | # @return [void] 7 | # 8 | # @api private 9 | # @since 1.9.0 10 | # @version 1.13.0 11 | def initialize 12 | @lock = ::Monitor.new 13 | end 14 | 15 | # @param block [Block] 16 | # @return [Any] 17 | # 18 | # @api private 19 | # @since 1.9.0 20 | # @version 1.13.0 21 | def synchronize(&block) 22 | @lock.synchronize(&block) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer/is_locked.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Acquirer::IsLocked 6 | class << self 7 | # @param redis_client [RedisClient] 8 | # @param lock_name [String] 9 | # @return [Boolean] 10 | # 11 | # @api private 12 | # @since 1.0.0 13 | def locked?(redis_client, lock_name) 14 | lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name) 15 | redis_client.call('EXISTS', lock_key) == 1 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/logging.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | 3 | module RedisQueuedLocks 4 | module Logging 5 | interface _Sampler 6 | def sampling_happened?: (Integer sampling_percent) -> bool 7 | end 8 | 9 | type samplerObj = Object & _Sampler 10 | 11 | def self.should_log?: (bool log_sampling_enabled, bool log_sample_this, Integer log_sampling_percent, samplerObj log_sampler) -> bool 12 | def self.valid_sampler?: (untyped sampler) -> bool 13 | def self.valid_interface?: (untyped logger) -> bool 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer/is_queued.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Acquirer::IsQueued 6 | class << self 7 | # @param redis_client [RedisClient] 8 | # @param lock_name [String] 9 | # @return [Boolean] 10 | # 11 | # @api private 12 | # @since 1.0.0 13 | def queued?(redis_client, lock_name) 14 | lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name) 15 | redis_client.call('EXISTS', lock_key_queue) == 1 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module AcquireLock 6 | module WithAcqTimeout 7 | def with_acq_timeout: ( 8 | RC::client redis, 9 | Integer? timeout, 10 | String lock_key, 11 | String lock_name, 12 | bool raise_errors, 13 | bool detailed_acq_timeout_error, 14 | ?on_timeout: Proc|nil 15 | ) { (?) -> untyped } -> untyped 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/flush_zombies.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | class FlushZombies < SwarmElement::Isolated 6 | type flushedZombies = { ok: bool, deleted_zombie_hosts: Set[String], deleted_zombie_acquirers: Set[String], deleted_zombie_locks: Set[String] } 7 | def self.flush_zombies: (RC::client redis_client, Integer zombie_ttl, Integer lock_scan_size, Integer queue_scan_size) -> flushedZombies 8 | 9 | def enabled?: () -> bool 10 | def swarm!: () -> void 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/instrument.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | 3 | module RedisQueuedLocks 4 | module Instrument 5 | interface _Sampler 6 | def sampling_happened?: (Integer sampling_percent) -> bool 7 | end 8 | 9 | type samplerObj = Object & _Sampler 10 | 11 | def self.should_instrument?: (bool instr_sampling_enabled, bool instr_sample_this, Integer instr_sampling_percent, samplerObj instr_sampler) -> bool 12 | def self.valid_sampler?: (untyped sampler) -> bool 13 | def self.valid_interface?: (untyped instrumenter) -> bool 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | # NOTE: dev dependencies 8 | gem 'activesupport', require: false 9 | gem 'armitage-rubocop', require: false 10 | gem 'bundler', require: false 11 | gem 'pry', require: false 12 | gem 'pry-doc', require: false 13 | gem 'rake', require: false 14 | gem 'rbs', require: false 15 | gem 'reline', require: false 16 | gem 'rspec', require: false 17 | gem 'rspec-retry', require: false # NOTE: temporary decision for non-refactored tests 18 | gem 'simplecov', require: false 19 | gem 'simplecov-lcov', require: false 20 | gem 'steep', require: false 21 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module AcquireLock 6 | module DequeueFromLockQueue 7 | module LogVisitor 8 | def self.dequeue_from_lock_queue: ( 9 | RQL::loggerObj logger, 10 | bool log_sampled, 11 | String lock_key, 12 | Integer queue_ttl, 13 | String acquirer_id, 14 | String host_id, 15 | Symbol access_strategy 16 | ) -> void 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/queues.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module Queues 6 | type queueList = Set[String] 7 | type queuesInfo = Set[{ queue: String, requests: Array[Hash[String,Float]] }] 8 | type queues = queueList | queuesInfo 9 | 10 | def self.queues: (RC::client redis_client, scan_size: Integer, with_info: bool) -> queues 11 | 12 | private def self.scan_queues: (RC::client redis_client, Integer scan_size) -> queueList 13 | private def self.extract_queues_info: (RC::client redis_client, Set[String] lock_queues) -> queuesInfo 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/locks.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module Locks 6 | type lockList = Set[String] 7 | type locksInfo = Set[{ lock: String, status: Symbol, info: Hash[String,String|Float|Integer] }] 8 | type locks = lockList | locksInfo 9 | 10 | def self.locks: (RC::client redis_client, scan_size: Integer, with_info: bool) -> locks 11 | 12 | private def self.scan_locks: (RC::client redis_client, Integer scan_size) -> lockList 13 | private def self.extract_locks_info: (RC::client redis_client, Set[::String] lock_keys) -> locksInfo 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer/acquire_lock/delay_execution.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Acquirer::AcquireLock::DelayExecution 6 | # Sleep with random time-shifting (it is necessary for empty lock-acquirement time slots). 7 | # 8 | # @param retry_delay [Integer] In milliseconds 9 | # @param retry_jitter [Integer] In milliseconds 10 | # @return [Integer] Slept seconds 11 | # 12 | # @api private 13 | # @since 1.0.0 14 | def delay_execution(retry_delay, retry_jitter) 15 | delay = (retry_delay + ::Kernel.rand(retry_jitter)).to_f / 1_000 16 | ::Kernel.sleep(delay) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /rbs_collection.lock.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | path: ".gem_rbs_collection" 3 | gems: 4 | - name: connection_pool 5 | version: '2.4' 6 | source: 7 | type: git 8 | name: ruby/gem_rbs_collection 9 | revision: 52a7ef8a9328fb2413e3b42dd8ee37deb703a940 10 | remote: https://github.com/ruby/gem_rbs_collection.git 11 | repo_dir: gems 12 | - name: logger 13 | version: '0' 14 | source: 15 | type: stdlib 16 | - name: monitor 17 | version: '0' 18 | source: 19 | type: stdlib 20 | - name: securerandom 21 | version: '0' 22 | source: 23 | type: stdlib 24 | - name: timeout 25 | version: '0' 26 | source: 27 | type: stdlib 28 | gemfile_lock_path: Gemfile.lock 29 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/debugger/interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Debugger::Interface 6 | # @param message [String] 7 | # @return [void] 8 | # 9 | # @api private 10 | # @since 1.0.0 11 | def debug(message) 12 | RedisQueuedLocks::Debugger.debug(message) 13 | end 14 | 15 | # @return [void] 16 | # 17 | # @api private 18 | # @since 1.0.0 19 | def enable_debugger! 20 | RedisQueuedLocks::Debugger.enable! 21 | end 22 | 23 | # @return [void] 24 | # 25 | # @api private 26 | # @since 1.0.0 27 | def disable_debugger! 28 | RedisQueuedLocks::Debugger.disable! 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/setup_simplecov.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov' 4 | require 'simplecov-lcov' 5 | 6 | SimpleCov::Formatter::LcovFormatter.config do |config| 7 | config.report_with_single_file = true 8 | config.output_directory = 'coverage' 9 | config.lcov_file_name = 'lcov.info' 10 | end 11 | 12 | SimpleCov.configure do 13 | enable_coverage :line 14 | enable_coverage :branch 15 | primary_coverage :line 16 | # TODO: minimum_coverage 100 # (temporary disabled for non-refactored tests) 17 | 18 | formatter SimpleCov::Formatter::MultiFormatter.new([ 19 | SimpleCov::Formatter::HTMLFormatter, 20 | SimpleCov::Formatter::LcovFormatter 21 | ]) 22 | add_filter '/spec/' 23 | end 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint (Rubocop/RBS) 2 | on: [push] 3 | jobs: 4 | lint: 5 | name: 'Lint (os: ${{ matrix.os }}) (ruby@${{ matrix.ruby_version }})' 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | ruby_version: ['3.4'] 10 | os: [ubuntu-latest] 11 | runs-on: ${{ matrix.os }} 12 | env: 13 | BUNDLE_FORCE_RUBY_PLATFORM: 1 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: supercharge/redis-github-action@1.7.0 17 | - uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: ${{ matrix.ruby_version }} 20 | bundler-cache: true 21 | - name: (Linter) Rubocop+RBS 22 | run: bundle exec rake rubocop 23 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer/keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Acquirer::Keys 6 | class << self 7 | # @param redis_client [RedisClient] 8 | # @option scan_size [Integer] 9 | # @return [Set] 10 | # 11 | # @api private 12 | # @since 1.0.0 13 | def keys(redis_client, scan_size:) 14 | Set.new.tap do |keyset| 15 | # @type var keyset: Set[String] 16 | redis_client.scan( 17 | 'MATCH', 18 | RedisQueuedLocks::Resource::KEY_PATTERN, 19 | count: scan_size 20 | ) do |key| 21 | keyset.add(key) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Acquirer 6 | require_relative 'acquirer/acquire_lock' 7 | require_relative 'acquirer/release_lock' 8 | require_relative 'acquirer/release_all_locks' 9 | require_relative 'acquirer/release_locks_of' 10 | require_relative 'acquirer/is_locked' 11 | require_relative 'acquirer/is_queued' 12 | require_relative 'acquirer/lock_info' 13 | require_relative 'acquirer/queue_info' 14 | require_relative 'acquirer/locks' 15 | require_relative 'acquirer/queues' 16 | require_relative 'acquirer/keys' 17 | require_relative 'acquirer/extend_lock_ttl' 18 | require_relative 'acquirer/clear_dead_requests' 19 | end 20 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/supervisor.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | class Supervisor 6 | include RQL::Utilities 7 | 8 | @rql_client: RQL::Client 9 | @visor: Thread? 10 | @observable: Proc? 11 | 12 | attr_reader rql_client: RQL::Client 13 | attr_reader visor: Thread? 14 | attr_reader observable: Proc? 15 | 16 | def initialize: (RQL::Client rql_client) -> void 17 | 18 | type supervisorStatus = { running: bool, state: String, observable: String } 19 | def status: () -> supervisorStatus 20 | 21 | def observe!: () { (?) -> untyped } -> void 22 | def running?: () -> bool 23 | def stop!: () -> void 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/redis_client_builder.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | module RedisClientBuilder 6 | def self.build: ( 7 | ?pooled: bool, 8 | ?sentinel: bool, 9 | ?config: Hash[untyped,untyped], 10 | ?pool_config: Hash[untyped,untyped] 11 | ) -> RC::client 12 | 13 | private def self.sentinel_config: (Hash[Symbol,untyped] config) -> RC::SentinelConfig 14 | private def self.non_sentinel_config: (Hash[Symbol,untyped] config) -> RC::Config 15 | private def self.pooled_client: (RC::config redis_config, Hash[Symbol,untyped] pool_config) -> RedisClient::Pooled 16 | private def self.non_pooled_client: (RC::config redis_config) -> RedisClient 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/logging/void_logger.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Logging 3 | module VoidLogger 4 | def self.warn: (?untyped? progname) { (?) -> untyped } -> nil 5 | def self.unknown: (?untyped? progname) { (?) -> untyped } -> nil 6 | def self.log: (?untyped? progname) { (?) -> untyped } -> nil 7 | def self.info: (?untyped? progname) { (?) -> untyped } -> nil 8 | def self.error: (?untyped? progname) { (?) -> untyped } -> nil 9 | def self.fatal: (?untyped? progname) { (?) -> untyped } -> nil 10 | def self.debug: (?untyped? progname) { (?) -> untyped } -> nil 11 | def self.add: (?untyped? severity, ?untyped? message, ?untyped? progname) { (?) -> untyped } -> nil 12 | def self.<<: (untyped message) -> nil 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'setup_simplecov' 4 | 5 | SimpleCov.start 6 | 7 | require 'rspec/retry' 8 | require 'pry' 9 | require 'redis_queued_locks' 10 | 11 | RSpec.configure do |config| 12 | # NOTE: (github-ci) (rspec-retry) temporary decision for non-refactored tests 13 | config.verbose_retry = true # (rspec-retry) 14 | config.display_try_failure_messages = true # (rspec-retry) 15 | config.default_retry_count = 5 # (rsec-retry) 16 | 17 | Kernel.srand config.seed 18 | config.order = :random 19 | config.filter_run_when_matching :focus 20 | config.shared_context_metadata_behavior = :apply_to_host_groups 21 | config.disable_monkey_patching! 22 | config.expect_with(:rspec) { |c| c.syntax = :expect } 23 | Thread.abort_on_exception = true 24 | end 25 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module AcquireLock 7 | module DequeueFromLockQueue 8 | def dequeue_from_lock_queue: ( 9 | RC::client redis, 10 | RQL::loggerObj logger, 11 | String lock_key, 12 | Symbol read_write_mode, 13 | String lock_key_queue, 14 | String read_lock_key_queue, 15 | String write_lock_key_queue, 16 | Integer queue_ttl, 17 | String acquirer_id, 18 | String host_id, 19 | Symbol access_strategy, 20 | bool log_sampled, 21 | bool instr_sampled 22 | ) -> { ok: bool, result: Integer } 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | require 'steep/rake_task' 6 | require 'rubocop' 7 | require 'rubocop/rake_task' 8 | require 'rubocop-performance' 9 | require 'rubocop-rspec' 10 | require 'rubocop-rake' 11 | require 'rubocop-on-rbs' 12 | require 'rubocop-thread_safety' 13 | 14 | RuboCop::RakeTask.new(:rubocop) do |t| 15 | config_path = File.expand_path(File.join('.rubocop.yml'), __dir__) 16 | t.options = [ 17 | '--config', config_path, 18 | '--plugin', 'rubocop-rspec', 19 | '--plugin', 'rubocop-performance', 20 | '--plugin', 'rubocop-rake', 21 | '--plugin', 'rubocop-on-rbs', 22 | '--plugin', 'rubocop-thread_safety' 23 | ] 24 | end 25 | 26 | RSpec::Core::RakeTask.new(:rspec) 27 | Steep::RakeTask.new(:steep) 28 | 29 | task default: :rspec 30 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/config/dsl.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | class Config 3 | module DSL 4 | type configSetters = Hash[String,Proc] 5 | type configValidators = Hash[String,Proc] 6 | 7 | module ClassMethods 8 | @config_setters: configSetters 9 | @config_validators: configValidators 10 | 11 | def config_setters: () -> configSetters 12 | def config_validators: () -> configValidators 13 | 14 | def validate: (String config_key) { (String) -> bool } -> void 15 | def setting: (String config_key, untyped config_value) -> void 16 | end 17 | 18 | module InstanceMethods 19 | def config_setters: () -> configSetters 20 | def config_validators: () -> configValidators 21 | end 22 | 23 | def self.included: (Class child_klass) -> void 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /.github/workflows/typecheck-static.yml: -------------------------------------------------------------------------------- 1 | name: TypeCheck (Static/Steep) 2 | on: [push] 3 | jobs: 4 | typecheck-static: 5 | name: 'TypeCheck (Static/Steep) (os: ${{ matrix.os }}) (ruby@${{ matrix.ruby_version }})' 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | ruby_version: ['3.4'] 10 | os: [ubuntu-latest] 11 | runs-on: ${{ matrix.os }} 12 | env: 13 | BUNDLE_RETRY: 3 14 | BUNDLE_JOBS: 4 15 | BUNDLE_FORCE_RUBY_PLATFORM: 1 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: supercharge/redis-github-action@1.7.0 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby_version }} 22 | bundler-cache: true 23 | - name: TypeCheck (Static) (Steep) 24 | run: bundle exec rbs collection install && bundle exec rake steep:check -j 10 25 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/config.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | class Config 3 | include RedisQueuedLocks::Config::DSL 4 | include RedisQueuedLocks::Config::DSL::InstanceMethods 5 | extend RedisQueuedLocks::Config::DSL::ClassMethods 6 | 7 | type configState = Hash[String,untyped] 8 | 9 | @config_state: configState 10 | attr_reader config_state: configState 11 | 12 | def initialize: () ?{ (RedisQueuedLocks::Config) -> void } -> void 13 | def configure: () ?{ (RedisQueuedLocks::Config) -> void } -> void 14 | def []: (String config_key) -> untyped 15 | def []=: (String config_key, untyped config_value) -> void 16 | def slice: (String config_key_pattern) -> configState 17 | 18 | private 19 | 20 | def prevent_key__non_existent: (String config_key) -> void 21 | def prevent_key__invalid_type: (String config_key, untyped config_value) -> void 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | armitage-rubocop: 3 | - lib/rubocop.general.yml 4 | - lib/rubocop.rake.yml 5 | - lib/rubocop.rspec.yml 6 | - lib/rubocop.rbs.yml 7 | 8 | AllCops: 9 | TargetRubyVersion: 3.3 10 | NewCops: enable 11 | Include: 12 | - lib/**/*.rb 13 | - spec/**/*.rb 14 | - sig/**/*.rbs 15 | - Gemfile 16 | - Rakefile 17 | - redis_queued_locks.gemspec 18 | - bin/console 19 | 20 | Metrics/ParameterLists: 21 | Enabled: false 22 | 23 | Metrics/BlockLength: 24 | Enabled: false 25 | 26 | Lint/EmptyBlock: 27 | Exclude: 28 | - spec/**/*.rb 29 | 30 | Gemspec/DevelopmentDependencies: 31 | EnforcedStyle: Gemfile 32 | 33 | Metrics/AbcSize: 34 | Enabled: false 35 | 36 | Metrics/CyclomaticComplexity: 37 | Enabled: false 38 | 39 | Metrics/PerceivedComplexity: 40 | Enabled: false 41 | 42 | Layout/LineEndStringConcatenationIndentation: 43 | Enabled: false 44 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module ExtendLockTTL 7 | EXTEND_LOCK_PTTL: String 8 | 9 | type extendResult = { ok: bool, result: Symbol } 10 | def self.extend_lock_ttl: ( 11 | RC::client redis_client, 12 | String lock_name, 13 | Integer milliseconds, 14 | RQL::loggerObj logger, 15 | RQL::instrObj instrumenter, 16 | untyped? instrument, 17 | bool log_samling_enabled, 18 | Integer log_sampling_percent, 19 | RQL::Logging::samplerObj log_sampler, 20 | bool sample_this, 21 | bool instr_sampling_enabled, 22 | Integer instr_sampling_percent, 23 | RQL::Instrument::samplerObj instr_sampler, 24 | bool instr_sample_this 25 | ) -> extendResult 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/redis_queued_locks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'timeout' 4 | require 'redis-client' 5 | require 'securerandom' 6 | require 'logger' 7 | 8 | # @api public 9 | # @since 1.0.0 10 | module RedisQueuedLocks 11 | require_relative 'redis_queued_locks/version' 12 | require_relative 'redis_queued_locks/errors' 13 | require_relative 'redis_queued_locks/utilities' 14 | require_relative 'redis_queued_locks/logging' 15 | require_relative 'redis_queued_locks/data' 16 | require_relative 'redis_queued_locks/debugger' 17 | require_relative 'redis_queued_locks/resource' 18 | require_relative 'redis_queued_locks/acquirer' 19 | require_relative 'redis_queued_locks/instrument' 20 | require_relative 'redis_queued_locks/swarm' 21 | require_relative 'redis_queued_locks/client' 22 | require_relative 'redis_queued_locks/config' 23 | 24 | # @since 1.0.0 25 | extend RedisQueuedLocks::Debugger::Interface 26 | end 27 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/instrument/sampler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 1.6.0 5 | module RedisQueuedLocks::Instrument::Sampler 6 | # @return [Range] 7 | # 8 | # @api private 9 | # @since 1.6.0 10 | SAMPLING_PERCENT_RANGE = (0..100) 11 | 12 | class << self 13 | # Super simple probalistic function based on the `weight` of / values. 14 | # Requires the parameter as the required percent of values sampled. 15 | # 16 | # @param sampling_percent [Integer] 17 | # - percent of values in range of 0..100; 18 | # @return [Boolean] 19 | # - for % of invocations (and for the rest invocations) 20 | # 21 | # @api public 22 | # @since 1.6.0 23 | def sampling_happened?(sampling_percent) 24 | sampling_percent >= SecureRandom.rand(SAMPLING_PERCENT_RANGE) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/errors.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | class Error < ::StandardError 3 | end 4 | 5 | class ArgumentError < ::ArgumentError 6 | end 7 | 8 | class LockAlreadyObtainedError < Error 9 | end 10 | 11 | class LockAcquirementIntermediateTimeoutError < ::Timeout::Error 12 | end 13 | 14 | class LockAcquirementTimeoutError < Error 15 | end 16 | 17 | class LockAcquirementRetryLimitError < Error 18 | end 19 | 20 | class TimedLockIntermediateTimeoutError < ::Timeout::Error 21 | end 22 | 23 | class TimedLockTimeoutError < Error 24 | end 25 | 26 | class ConflictLockObtainError < Error 27 | end 28 | 29 | class SwarmError < Error 30 | end 31 | 32 | class SwarmArgumentError < ArgumentError 33 | end 34 | 35 | class ConfigError < Error 36 | end 37 | 38 | class ConfigNotFoundError < ConfigError 39 | end 40 | 41 | class ConfigValidationError < ConfigError 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/logging/sampler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api public 4 | # @since 1.5.0 5 | module RedisQueuedLocks::Logging::Sampler 6 | # @return [Range] 7 | # 8 | # @api private 9 | # @since 1.6.0 10 | SAMPLING_PERCENT_RANGE = (0..100) 11 | 12 | class << self 13 | # Super simple probalistic function based on the `weight` of / values. 14 | # Requires the parameter as the required percent of values sampled. 15 | # 16 | # @param sampling_percent [Integer] 17 | # - percent of values in range of 0..100; 18 | # @return [Boolean] 19 | # - for % of invocations (and for the rest invocations) 20 | # 21 | # @api public 22 | # @since 1.5.0 23 | # @version 1.6.0 24 | def sampling_happened?(sampling_percent) 25 | sampling_percent >= SecureRandom.rand(SAMPLING_PERCENT_RANGE) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | 3 | module RedisQueuedLocks 4 | module Acquirer 5 | module AcquireLock 6 | module YieldExpire 7 | module LogVisitor 8 | def self.expire_lock: ( 9 | RQL::loggerObj logger, 10 | bool log_sampled, 11 | String lock_key, 12 | Integer queue_ttl, 13 | String acquirer_id, 14 | String host_id, 15 | Symbol access_strategy 16 | ) -> void 17 | 18 | def self.decrease_lock: ( 19 | RQL::loggerObj logger, 20 | bool log_sampled, 21 | String lock_key, 22 | Integer decreased_ttl, 23 | Integer queue_ttl, 24 | String acquirer_id, 25 | String host_id, 26 | Symbol access_strategy 27 | ) -> void 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/clear_dead_requests.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module ClearDeadRequests 7 | type clearResult = { ok: bool, result: { processed_queues: Set[String] } } 8 | def self.clear_dead_requests: ( 9 | RC::client redis_client, 10 | Integer scan_size, 11 | Integer dead_ttl, 12 | RQL::loggerObj logger, 13 | RQL::instrObj instrumenter, 14 | untyped? instrument, 15 | bool log_sampling_enabled, 16 | Integer log_sampling_percent, 17 | RQL::Logging::samplerObj log_sampler, 18 | bool log_sample_this, 19 | bool instr_sampling_enabled, 20 | Integer instr_sampling_percent, 21 | RQL::Instrument::samplerObj instr_sampler, 22 | bool instr_sample_this 23 | ) -> clearResult 24 | 25 | private def self.each_lock_queue: (RC::client redis_client, Integer scan_size) { (String lock_queue) -> untyped } -> Enumerator[String] 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/release_all_locks.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module ReleaseAllLocks 7 | extend RQL::Utilities 8 | 9 | type releaseResult = { ok: bool, result: { rel_key_cnt: Integer, rel_time: Integer|Float } } 10 | def self.release_all_locks: ( 11 | RC::client redis, 12 | Integer batch_size, 13 | RQL::loggerObj logger, 14 | RQL::instrObj instrumenter, 15 | untyped instrument, 16 | bool log_sampling_enabled, 17 | Integer log_sampling_percent, 18 | RQL::Logging::samplerObj log_sampler, 19 | bool log_sample_this, 20 | bool instr_sampling_enabled, 21 | Integer instr_sampling_percent, 22 | RQL::Instrument::samplerObj instr_sampler, 23 | bool instr_sample_this 24 | ) -> releaseResult 25 | 26 | type fullRelesaeResult = { ok: bool, result: { rel_key_cnt: Integer } } 27 | private def self.fully_release_all_locks: (RC::client redis, Integer batch_size) -> fullRelesaeResult 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /.github/workflows/typecheck-runtime.yml: -------------------------------------------------------------------------------- 1 | name: TypeCheck (Runtime/RBS) 2 | on: [push] 3 | jobs: 4 | typecheck-runtime: 5 | name: 'TypeCheck (Runtime/RBS) (os: ${{ matrix.os }}) (ruby@${{ matrix.ruby_version }})' 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | ruby_version: ['3.4'] 10 | os: [ubuntu-latest] 11 | runs-on: ${{ matrix.os }} 12 | env: 13 | BUNDLE_RETRY: 3 14 | BUNDLE_JOBS: 4 15 | BUNDLE_FORCE_RUBY_PLATFORM: 1 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: supercharge/redis-github-action@1.7.0 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby_version }} 22 | bundler-cache: true 23 | - name: Prepare Bundler/Ruby Environment 24 | run: gem update && gem install bundler && bundle install --redownload && bundle clean --force 25 | - name: TypeCheck (Runtime) (RBS) 26 | run: bundle exec rbs collection install && RBS_TEST_RAISE=true RUBYOPT='-rrbs/test/setup' RBS_TEST_OPT='-I sig' RBS_TEST_LOGLEVEL=error RBS_TEST_TARGET='RedisQueuedLocks::*' bundle exec rspec --failure-exit-code=0 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024-2025 Rustam Ibragimov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module AcquireLock 7 | module YieldExpire 8 | DECREASE_LOCK_PTTL: String 9 | 10 | def yield_expire: ( 11 | RC::client redis, 12 | RQL::loggerObj logger, 13 | String lock_key, 14 | String acquirer_id, 15 | String host_id, 16 | Symbol access_strategy, 17 | bool timed, 18 | Integer|Float ttl_shift, 19 | Integer? ttl, 20 | Integer queue_ttl, 21 | Hash[String|Symbol,untyped]? meta, 22 | bool log_sampled, 23 | bool instr_sampled, 24 | bool should_expire, 25 | bool should_decrease 26 | ) ?{ (?) -> untyped } -> untyped? 27 | 28 | private 29 | 30 | def yield_with_timeout: ( 31 | Float timeout, 32 | String lock_key, 33 | Integer? lock_ttl, 34 | String acquirer_id, 35 | String host_id, 36 | Hash[String|Symbol,untyped]? meta, 37 | ) { (?) -> untyped } -> untyped 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /sig/vendor/redis_client.rbs: -------------------------------------------------------------------------------- 1 | class RedisClient 2 | interface _Client 3 | def call: (*untyped) -> untyped 4 | def pipelined: () { (Pipeline pipeline) -> untyped } -> untyped 5 | def scan: (*untyped) { (String key) -> untyped } -> untyped 6 | def multi: (?watch: Array[String]) { (Multi transact) -> untyped } -> untyped 7 | def with: (?untyped) { (::RedisClient|::RedisClient::Pooled rconn) -> untyped } -> untyped 8 | end 9 | 10 | include _Client 11 | 12 | class Pooled 13 | include _Client 14 | end 15 | 16 | class Pipeline 17 | def call: (*untyped) -> untyped 18 | end 19 | 20 | class Multi 21 | def call: (*untyped) -> untyped 22 | end 23 | 24 | class SentinelConfig 25 | def new_pool: (**untyped) -> ::RedisClient::Pooled 26 | def new_client: (**untyped) -> ::RedisClient 27 | end 28 | 29 | class Config 30 | def new_pool: (**untyped) -> ::RedisClient::Pooled 31 | def new_client: (**untyped) -> ::RedisClient 32 | end 33 | 34 | def self.sentinel: (**untyped) -> SentinelConfig 35 | def self.config: (**untyped) -> Config 36 | 37 | type client = ::RedisClient | ::RedisClient::Pooled 38 | type config = ::RedisClient::Config | ::RedisClient::SentinelConfig 39 | end 40 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.7.0 5 | module RedisQueuedLocks::Acquirer::AcquireLock::DequeueFromLockQueue::LogVisitor 6 | class << self 7 | # @param logger [::Logger,#debug] 8 | # @param log_sampled [Boolean] 9 | # @param lock_key [String] 10 | # @param queue_ttl [Integer] 11 | # @param acquirer_id [String] 12 | # @param host_id [String] 13 | # @param access_strategy [Symbol] 14 | # @return [void] 15 | # 16 | # @api private 17 | # @since 1.7.0 18 | # @version 1.9.0 19 | def dequeue_from_lock_queue( 20 | logger, 21 | log_sampled, 22 | lock_key, 23 | queue_ttl, 24 | acquirer_id, 25 | host_id, 26 | access_strategy 27 | ) 28 | return unless log_sampled 29 | 30 | logger.debug do 31 | "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue] " \ 32 | "lock_key => '#{lock_key}' " \ 33 | "queue_ttl => #{queue_ttl} " \ 34 | "acq_id => '#{acquirer_id}' " \ 35 | "hst_id => '#{host_id}' " \ 36 | "acs_strat => '#{access_strategy}" 37 | end rescue nil 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/release_lock.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module ReleaseLock 7 | extend RQL::Utilities 8 | 9 | type releaseResult = { 10 | ok: bool, 11 | result: { 12 | rel_time: Integer|Float, 13 | rel_key: String, 14 | rel_queue: String, 15 | queue_res: Symbol, 16 | lock_res: Symbol 17 | } 18 | } 19 | def self.release_lock: ( 20 | RC::client redis, 21 | String lock_name, 22 | RQL::instrObj instrumenter, 23 | RQL::loggerObj logger, 24 | bool log_sampling_enabled, 25 | Integer log_samplingPercent, 26 | RQL::Logging::samplerObj log_sampler, 27 | bool log_sample_this, 28 | bool instr_sampling_enabled, 29 | Integer instr_sampling_percent, 30 | RQL::Instrument::samplerObj instr_sampler, 31 | bool instr_sample_this 32 | ) -> releaseResult 33 | 34 | type fullReleaseResult = { ok: bool, result: { queue: Symbol, lock: Symbol } } 35 | private def self.fully_release_lock: (RC::client redis, String lock_key, String lock_key_queue) -> fullReleaseResult 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/swarm/zombie_info.rbs: -------------------------------------------------------------------------------- 1 | use RedisClient as RC 2 | 3 | module RedisQueuedLocks 4 | class Swarm 5 | module ZombieInfo 6 | type zombieInfo = { zombie_hosts: Set[String], zombie_acquirers: Set[String], zombie_locks: Set[String] } 7 | def self.zombies_info: (RC::client redis_client, Integer zombie_ttl, Integer lock_scan_size) -> zombieInfo 8 | 9 | type zombieLocks = Set[String] 10 | def self.zombie_locks: (RC::client redis_client, Integer zombie_ttl, Integer lock_scan_size) -> zombieLocks 11 | 12 | type zombieHosts = Set[String] 13 | def self.zombie_hosts: (RC::client redis_client, Integer zombie_ttl) -> zombieHosts 14 | 15 | type zombieAcquirers = Set[String] 16 | def self.zombie_acquirers: (RC::client redis_client, Integer zombie_ttl, Integer lock_scan_size) -> zombieAcquirers 17 | 18 | private def self.extract_zombie_hosts: (RC::client rconn, Integer zombie_ttl) -> zombieHosts 19 | private def self.extract_zombie_locks: (RC::client rconn, Integer zombie_ttl, Integer lock_scan_size) -> zombieLocks 20 | private def self.extract_zombie_acquirers: (RC::client rconn, Integer zombie_ttl, Integer lock_scan_size) -> zombieAcquirers 21 | private def self.extract_all: (RC::client rconn, Integer zombie_ttl, Integer lock_scan_size) -> zombieInfo 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/debugger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.0.0 5 | module RedisQueuedLocks::Debugger 6 | require_relative 'debugger/interface' 7 | 8 | # @return [String] 9 | # 10 | # @api private 11 | # @since 1.0.0 12 | DEBUG_ENABLED_METHOD = <<~METHOD_DECLARATION.strip.freeze 13 | def debug(message) = STDOUT.write("#\{message}\n") 14 | METHOD_DECLARATION 15 | 16 | # @return [String] 17 | # 18 | # @api private 19 | # @since 1.0.0 20 | DEBUG_DISABLED_MEHTOD = <<~METHOD_DECLARATION.strip.freeze 21 | def debug(message); end 22 | METHOD_DECLARATION 23 | 24 | class << self 25 | # @api private 26 | # @since 1.0.0 27 | instance_variable_set(:@enabled, false) 28 | 29 | # @return [void] 30 | # 31 | # @api private 32 | # @since 1.0.0 33 | def enable! 34 | @enabled = true 35 | eval(DEBUG_ENABLED_METHOD) 36 | end 37 | 38 | # @return [void] 39 | # 40 | # @api private 41 | # @since 1.0.0 42 | def disable! 43 | @enabled = false 44 | eval(DEBUG_DISABLED_MEHTOD) 45 | end 46 | 47 | # @return [Boolean] 48 | # 49 | # @api private 50 | # @since 1.0.0 51 | def enabled? 52 | @enabled 53 | end 54 | 55 | # @param message [String] 56 | # @return [void] 57 | # 58 | # @api private 59 | # @since 1.0.0 60 | def debug(message); end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module AcquireLock 7 | module TryToLock 8 | EXTEND_LOCK_PTTL: String 9 | 10 | # { ok: bool, result: Symbol } 11 | # { ok: bool, result: Symbol } 12 | # { 13 | # ok: bool, 14 | # result: { 15 | # process: Symbol, 16 | # lock_key: String, 17 | # acq_id: String, 18 | # hst_id: String, 19 | # ts: Integer?, 20 | # ttl: Integer? 21 | # } 22 | # } 23 | 24 | def try_to_lock: ( 25 | RC::client redis, 26 | RQL::loggerObj logger, 27 | bool log_lock_try, 28 | String lock_key, 29 | Symbol read_write_mode, 30 | String lock_key_queue, 31 | String read_lock_key_queue, 32 | String write_lock_key_queue, 33 | String acquirer_id, 34 | String host_id, 35 | Integer|Float acquirer_position, 36 | Integer? ttl, 37 | Integer queue_ttl, 38 | bool fail_fast, 39 | Symbol conflict_strategy, 40 | Symbol access_strategy, 41 | Hash[String|Symbol,untyped]? meta, 42 | bool log_sampled, 43 | bool instr_sampled 44 | ) -> { ok: bool, result: Symbol|Hash[Symbol,untyped] } 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests (RSpec) 2 | on: [push] 3 | jobs: 4 | test-previous: 5 | name: '(os: ${{ matrix.os }}) (ruby@${{ matrix.ruby_version }})' 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | ruby_version: ['3.3'] 10 | os: [ubuntu-latest] 11 | runs-on: ${{ matrix.os }} 12 | env: 13 | BUNDLE_GEMFILE: github_ci/ruby${{ matrix.ruby_version }}.gemfile 14 | BUNDLE_RETRY: 3 15 | BUNDLE_JOBS: 4 16 | BUNDLE_FORCE_RUBY_PLATFORM: 1 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: supercharge/redis-github-action@1.7.0 20 | - uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby_version }} 23 | bundler-cache: true 24 | - name: (Test) RSpec 25 | run: bundle exec rake rspec 26 | test-mainstream: 27 | name: 'Tests (os: ${{ matrix.os }}) (ruby@${{ matrix.ruby_version }})' 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | ruby_version: ['3.4'] 32 | os: [ubuntu-latest] 33 | runs-on: ${{ matrix.os }} 34 | env: 35 | BUNDLE_RETRY: 3 36 | BUNDLE_JOBS: 4 37 | BUNDLE_FORCE_RUBY_PLATFORM: 1 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: supercharge/redis-github-action@1.7.0 41 | - uses: ruby/setup-ruby@v1 42 | with: 43 | ruby-version: ${{ matrix.ruby_version }} 44 | bundler-cache: true 45 | - name: Tests (RSpec) 46 | run: bundle exec rake rspec 47 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/acquirer/release_locks_of.rbs: -------------------------------------------------------------------------------- 1 | use RedisQueuedLocks as RQL 2 | use RedisClient as RC 3 | 4 | module RedisQueuedLocks 5 | module Acquirer 6 | module ReleaseLocksOf 7 | extend RQL::Utilities 8 | 9 | type releaseResult = { 10 | ok: bool, 11 | result: { 12 | rel_key_cnt: Integer, 13 | tch_queue_cnt: Integer, 14 | rel_time: Integer|Float 15 | } 16 | } 17 | def self.release_locks_of: ( 18 | String host_id, 19 | String acquirer_id, 20 | RC::client redis, 21 | Integer lock_scan_size, 22 | Integer queue_scan_size, 23 | RQL::loggerObj logger, 24 | RQL::instrObj instrumenter, 25 | untyped instrument, 26 | bool log_sampling_enabled, 27 | Integer log_sampling_percent, 28 | RQL::Logging::samplerObj log_sampler, 29 | bool log_sample_this, 30 | bool instr_sampling_enabled, 31 | Integer instr_sampling_percent, 32 | RQL::Instrument::samplerObj instr_sampler, 33 | bool instr_sample_this 34 | ) -> releaseResult 35 | 36 | type fullyReleaseAllLocksResult = { ok: bool, result: { rel_key_cnt: Integer, tch_queue_cnt: Integer } } 37 | private def self.fully_release_locks_of: ( 38 | String refused_host_id, 39 | String refused_acquirer_id, 40 | RC::client redis, 41 | Integer locks_scan_size, 42 | Integer queue_scan_size 43 | ) -> fullyReleaseAllLocksResult 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.7.0 5 | module RedisQueuedLocks::Acquirer::AcquireLock::DequeueFromLockQueue 6 | require_relative 'dequeue_from_lock_queue/log_visitor' 7 | 8 | # @param redis [RedisClient] 9 | # @param logger [::Logger,#debug] 10 | # @param lock_key [String] 11 | # @param read_write_mode [Symbol] 12 | # @param lock_key_queue [String] 13 | # @param read_lock_key_queue [String] 14 | # @param write_lock_key_queue [String] 15 | # @param queue_ttl [Integer] 16 | # @param acquirer_id [String] 17 | # @param host_id [String] 18 | # @param access_strategy [Symbol] 19 | # @param log_sampled [Boolean] 20 | # @param instr_sampled [Boolean] 21 | # @return [Hash] Format: { ok: true/false, result: Integer } 22 | # 23 | # @api private 24 | # @since 1.7.0 25 | # @version 1.13.0 26 | def dequeue_from_lock_queue( 27 | redis, 28 | logger, 29 | lock_key, 30 | read_write_mode, 31 | lock_key_queue, 32 | read_lock_key_queue, 33 | write_lock_key_queue, 34 | queue_ttl, 35 | acquirer_id, 36 | host_id, 37 | access_strategy, 38 | log_sampled, 39 | instr_sampled 40 | ) 41 | # @type var result: Integer 42 | result = redis.call('ZREM', lock_key_queue, acquirer_id) 43 | 44 | LogVisitor.dequeue_from_lock_queue( 45 | logger, log_sampled, 46 | lock_key, queue_ttl, acquirer_id, host_id, access_strategy 47 | ) 48 | 49 | { ok: true, result: result } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /sig/redis_queued_locks/resource.rbs: -------------------------------------------------------------------------------- 1 | module RedisQueuedLocks 2 | module Resource 3 | KEY_PATTERN: String 4 | LOCK_PATTERN: String 5 | LOCK_QUEUE_PATTERN: String 6 | READ_LOCK_QUEUE_PATTERN: String 7 | WRITE_LOCK_QUEUE_PATTERN: String 8 | SWARM_KEY: String 9 | REDIS_TIMESHIFT_ERROR: Integer 10 | 11 | def self.calc_uniq_identity: () -> String 12 | def self.acquirer_identifier: ( 13 | Integer|String process_id, 14 | Integer|String thread_id, 15 | Integer|String fiber_id, 16 | Integer|String ractor_id, 17 | String identity 18 | ) -> String 19 | 20 | def self.host_identifier: ( 21 | Integer|String process_id, 22 | Integer|String thread_id, 23 | Integer|string ractor_id, 24 | String identity 25 | ) -> String 26 | 27 | def self.prepare_lock_key: (String lock_name) -> String 28 | def self.prepare_lock_queue: (String lock_name) -> String 29 | def self.prepare_read_lock_queue: (String lock_name) -> String 30 | def self.prepare_write_lock_queue: (String lock_name) -> String 31 | def self.calc_initial_acquirer_position: () -> Float 32 | def self.acquirer_dead_score: (Integer|Float queue_ttl) -> Float 33 | def self.calc_zombie_score: (Float zombie_ttl) -> Float 34 | def self.dead_score_reached?: (Float acquirer_position, Integer queue_ttl) -> bool 35 | def self.lock_key_from_queue: (String lock_queue) -> String 36 | def self.get_thread_id: () -> Integer 37 | def self.get_fiber_id: () -> Integer 38 | def self.get_ractor_id: () -> Integer 39 | def self.get_process_id: () -> Integer 40 | def self.possible_host_identifiers: (String identity) -> Array[String] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/redis_queued_locks/swarm/acquirers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @api private 4 | # @since 1.9.0 5 | module RedisQueuedLocks::Swarm::Acquirers 6 | class << self 7 | # Returns the list of swarm acquirers stored as HASH. 8 | # Liveness probe time is represented as a float value (Time.now.to_f initially). 9 | # 10 | # @param redis_client [RedisClient] 11 | # @param zombie_ttl [Integer] 12 | # @return [Hash>] Format: 13 | # { 14 | # => { 15 | # zombie: , 16 | # last_probe_time: