{"id":16443393,"url":"https://github.com/0exp/redis_queued_locks","last_synced_at":"2025-04-06T20:13:18.535Z","repository":{"id":224381459,"uuid":"763125640","full_name":"0exp/redis_queued_locks","owner":"0exp","description":"Distributed locks with \"prioritized lock acquisition queue\" capabilities based on the Redis Database. Provides flexible invocation flow, parametrized time limits, instrumentation, logging, etc.","archived":false,"fork":false,"pushed_at":"2024-11-17T22:53:09.000Z","size":774,"stargazers_count":39,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-24T16:30:18.492Z","etag":null,"topics":["advisory-lock","distributed-lock","distributed-locks","distributed-redis-lock","distributed-redis-locks","queued-lock","redis-lock","redis-locks","redis-queued-lock","redis-queued-locks","redlock","redlock-ruby","reentrant-locks","reentrant-redis-lock"],"latest_commit_sha":null,"homepage":"https://github.com/0exp/redis_queued_locks","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0exp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"0exp"}},"created_at":"2024-02-25T16:18:31.000Z","updated_at":"2024-11-17T22:53:13.000Z","dependencies_parsed_at":"2024-02-25T16:50:34.281Z","dependency_job_id":"7aed3ff6-9605-44b3-afae-4dae93581b0c","html_url":"https://github.com/0exp/redis_queued_locks","commit_stats":null,"previous_names":["0exp/redis_queued_locks"],"tags_count":57,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fredis_queued_locks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fredis_queued_locks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fredis_queued_locks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fredis_queued_locks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0exp","download_url":"https://codeload.github.com/0exp/redis_queued_locks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543595,"owners_count":20955865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["advisory-lock","distributed-lock","distributed-locks","distributed-redis-lock","distributed-redis-locks","queued-lock","redis-lock","redis-locks","redis-queued-lock","redis-queued-locks","redlock","redlock-ruby","reentrant-locks","reentrant-redis-lock"],"created_at":"2024-10-11T09:20:19.695Z","updated_at":"2025-04-06T20:13:18.522Z","avatar_url":"https://github.com/0exp.png","language":"Ruby","funding_links":["https://github.com/sponsors/0exp"],"categories":[],"sub_categories":[],"readme":"# RedisQueuedLocks \u0026middot; [![Gem Version](https://badge.fury.io/rb/redis_queued_locks.svg)](https://badge.fury.io/rb/redis_queued_locks) [![Build](https://github.com/0exp/redis_queued_locks/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/0exp/redis_queued_locks/actions)\n\n\u003ca href=\"https://redis.io/docs/manual/patterns/distributed-locks/\"\u003eDistributed locks\u003c/a\u003e with \"prioritized lock acquisition queue\" capabilities based on the Redis Database.\n\nEach lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) (with requeue capabilities) which guarantees the request queue will never be stacked.\n\nIn addition to the classic `queued` (FIFO) strategy RQL supports `random` (RANDOM) lock obtaining strategy when any acquirer from the lock queue can obtain the lock regardless the position in the queue.\n\nProvides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), logging and instrumentation.\n\n---\n\n## Table of Contents\n\n- [Requirements](#requirements)\n- [Experience](#experience)\n- [Algorithm](#algorithm)\n- [Installation](#installation)\n- [Setup](#setup)\n- [Configuration](#configuration)\n- [Usage](#usage)\n  - [lock](#lock---obtain-a-lock)\n  - [lock!](#lock---exceptional-lock-obtaining)\n  - [lock_info](#lock_info)\n  - [queue_info](#queue_info)\n  - [locked?](#locked)\n  - [queued?](#queued)\n  - [unlock](#unlock---release-a-lock)\n  - [clear_locks](#clear_locks---release-all-locks-and-lock-queues)\n  - [extend_lock_ttl](#extend_lock_ttl)\n  - [locks](#locks---get-list-of-obtained-locks)\n  - [queues](#queues---get-list-of-lock-request-queues)\n  - [keys](#keys---get-list-of-taken-locks-and-queues)\n  - [locks_info](#locks_info---get-list-of-locks-with-their-info)\n  - [queues_info](#queues_info---get-list-of-queues-with-their-info)\n  - [clear_dead_requests](#clear_dead_requests)\n  - [current_acquirer_id](#current_acquirer_id)\n  - [current_host_id](#current_host_id)\n  - [possible_host_ids](#possible_host_ids)\n- [Swarm Mode and Zombie Locks](#swarm-mode-and-zombie-locks)\n  - [work and usage preview (temporary example-based docs)](#work-and-usage-preview-temporary-example-based-docs)\n  - [How to Swarm](#how-to-swarm)\n    - [configuration](#)\n    - [swarm_status](#swarm_status)\n    - [swarm_info](#swarm_info)\n    - [swarmize!](#swarmize!)\n    - [deswarmize!](#deswarmize!)\n    - [probe_hosts](#probe_hosts)\n    - [flush_zobmies](#flush_zombies)\n  - [zombies_info](#zombies_info)\n  - [zombie_locks](#zombie_locks)\n  - [zombie_hosts](#zombie_hosts)\n  - [zombie_acquirers](#zombie_acquirers)\n- [Lock Access Strategies](#lock-access-strategies)\n  - [queued](#lock-access-strategies)\n  - [random](#lock-access-strategies)\n- [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)\n- [Logging](#logging)\n  - [Logging Configuration](#logging-configuration)\n- [Instrumentation](#instrumentation)\n  - [Instrumentation Configuration](#instrumentation-configuration)\n  - [Instrumentation Events](#instrumentation-events)\n- [Roadmap](#roadmap)\n- [Contributing](#contributing)\n- [License](#license)\n- [Authors](#authors)\n\n---\n\n### Requirements\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- Redis Version: `~\u003e 7.x`;\n- Redis Protocol: `RESP3`;\n- gem `redis-client`: `~\u003e 0.20`;\n- Ruby: `\u003e= 3.1`;\n\n---\n\n### Experience\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- Battle-tested on huge ruby projects in production: `~3000` locks-per-second are obtained and released on an ongoing basis;\n- Works well with `hiredis` driver enabled (it is enabled by default on our projects where `redis_queued_locks` are used);\n\n---\n\n### Algorithm\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n\u003e Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) which guarantees that the request queue will never be stacked.\n\n\u003e In addition to the classic \"queued\" (FIFO) strategy RQL supports \"random\" (RANDOM) lock obtaining strategy when any acquirer from the lock queue can obtain the lock regardless the position in the queue.\n\n**Soon**: detailed explanation.\n\n---\n\n### Installation\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n```ruby\ngem 'redis_queued_locks'\n```\n\n```shell\nbundle install\n# --- or ---\ngem install redis_queued_locks\n```\n\n```ruby\nrequire 'redis_queued_locks'\n```\n\n---\n\n### Setup\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n```ruby\nrequire 'redis_queued_locks'\n\n# Step 1: initialize RedisClient instance\nredis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance\n\n# Step 2: initialize RedisQueuedLock::Client instance\nrq_lock_client = RedisQueuedLocks::Client.new(redis_client) do |config|\n  # NOTE:\n  #   - some your application-related configs;\n  #   - for documentation see \u003cConfiguration\u003e section in readme;\nend\n\n# Step 3: start to work with locks :)\nrq_lock_client.lock(\"some-lock\") { puts \"Hello, lock!\" }\n```\n\n---\n\n### Configuration\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n```ruby\nredis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance\n\nclinet = RedisQueuedLocks::Client.new(redis_client) do |config|\n  # (default: 3) (supports nil)\n  # - nil means \"infinite retries\" and you are only limited by the \"try_to_lock_timeout\" config;\n  config.retry_count = 3\n\n  # (milliseconds) (default: 200)\n  config.retry_delay = 200\n\n  # (milliseconds) (default: 25)\n  config.retry_jitter = 25\n\n  # (seconds) (supports nil)\n  # - nil means \"no timeout\" and you are only limited by \"retry_count\" config;\n  config.try_to_lock_timeout = 10\n\n  # (milliseconds) (default: 5_000)\n  # - lock's time to live\n  config.default_lock_ttl = 5_000\n\n  # (seconds) (default: 15)\n  # - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the queue);\n  config.default_queue_ttl = 15\n\n  # (boolean) (default: false)\n  # - should be all blocks of code are timed by default;\n  config.is_timed_by_default = false\n\n  # (boolean) (default: false)\n  # - When the lock acquirement try reached the acquirement time limit (:timeout option) the\n  #   `RedisQueuedLocks::LockAcquirementTimeoutError` is raised (when `raise_errors` option\n  #   of the #lock method is set to `true`). The error message contains the lock key name and\n  #   the timeout value).\n  # - \u003ctrue\u003e option adds the additional details to the error message:\n  #   - current lock queue state (you can see which acquirer blocks your request and\n  #     how much acquirers are in queue);\n  #   - current lock data stored inside (for example: you can check the current acquirer and\n  #     the lock meta state if you store some additional data there);\n  # - Realized as an option because of the additional lock data requires two additional Redis\n  #   queries: (1) get the current lock from redis and (2) fetch the lock queue state;\n  # - These two additional Redis queries has async nature so you can receive\n  #   inconsistent data of the lock and of the lock queue in your error emssage because:\n  #   - required lock can be released after the error moment and before the error message build;\n  #   - required lock can be obtained by other process after the error moment and\n  #     before the error message build;\n  #   - required lock queue can reach a state when the blocking acquirer start to obtain the lock\n  #     and moved from the lock queue after the error moment and before the error message build;\n  # - You should consider the async nature of this error message and should use received data\n  #   from error message correspondingly;\n  config.detailed_acq_timeout_error = false\n\n  # (symbol) (default: :queued)\n  # - Defines the way in which the lock should be obitained;\n  # - By default it is configured to obtain a lock in classic `queued` way:\n  #   you should wait your position in queue in order to obtain a lock;\n  # - Can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);\n  # - Supports different strategies:\n  #   - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;\n  #   - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist,\n  #     retries, timeouts and so on). if lock is free to obtain - it will be obtained;\n  config.default_access_strategy = :queued\n\n  # (symbol) (default: :wait_for_lock)\n  # - Global default conflict strategy mode;\n  # - Can be customized in methods `#lock` and `#lock!` via `:conflict_strategy` attribute (see method signatures of #lock and #lock! methods);\n  # - Conflict strategy is a logical behavior for cases when the process that obtained the lock want to acquire this lock again;\n  # - Realizes \"reentrant locks\" abstraction (same process conflict / same process deadlock);\n  # - By default uses `:wait_for_lock` strategy (classic way);\n  # - Strategies:\n  #   - `:work_through` - continue working under the lock \u003cwithout\u003e lock's TTL extension;\n  #   - `:extendable_work_through` - continue working under the lock \u003cwith\u003e lock's TTL extension;\n  #   - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));\n  #   - `:dead_locking` - fail with deadlock exception;\n  # - See \"Dead locks and Reentrant Locks\" documentation section in REDME.md for details;\n  config.default_conflict_strategy = :wait_for_lock\n\n  # (default: 100)\n  # - how many items will be released at a time in #clear_locks and in #clear_dead_requests (uses SCAN);\n  # - affects the performance of your Redis and Ruby Application (configure thoughtfully);\n  config.lock_release_batch_size = 100\n\n  # (default: 500)\n  # - how many items should be extracted from redis during the #locks, #queues, #keys\n  #   #locks_info, and #queues_info operations (uses SCAN);\n  # - affects the performance of your Redis and Ruby Application (configure thoughtfully;)\n  config.key_extraction_batch_size = 500\n\n  # (default: 1 day)\n  # - the default period of time (in milliseconds) after which a lock request is considered dead;\n  # - used for `#clear_dead_requests` as default vaule of `:dead_ttl` option;\n  config.dead_request_ttl = (1 * 24 * 60 * 60 * 1000) # one day in milliseconds\n\n  # (default: RedisQueuedLocks::Instrument::VoidNotifier)\n  # - instrumentation layer;\n  # - you can provide your own instrumenter that should realize `#notify(event, payload = {})` interface:\n  #   - event: \u003cstring\u003e requried;\n  #   - payload: \u003chash\u003e requried;\n  # - disabled by default via `VoidNotifier`;\n  config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport\n\n  # (default: -\u003e { RedisQueuedLocks::Resource.calc_uniq_identity })\n  # - uniqude idenfitier that is uniq per process/pod;\n  # - prevents potential lock-acquirement collisions bettween different process/pods\n  #   that have identical process_id/thread_id/fiber_id/ractor_id (identivcal acquirer ids);\n  # - it is calculated once per `RedisQueudLocks::Client` instance;\n  # - expects the proc object;\n  # - `SecureRandom.hex(8)` by default;\n  config.uniq_identifier = -\u003e { RedisQueuedLocks::Resource.calc_uniq_identity }\n\n  # (default: RedisQueuedLocks::Logging::VoidLogger)\n  # - the logger object;\n  # - should implement `debug(progname = nil, \u0026block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;\n  # - supports `SemanticLogger::Logger` (see \"semantic_logger\" gem)\n  # - at this moment the only debug logs are realised in following cases:\n  #   - \"[redis_queued_locks.start_lock_obtaining]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.start_try_to_lock_cycle]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.dead_score_reached__reset_acquirer_position]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.lock_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\", \"acs_strat\");\n  #   - \"[redis_queued_locks.extendable_reentrant_lock_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\", \"acs_strat\");\n  #   - \"[redis_queued_locks.reentrant_lock_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\", \"acs_strat\");\n  #   - \"[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.expire_lock]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.decrease_lock]\" (logs \"lock_key\", \"decreased_ttl\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  # - by default uses VoidLogger that does nothing;\n  config.logger = RedisQueuedLocks::Logging::VoidLogger\n\n  # (default: false)\n  # - adds additional debug logs;\n  # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);\n  # - it adds following debug logs in addition to the existing:\n  #   - \"[redis_queued_locks.try_lock.start]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.try_lock.rconn_fetched]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.try_lock.same_process_conflict_detected]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.try_lock.same_process_conflict_analyzed]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\");\n  #   - \"[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", \"last_ext_ttl\", \"last_ext_ts\");\n  #   - \"[redis_queued_locks.try_lock.reentrant_lock__work_through]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", last_spc_ts);\n  #   - \"[redis_queued_locks.try_lock.acq_added_to_queue]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\")\";\n  #   - \"[redis_queued_locks.try_lock.remove_expired_acqs]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.try_lock.get_first_from_queue]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\");\n  #   - \"[redis_queued_locks.try_lock.exit__queue_ttl_reached]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  #   - \"[redis_queued_locks.try_lock.exit__no_first]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\", \"\u003ccurrent_lock_data\u003e\");\n  #   - \"[redis_queued_locks.try_lock.exit__lock_still_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\", \"locked_by_acq_id\", \"\u003ccurrent_lock_data\u003e\");\n  #   - \"[redis_queued_locks.try_lock.obtain__free_to_acquire]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n  config.log_lock_try = false\n\n  # (default: false)\n  # - enables \u003clog sampling\u003e: only the configured percent of RQL cases will be logged;\n  # - disabled by default;\n  # - works in tandem with \u003cconfig.log_sampling_percent\u003e and \u003clog.sampler\u003e configs;\n  config.log_sampling_enabled = false\n\n  # (default: 15)\n  # - the percent of cases that should be logged;\n  # - take an effect when \u003cconfig.log_sampling_enalbed\u003e is true;\n  # - works in tandem with \u003cconfig.log_sampling_enabled\u003e and \u003cconfig.log_sampler\u003e configs;\n  config.log_sampling_percent = 15\n\n  # (default: RedisQueuedLocks::Logging::Sampler)\n  # - percent-based log sampler that decides should be RQL case logged or not;\n  # - works in tandem with \u003cconfig.log_sampling_enabled\u003e and \u003cconfig.log_sampling_percent\u003e configs;\n  # - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand\n  #   method so the algorithm error is ~(0%..13%);\n  # - you can provide your own log sampler with bettter algorithm that should realize\n  #   `sampling_happened?(percent) =\u003e boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);\n  config.log_sampler = RedisQueuedLocks::Logging::Sampler\n\n  # (default: false)\n  # - enables \u003cinstrumentaion sampling\u003e: only the configured percent of RQL cases will be instrumented;\n  # - disabled by default;\n  # - works in tandem with \u003cconfig.instr_sampling_percent and \u003clog.instr_sampler\u003e;\n  config.instr_sampling_enabled = false\n\n  # (default: 15)\n  # - the percent of cases that should be instrumented;\n  # - take an effect when \u003cconfig.instr_sampling_enalbed\u003e is true;\n  # - works in tandem with \u003cconfig.instr_sampling_enabled\u003e and \u003cconfig.instr_sampler\u003e configs;\n  config.instr_sampling_percent = 15\n\n  # (default: RedisQueuedLocks::Instrument::Sampler)\n  # - percent-based log sampler that decides should be RQL case instrumented or not;\n  # - works in tandem with \u003cconfig.instr_sampling_enabled\u003e and \u003cconfig.instr_sampling_percent\u003e configs;\n  # - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand\n  #   method so the algorithm error is ~(0%..13%);\n  # - you can provide your own log sampler with bettter algorithm that should realize\n  #   `sampling_happened?(percent) =\u003e boolean` interface (see `RedisQueuedLocks::Instrument::Sampler` for example);\n  config.instr_sampler = RedisQueuedLocks::Instrument::Sampler\nend\n```\n\n---\n\n### Usage\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- [lock](#lock---obtain-a-lock)\n- [lock!](#lock---exeptional-lock-obtaining)\n- [lock_info](#lock_info)\n- [queue_info](#queue_info)\n- [locked?](#locked)\n- [queued?](#queued)\n- [unlock](#unlock---release-a-lock)\n- [clear_locks](#clear_locks---release-all-locks-and-lock-queues)\n- [extend_lock_ttl](#extend_lock_ttl)\n- [locks](#locks---get-list-of-obtained-locks)\n- [queues](#queues---get-list-of-lock-request-queues)\n- [keys](#keys---get-list-of-taken-locks-and-queues)\n- [locks_info](#locks_info---get-list-of-locks-with-their-info)\n- [queues_info](#queues_info---get-list-of-queues-with-their-info)\n- [clear_dead_requests](#clear_dead_requests)\n- [current_acquirer_id](#current_acquirer_id)\n- [current_host_id](#current_host_id)\n- [possible_host_ids](#possible_host_ids)\n\n---\n\n#### #lock - obtain a lock\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- `#lock` - obtain a lock;\n- If block is passed:\n  - the obtained lock will be released after the block execution or the lock's ttl (what will happen first);\n    - if you want to timeout (fail with timeout) the block execution with lock's TTL use `timed: true` option;\n  - the block's result will be returned;\n- If block is not passed:\n  - the obtained lock will be released after lock's ttl;\n  - the lock information will be returned (hash with technical info that contains: lock key, acquirer identifier, acquirement timestamp, lock's ttl, type of obtaining process, etc);\n\n```ruby\ndef lock(\n  lock_name,\n  ttl: config[:default_lock_ttl],\n  queue_ttl: config[:default_queue_ttl],\n  timeout: config[:try_to_lock_timeout],\n  timed: config[:is_timed_by_default],\n  retry_count: config[:retry_count],\n  retry_delay: config[:retry_delay],\n  retry_jitter: config[:retry_jitter],\n  raise_errors: false,\n  fail_fast: false,\n  conflict_strategy: config[:default_conflict_strategy],\n  access_strategy: config[:default_access_strategy],\n  identity: uniq_identity, # (attr_accessor) calculated during client instantiation via config[:uniq_identifier] proc;\n  meta: nil,\n  detailed_acq_timeout_error: config[:detailed_acq_timeout_error],\n  instrument: nil,\n  instrumenter: config[:instrumenter],\n  logger: config[:logger],\n  log_lock_try: config[:log_lock_try],\n  log_sampling_enabled: config[:log_sampling_enabled],\n  log_sampling_percent: config[:log_sampling_percent],\n  log_sampler: config[:log_sampler],\n  log_sample_this: false,\n  instr_sampling_enabled: config[:instr_sampling_enabled],\n  instr_sampling_percent: config[:instr_sampling_percent],\n  instr_sampler: config[:instr_sampler],\n  instr_sample_this: false,\n  \u0026block\n)\n```\n\n- `lock_name` - (required) `[String]`\n  - Lock name to be obtained.\n- `ttl` - (optional) - [Integer]\n  - Lock's time to live (in milliseconds);\n  - pre-configured in `config[:default_lock_ttl]`;\n- `queue_ttl` - (optional) `[Integer]`\n  - Lifetime of the acuier's lock request. In seconds.\n  - pre-configured in `config[:default_queue_ttl]`;\n- `timeout` - (optional) `[Integer,NilClass]`\n  - Time period a client should try to acquire the lock (in seconds). Nil means \"without timeout\".\n  - pre-configured in `config[:try_to_lock_timeout]`;\n- `timed` - (optiona) `[Boolean]`\n  - Limit the invocation time period of the passed block of code by the lock's TTL.\n  - pre-configured in `config[:is_timed_by_default]`;\n  - `false` by default;\n- `retry_count` - (optional) `[Integer,NilClass]`\n  - How many times we should try to acquire a lock. Nil means \"infinite retries\".\n  - pre-configured in `config[:retry_count]`;\n- `retry_delay` - (optional) `[Integer]`\n  - A time-interval between the each retry (in milliseconds).\n  - pre-configured in `config[:retry_delay]`;\n- `retry_jitter` - (optional) `[Integer]`\n  - Time-shift range for retry-delay (in milliseconds);\n  - pre-configured in `config[:retry_jitter]`;\n- `instrumenter` - (optional) `[#notify]`\n  - See RedisQueuedLocks::Instrument::ActiveSupport for example;\n  - See [Instrumentation](#instrumentation) section of docs;\n  - pre-configured in `config[:isntrumenter]` with void notifier (`RedisQueuedLocks::Instrumenter::VoidNotifier`);\n- `instrument` - (optional) `[NilClass,Any]`\n  - Custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;\n  - `nil` by default (means \"no custom instrumentation data\");\n- `raise_errors` - (optional) `[Boolean]`\n  - Raise errors on library-related limits (such as timeout or retry count limit) and on lock conflicts (such as same-process dead locks);\n  - `false` by default;\n- `fail_fast` - (optional) `[Boolean]`\n  - Should the required lock to be checked before the try and exit immidietly if lock is\n    already obtained;\n  - Should the logic exit immidietly after the first try if the lock was obtained\n    by another process while the lock request queue was initially empty;\n  - `false` by default;\n- `access_strategy` - (optional) - `[Symbol]`\n  - Defines the way in which the lock should be obitained (in queued way, in random way and so on);\n  - By default it is configured to obtain a lock in classic `:queued` way: you should wait your position in queue in order to obtain a lock;\n  - Supports following strategies:\n    - `:queued` (FIFO): (default) the classic queued behavior, your lock will be obitaned if you are first in queue and the required lock is free;\n    - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist, retries, timeouts and so on).\n      if lock is free to obtain - it will be obtained;\n  - pre-configured in `config[:default_access_strategy]`;\n  - See [Lock Access Strategies](#lock-access-strategies) documentation section for details;\n- `conflict_strategy` - (optional) - `[Symbol]`\n  - The conflict strategy mode for cases when the process that obtained the lock\n    want to acquire this lock again;\n  - By default uses `:wait_for_lock` strategy;\n  - pre-confured in `config[:default_conflict_strategy]`;\n  - Strategies:\n    - `:work_through` - continue working under the lock **without** lock's TTL extension;\n    - `:extendable_work_through` - continue working under the lock **with** lock's TTL extension;\n    - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));\n    - `:dead_locking` - fail with deadlock exception;\n  - See [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) documentation section for details;\n- `identity` - (optional) `[String]`\n  - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the\n    collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different\n    pods or/and nodes of your application;\n  - It is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`\n    ivar (accessed via `uniq_dentity` accessor method);\n  - Identity calculator is pre-configured in `config[:uniq_identifier]`;\n- `meta` - (optional) `[NilClass,Hash\u003cString|Symbol,Any\u003e]`\n  - A custom metadata wich will be passed to the lock data in addition to the existing data;\n  - Custom metadata can not contain reserved lock data keys (such as `lock_key`, `acq_id`, `ts`, `ini_ttl`, `rem_ttl`);\n  - `nil` by default (means \"no metadata\");\n- `detailed_acq_timeout_error` - (optional) `[Boolean]`\n  - When the lock acquirement try reached the acquirement time limit (:timeout option) the\n    `RedisQueuedLocks::LockAcquirementTimeoutError` is raised (when `raise_errors` option\n    set to `true`). The error message contains the lock key name and the timeout value).\n  - \u003ctrue\u003e option adds the additional details to the error message:\n    - current lock queue state (you can see which acquirer blocks your request and how much acquirers are in queue);\n    - current lock data stored inside (for example: you can check the current acquirer and the lock meta state if you store some additional data there);\n  - Realized as an option because of the additional lock data requires two additional Redis\n    queries: (1) get the current lock from redis and (2) fetch the lock queue state;\n  - These two additional Redis queries has async nature so you can receive\n    inconsistent data of the lock and of the lock queue in your error emssage because:\n    - required lock can be released after the error moment and before the error message build;\n    - required lock can be obtained by other process after the error moment and\n      before the error message build;\n    - required lock queue can reach a state when the blocking acquirer start to obtain the lock\n      and moved from the lock queue after the error moment and before the error message build;\n  - You should consider the async nature of this error message and should use received data\n    from error message correspondingly;\n  - pre-configred in `config[:detailed_acq_timeout_error]`;\n- `logger` - (optional) `[::Logger,#debug]`\n  - Logger object used for loggin internal mutation oeprations and opertioan results / process progress;\n  - pre-configured in `config[:logger]` with void logger `RedisQueuedLocks::Logging::VoidLogger`;\n- `log_lock_try` - (optional) `[Boolean]`\n  - should be logged the each try of lock acquiring (a lot of logs can be generated depending on your retry configurations);\n  - pre-configured in `config[:log_lock_try]`;\n  - `false` by default;\n- `log_sampling_enabled` - (optional) `[Boolean]`\n  - enables **log sampling**: only the configured percent of RQL cases will be logged;\n  - disabled by default;\n  - works in tandem with `log_sampling_percent` and `log_sampler` options;\n  - pre-configured in `config[:log_sampling_enabled]`;\n- `log_sampling_percent` - (optional) `[Integer]`\n  - the percent of cases that should be logged;\n  - take an effect when `log_sampling_enalbed` is true;\n  - works in tandem with `log_sampling_enabled` and `log_sampler` options;\n  - pre-configured in `config[:log_sampling_percent]`;\n- `log_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Logging::Sampler\u003e]`\n  - percent-based log sampler that decides should be RQL case logged or not;\n  - works in tandem with `log_sampling_enabled` and `log_sampling_percent` options;\n  - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand\n    method so the algorithm error is ~(0%..13%);\n  - you can provide your own log sampler with bettter algorithm that should realize\n    `sampling_happened?(percent) =\u003e boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);\n  - pre-configured in `config[:log_sampler]`;\n- `log_sample_this` - (optional) `[Boolean]`\n  - marks the method that everything should be logged despite the enabled log sampling;\n  - makes sense when log sampling is enabled;\n  - `false` by default;\n- `instr_sampling_enabled` - (optional) `[Boolean]`\n  - enables **instrumentaion sampling**: only the configured percent of RQL cases will be instrumented;\n  - disabled by default;\n  - works in tandem with `instr_sampling_percent` and `instr_sampler` options;\n  - pre-configured in `config[:instr_sampling_enabled]`;\n- `instr_sampling_percent` - (optional) `[Integer]`\n  - the percent of cases that should be instrumented;\n  - take an effect when `instr_sampling_enalbed` is true;\n  - works in tandem with `instr_sampling_enabled` and `instr_sampler` options;\n  - pre-configured in `config[:instr_sampling_percent]`;\n- `instr_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Instrument::Sampler\u003e]`\n  - percent-based log sampler that decides should be RQL case instrumented or not;\n  - works in tandem with `instr_sampling_enabled` and `instr_sampling_percent` options;\n  - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand\n    method so the algorithm error is ~(0%..13%);\n  - you can provide your own log sampler with bettter algorithm that should realize\n    `sampling_happened?(percent) =\u003e boolean` interface (see `RedisQueuedLocks::Instrument::Sampler` for example);\n  - pre-configured in `config[:instr_sampler]`;\n- `instr_sample_this` - (optional) `[Boolean]`\n  - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;\n  - makes sense when instrumentation sampling is enabled;\n  - `false` by default;\n- `block` - (optional) `[Block]`\n  - A block of code that should be executed after the successfully acquired lock.\n  - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);\n  - If block is **not passed** the obtained lock will be released after it's ttl;\n  - If you want the block to have a TTL too and this TTL to be the same as TTL of the lock\n    use `timed: true` option (`rql.lock(\"my_lock\", timed: true, ttl: 5_000) { ... }`)\n\nReturn value:\n\n- If block is passed the block's yield result will be returned:\n  ```ruby\n  result = rql.lock(\"my_lock\") { 1 + 1 }\n  result # =\u003e 2\n  ```\n- If block is not passed the lock information will be returned:\n  ```ruby\n  result = rql.lock(\"my_lock\")\n  result # =\u003e\n  {\n    ok: true,\n    result: {\n      lock_key: \"rql:lock:my_lock\",\n      acq_id: \"rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056\",\n      ts: 1711909612.653696,\n      ttl: 10000,\n      process: :lock_obtaining\n    }\n  }\n  ```\n- Lock information result:\n  - Signature: `[yield, Hash\u003cSymbol,Boolean|Hash\u003cSymbol,Numeric|String\u003e\u003e]`\n  - Format: `{ ok: true/false, result: \u003cSymbol|Hash\u003cSymbol,Hash\u003e\u003e }`;\n  - Includes the `:process` key that describes a logical type of the lock obtaining process. Possible values:\n    - `:lock_obtaining` - classic lock obtaining proces. Default behavior (`conflict_strategy: :wait_for_lock`);\n    - `:extendable_conflict_work_through` - reentrant lock acquiring process with lock's TTL extension. Suitable for `conflict_strategy: :extendable_work_through`;\n    - `:conflict_work_through` - reentrant lock acquiring process without lock's TTL extension. Suitable for `conflict_strategy: :work_through`;\n    - `:dead_locking` - current process tries to acquire a lock that is already acquired by them. Suitalbe for `conflict_startegy: :dead_locking`;\n    - For more details see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) readme section;\n  - For successful lock obtaining:\n    ```ruby\n    {\n      ok: true,\n      result: {\n        lock_key: String, # acquirerd lock key (\"rql:lock:your_lock_name\")\n        acq_id: String, # acquirer identifier (\"process_id/thread_id/fiber_id/ractor_id/identity\")\n        hst_id: String, # host identifier (\"process_id/thread_id/ractor_id/identity\")\n        ts: Float, # time (epoch) when lock was obtained (float, Time#to_f)\n        ttl: Integer, # lock's time to live in milliseconds (integer)\n        process: Symbol # which logical process has acquired the lock (:lock_obtaining, :extendable_conflict_work_through, :conflict_work_through, :conflict_dead_lock)\n      }\n    }\n    ```\n\n    ```ruby\n    # example:\n    {\n      ok: true,\n      result: {\n        lock_key: \"rql:lock:my_lock\",\n        acq_id: \"rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056\",\n        acq_id: \"rql:acq:26672/2280/2320/70ea5dbf10ea1056\",\n        ts: 1711909612.653696,\n        ttl: 10000,\n        process: :lock_obtaining # for custom conflict strategies may be: :conflict_dead_lock, :conflict_work_through, :extendable_conflict_work_through\n      }\n    }\n    ```\n  - For failed lock obtaining:\n    ```ruby\n    { ok: false, result: :timeout_reached }\n    { ok: false, result: :retry_count_reached }\n    { ok: false, result: :conflict_dead_lock } # see \u003cconflict_strategy\u003e option for details (:dead_locking strategy)\n    { ok: false, result: :fail_fast_no_try } # see \u003cfail_fast\u003e option\n    { ok: false, result: :fail_fast_after_try } # see \u003cfail_fast\u003e option\n    { ok: false, result: :unknown }\n    ```\n\nExamples:\n\n- obtain a lock:\n\n```ruby\nrql.lock(\"my_lock\") { print \"Hello!\" }\n```\n\n- obtain a lock with custom lock TTL:\n\n```ruby\nrql.lock(\"my_lock\", ttl: 5_000) { print \"Hello!\" } # for 5 seconds\n```\n\n- obtain a lock and limit the passed block of code TTL with lock's TTL:\n\n```ruby\nrql.lock(\"my_lock\", ttl: 5_000, timed: true) { sleep(4) }\n# =\u003e OK\n\nrql.lock(\"my_lock\", ttl: 5_000, timed: true) { sleep(6) }\n# =\u003e fails with RedisQueuedLocks::TimedLockTimeoutError\n```\n\n- infinite lock obtaining (no retry limit, no timeout limit):\n\n```ruby\nrql.lock(\"my_lock\", retry_count: nil, timeout: nil)\n```\n\n- try to obtain with a custom waiting timeout:\n\n```ruby\n# First Ruby Process:\nrql.lock(\"my_lock\", ttl: 5_000) { sleep(4) } # acquire a long living lock\n\n# Another Ruby Process:\nrql.lock(\"my_lock\", timeout: 2) # try to acquire but wait for a 2 seconds maximum\n# =\u003e\n{ ok: false, result: :timeout_reached }\n```\n\n- obtain a lock and immediatly continue working (the lock will live in the background in Redis with the passed ttl)\n\n```ruby\nrql.lock(\"my_lock\", ttl: 6_500) # blocks execution until the lock is obtained\nputs \"Let's go\" # will be called immediately after the lock is obtained\n```\n\n- add custom metadata to the lock (via `:meta` option):\n\n```ruby\nrql.lock(\"my_lock\", ttl: 123456, meta: { \"some\" =\u003e \"data\", key: 123.456 })\n\nrql.lock_info(\"my_lock\")\n# =\u003e\n{\n  \"lock_key\" =\u003e \"rql:lock:my_lock\",\n  \"acq_id\" =\u003e \"rql:acq:123/456/567/678/374dd74324\",\n  \"hst_id\" =\u003e \"rql:acq:123/456/678/374dd74324\",\n  \"ts\" =\u003e 123456789,\n  \"ini_ttl\" =\u003e 123456,\n  \"rem_ttl\" =\u003e 123440,\n  \"some\" =\u003e \"data\",\n  \"key\" =\u003e \"123.456\" # NOTE: returned as a raw string directly from Redis\n}\n```\n\n- (`:queue_ttl`) setting a short limit of time to the lock request queue position (if a process fails to acquire\n  the lock within this period of time (and before timeout/retry_count limits occurs of course) -\n  it's lock request will be moved to the end of queue):\n\n```ruby\nrql.lock(\"my_lock\", queue_ttl: 5, timeout: 10_000, retry_count: nil)\n# \"queue_ttl: 5\": 5 seconds time slot before the lock request moves to the end of queue;\n# \"timeout\" and \"retry_count\" is used as \"endless lock try attempts\" example to show the lock queue behavior;\n\n# lock queue: =\u003e\n[\n \"rql:acq:123/456/567/676/374dd74324\",\n \"rql:acq:123/456/567/677/374dd74322\", # \u003c- long living lock\n \"rql:acq:123/456/567/679/374dd74321\",\n \"rql:acq:123/456/567/683/374dd74322\", # \u003c== we are here\n \"rql:acq:123/456/567/685/374dd74329\", # some other waiting process\n]\n\n# ... some period of time (2 seconds later)\n# lock queue: =\u003e\n[\n \"rql:acq:123/456/567/677/374dd74322\", # \u003c- long living lock\n \"rql:acq:123/456/567/679/374dd74321\",\n \"rql:acq:123/456/567/683/374dd74322\", # \u003c== we are here\n \"rql:acq:123/456/567/685/374dd74329\", # some other waiting process\n]\n\n# ... some period of time (3 seconds later)\n# ... queue_ttl time limit is reached\n# lock queue: =\u003e\n[\n \"rql:acq:123/456/567/685/374dd74329\", # some other waiting process\n \"rql:acq:123/456/567/683/374dd74322\", # \u003c== we are here (moved to the end of the queue)\n]\n```\n\n- obtain a lock in `:random` way (with `:random` strategy): in `:random` strategy\n  any acquirer from the lcok queue can obtain the lock regardless of the position in the lock queue;\n\n```ruby\n# Current Process (process#1)\nrql.lock('my_lock', ttl: 2_000, access_strategy: :random)\n# =\u003e holds the lock\n\n# Another Process (process#2)\nrql.lock('my_lock', retry_delay: 7000, ttl: 4000, access_strategy: :random)\n# =\u003e the lock is not free, stay in a queue and retry...\n\n# Another Process (process#3)\nrql.lock('my_lock', retry_delay: 3000, ttl: 3000, access_strategy: :random)\n# =\u003e the lock is not free, stay in a queue and retry...\n\n# lock queue:\n[\n \"rql:acq:123/456/567/677/374dd74322\", # process#1 (holds the lock)\n \"rql:acq:123/456/567/679/374dd74321\", # process#2 (waiting for the lock, in retry)\n \"rql:acq:123/456/567/683/374dd74322\", # process#3 (waiting for the lock, in retry)\n]\n\n# ... some period of time\n# -\u003e process#1 =\u003e released the lock;\n# -\u003e process#2 =\u003e delayed retry, waiting;\n# -\u003e process#3 =\u003e preparing for retry (the delay is over);\n# lock queue:\n[\n \"rql:acq:123/456/567/679/374dd74321\", # process#2 (waiting for the lock, DELAYED)\n \"rql:acq:123/456/567/683/374dd74322\", # process#3 (trying to obtain the lock, RETRYING now)\n]\n\n# ... some period of time\n# -\u003e process#2 =\u003e didn't have time to obtain the lock, delayed retry;\n# -\u003e process#3 =\u003e holds the lock;\n# lock queue:\n[\n \"rql:acq:123/456/567/679/374dd74321\", # process#2 (waiting for the lock, DELAYED)\n \"rql:acq:123/456/567/683/374dd74322\", # process#3 (holds the lock)\n]\n\n# `process#3` is the last in queue, but has acquired the lock because his lock request \"randomly\" came first;\n```\n\n---\n\n#### #lock! - exceptional lock obtaining\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- `#lock!` - exceptional lock obtaining;\n- fails when (and with):\n  - (`RedisQueuedLocks::LockAlreadyObtainedError`) when `fail_fast` is `true` and lock is already obtained;\n  - (`RedisQueuedLocks::LockAcquirementTimeoutError`) `timeout` limit reached before lock is obtained;\n  - (`RedisQueuedLocks::LockAcquirementRetryLimitError`) `retry_count` limit reached before lock is obtained;\n  - (`RedisQueuedLocks::ConflictLockObtainError`) when `conflict_strategy: :dead_locking` is used and the \"same-process-dead-lock\" is happened (see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) for details);\n\n```ruby\ndef lock!(\n  lock_name,\n  ttl: config[:default_lock_ttl],\n  queue_ttl: config[:default_queue_ttl],\n  timeout: config[:try_to_lock_timeout],\n  timed: config[:is_timed_by_default],\n  retry_count: config[:retry_count],\n  retry_delay: config[:retry_delay],\n  retry_jitter: config[:retry_jitter],\n  fail_fast: false,\n  identity: uniq_identity,\n  meta: nil,\n  detailed_acq_timeout_error: config[:detailed_acq_timeout_error]\n  logger: config[:logger],\n  log_lock_try: config[:log_lock_try],\n  instrument: nil,\n  instrumenter: config[:instrumenter],\n  access_strategy: config[:default_access_strategy],\n  conflict_strategy: config[:default_conflict_strategy],\n  log_sampling_enabled: config[:log_sampling_enabled],\n  log_sampling_percent: config[:log_sampling_percent],\n  log_sampler: config[:log_sampler],\n  log_sample_this: false,\n  instr_sampling_enabled: config[:instr_sampling_enabled],\n  instr_sampling_percent: config[:instr_sampling_percent],\n  instr_sampler: config[:instr_sampler],\n  instr_sample_this: false,\n  \u0026block\n)\n```\n\nSee `#lock` method [documentation](#lock---obtain-a-lock).\n\n---\n\n#### #lock_info\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get the lock information;\n- returns `nil` if lock does not exist;\n- lock data (`Hash\u003cString,String|Integer\u003e`):\n  - `\"lock_key\"` - `string` - lock key in redis;\n  - `\"acq_id\"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity);\n  - `\"hst_id\"` - `string` - host identifier (process_id/thread_id/ractor_id/identity);\n  - `\"ts\"` - `numeric`/`epoch` - the time when lock was obtained;\n  - `\"init_ttl\"` - `integer` - (milliseconds) initial lock key ttl;\n  - `\"rem_ttl\"` - `integer` - (milliseconds) remaining lock key ttl;\n  - `\u003ccustom metadata\u003e`- `string`/`integer` - custom metadata passed to the `lock`/`lock!` methods via `meta:` keyword argument (see [lock]((#lock---obtain-a-lock)) method documentation);\n  - additional keys for **reentrant locks** and **extendable reentrant locks**:\n    - for any type of reentrant locks:\n      - `\"spc_cnt\"` - `integer` - how many times the lock was obtained as reentrant lock;\n    - for non-extendable reentrant locks:\n      - `\"l_spc_ts\"` - `numeric`/`epoch` - timestamp of the last **non-extendable** reentrant lock obtaining;\n    - for extendalbe reentrant locks:\n      - `\"spc_ext_ttl\"` - `integer` - (milliseconds) sum of TTL of the each **extendable** reentrant lock (the total TTL extension time);\n      - `\"l_spc_ext_ini_ttl\"` - `integer` - (milliseconds) TTL of the last reentrant lock;\n      - `\"l_spc_ext_ts\"` - `numeric`/`epoch` - timestamp of the last extendable reentrant lock obtaining;\n\n```ruby\n# \u003cwithout custom metadata\u003e\nrql.lock_info(\"your_lock_name\")\n\n# =\u003e\n{\n  \"lock_key\" =\u003e \"rql:lock:your_lock_name\",\n  \"acq_id\" =\u003e \"rql:acq:123/456/567/678/374dd74324\",\n  \"hst_id\" =\u003e \"rql:acq:123/456/678/374dd74324\",\n  \"ts\" =\u003e 123456789.12345,\n  \"ini_ttl\" =\u003e 5_000,\n  \"rem_ttl\" =\u003e 4_999\n}\n```\n\n```ruby\n# \u003cwith custom metadata\u003e\nrql.lock(\"your_lock_name\", meta: { \"kek\" =\u003e \"pek\", \"bum\" =\u003e 123 })\nrql.lock_info(\"your_lock_name\")\n\n# =\u003e\n{\n  \"lock_key\" =\u003e \"rql:lock:your_lock_name\",\n  \"acq_id\" =\u003e \"rql:acq:123/456/567/678/374dd74324\",\n  \"hst_id\" =\u003e \"rql:acq:123/456/678/374dd74324\",\n  \"ts\" =\u003e 123456789.12345,\n  \"ini_ttl\" =\u003e 5_000,\n  \"rem_ttl\" =\u003e 4_999,\n  \"kek\" =\u003e \"pek\",\n  \"bum\" =\u003e \"123\" # NOTE: returned as a raw string directly from Redis\n}\n```\n\n```ruby\n# \u003cfor reentrant locks\u003e\n# (see `conflict_strategy:` kwarg attribute of #lock/#lock! methods and `config.default_conflict_strategy` config)\n\nrql.lock(\"your_lock_name\", ttl: 5_000)\nrql.lock(\"your_lock_name\", ttl: 3_000)\nrql.lock(\"your_lock_name\", ttl: 2_000)\nrql.lock_info(\"your_lock_name\")\n\n# =\u003e\n{\n  \"lock_key\" =\u003e \"rql:lock:your_lock_name\",\n  \"acq_id\" =\u003e \"rql:acq:123/456/567/678/374dd74324\",\n  \"hst_id\" =\u003e \"rql:acq:123/456/678/374dd74324\",\n  \"ts\" =\u003e 123456789.12345,\n  \"ini_ttl\" =\u003e 5_000,\n  \"rem_ttl\" =\u003e 9_444,\n  # ==\u003e keys for any type of reentrant lock:\n  \"spc_count\" =\u003e 2, # how many times the lock was obtained as reentrant lock\n  # ==\u003e keys for extendable reentarnt locks with `:extendable_work_through` strategy:\n  \"spc_ext_ttl\" =\u003e 5_000, # sum of TTL of the each \u003cextendable\u003e reentrant lock (3_000 + 2_000)\n  \"l_spc_ext_ini_ttl\" =\u003e 2_000, # TTL of the last \u003cextendable\u003e reentrant lock\n  \"l_spc_ext_ts\" =\u003e  123456792.12345, # timestamp of the last \u003cextendable\u003e reentrant lock obtaining\n  # ==\u003e keys for non-extendable locks with `:work_through` strategy:\n  \"l_spc_ts\" =\u003e 123456.789 # timestamp of the last \u003cnon-extendable\u003e reentrant lock obtaining\n}\n```\n\n---\n\n#### #queue_info\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\nReturns an information about the required lock queue by the lock name. The result\nrepresnts the ordered lock request queue that is ordered by score (Redis Sets) and shows\nlock acquirers and their position in queue. Async nature with redis communcation can lead\nthe situation when the queue becomes empty during the queue data extraction. So sometimes\nyou can receive the lock queue info with empty queue value (an empty array).\n\n- get the lock queue information;\n- queue represents the ordered set of lock key reqests:\n  - set is ordered by score in ASC manner (inside the Redis Set);\n  - score is represented as a timestamp when the lock request was made;\n  - represents the acquirer identifier and their score as an array of hashes;\n- returns `nil` if lock queue does not exist;\n- lock queue data (`Hash\u003cString,String|Array\u003cHash\u003cString|Numeric\u003e\u003e`):\n  - `\"lock_queue\"` - `string` - lock queue key in redis;\n  - `\"queue\"` - `array` - an array of lock requests (array of hashes):\n    - `\"acq_id\"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity by default);\n    - `\"score\"` - `float`/`epoch` - time when the lock request was made (epoch);\n\n```ruby\nrql.queue_info(\"your_lock_name\")\n\n# =\u003e\n{\n  \"lock_queue\" =\u003e \"rql:lock_queue:your_lock_name\",\n  \"queue\" =\u003e [\n    { \"acq_id\" =\u003e \"rql:acq:123/456/567/678/fa76df9cc2\", \"score\" =\u003e 1711606640.540842},\n    { \"acq_id\" =\u003e \"rql:acq:123/567/456/679/c7bfcaf4f9\", \"score\" =\u003e 1711606640.540906},\n    { \"acq_id\" =\u003e \"rql:acq:555/329/523/127/7329553b11\", \"score\" =\u003e 1711606640.540963},\n    # ...etc\n  ]\n}\n```\n\n---\n\n#### #locked?\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- is the lock obtaied or not?\n\n```ruby\nrql.locked?(\"your_lock_name\") # =\u003e true/false\n```\n\n---\n\n#### #queued?\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- is the lock queued for obtain / has requests for obtain?\n\n```ruby\nrql.queued?(\"your_lock_name\") # =\u003e true/false\n```\n\n---\n\n#### #unlock - release a lock\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- release the concrete lock with lock request queue;\n- queue will be relased first;\n- has an alias: `#release_lock`;\n- accepts:\n  - `lock_name` - (required) `[String]` - the lock name that should be released.\n  - `:logger` - (optional) `[::Logger,#debug]`\n    - custom logger object;\n    - pre-configured in `config[:logger]`;\n  - `:instrumenter` - (optional) `[#notify]`\n    - custom instrumenter object;\n    - pre-configured in `config[:instrumetner]`;\n  - `:instrument` - (optional) `[NilClass,Any]`;\n    - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;\n    - `nil` by default (no additional data);\n  - `:log_sampling_enabled` - (optional) `[Boolean]`\n    - enables **log sampling**;\n    - pre-configured in `config[:log_sampling_enabled]`;\n  - `:log_sampling_percent` - (optional) `[Integer]`\n    - **log sampling**:the percent of cases that should be logged;\n    - pre-configured in `config[:log_sampling_percent]`;\n  - `:log_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Logging::Sampler\u003e]`\n    - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;\n    - pre-configured in `config[:log_sampler]`;\n  - `log_sample_this` - (optional) `[Boolean]`\n    - marks the method that everything should be logged despite the enabled log sampling;\n    - makes sense when log sampling is enabled;\n    - `false` by default;\n  - `:instr_sampling_enabled` - (optional) `[Boolean]`\n    - enables **instrumentaion sampling**;\n    - pre-configured in `config[:instr_sampling_enabled]`;\n  - `instr_sampling_percent` - (optional) `[Integer]`\n    - the percent of cases that should be instrumented;\n    - pre-configured in `config[:instr_sampling_percent]`;\n  - `instr_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Instrument::Sampler\u003e]`\n    - percent-based log sampler that decides should be RQL case instrumented or not;\n    - pre-configured in `config[:instr_sampler]`;\n  - `instr_sample_this` - (optional) `[Boolean]`\n    - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;\n    - makes sense when instrumentation sampling is enabled;\n    - `false` by default;\n- if you try to unlock non-existent lock you will receive `ok: true` result with operation timings\n  and `:nothing_to_release` result factor inside;\n\nReturn:\n- `[Hash\u003cSymbol,Boolean|Hash\u003cSymbol,Numeric|String|Symbol\u003e\u003e]` (`{ ok: true/false, result: Hasn }`);\n- `:result` format;\n  - `:rel_time` - `Float` - time spent to process redis commands (in seconds);\n  - `:rel_key` - `String` - released lock key (RedisQueudLocks-internal lock key name from Redis);\n  - `:rel_queue` - `String` - released lock queue key (RedisQueuedLocks-internal queue key name from Redis);\n  - `:queue_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required queue does not exist);\n  - `:lock_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required lock does not exist);\n\nConsider that `lock_res` and `queue_res` can have different value because of the async nature of invoked Redis'es commands.\n\n```ruby\nrql.unlock(\"your_lock_name\")\n\n# =\u003e\n{\n  ok: true,\n  result: {\n    rel_time: 0.02, # time spent to lock release (in seconds)\n    rel_key: \"rql:lock:your_lock_name\", # released lock key\n    rel_queue: \"rql:lock_queue:your_lock_name\", # released lock key queue\n    queue_res: :released, # or :nothing_to_release\n    lock_res: :released # or :nothing_to_release\n  }\n}\n```\n\n---\n\n#### #clear_locks - release all locks and lock queues\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- release all obtained locks and related lock request queues;\n- queues will be released first;\n- has an alias: `#release_locks`;\n- accepts:\n  - `:batch_size` - (optional) `[Integer]`\n    - the size of batch of locks and lock queus that should be cleared under the one pipelined redis command at once;\n    - pre-configured in `config[:lock_release_batch_size]`;\n  - `:logger` - (optional) `[::Logger,#debug]`\n    - custom logger object;\n    - pre-configured value in `config[:logger]`;\n  - `:instrumenter` - (optional) `[#notify]`\n    - custom instrumenter object;\n    - pre-configured value in `config[:isntrumenter]`;\n  - `:instrument` - (optional) `[NilClass,Any]`\n    - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;\n  - `:log_sampling_enabled` - (optional) `[Boolean]`\n    - enables **log sampling**;\n    - pre-configured in `config[:log_sampling_enabled]`;\n  - `:log_sampling_percent` - (optional) `[Integer]`\n    - **log sampling**:the percent of cases that should be logged;\n    - pre-configured in `config[:log_sampling_percent]`;\n  - `:log_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Logging::Sampler\u003e]`\n    - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;\n    - pre-configured in `config[:log_sampler]`;\n  - `log_sample_this` - (optional) `[Boolean]`\n    - marks the method that everything should be logged despite the enabled log sampling;\n    - makes sense when log sampling is enabled;\n    - `false` by default;\n  - `:instr_sampling_enabled` - (optional) `[Boolean]`\n    - enables **instrumentaion sampling**;\n    - pre-configured in `config[:instr_sampling_enabled]`;\n  - `instr_sampling_percent` - (optional) `[Integer]`\n    - the percent of cases that should be instrumented;\n    - pre-configured in `config[:instr_sampling_percent]`;\n  - `instr_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Instrument::Sampler\u003e]`\n    - percent-based log sampler that decides should be RQL case instrumented or not;\n    - pre-configured in `config[:instr_sampler]`;\n  - `instr_sample_this` - (optional) `[Boolean]`\n    - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;\n    - makes sense when instrumentation sampling is enabled;\n    - `false` by default;\n- returns:\n  - `[Hash\u003cSymbol,Numeric\u003e]` - Format: `{ ok: true, result: Hash\u003cSymbol,Numeric\u003e }`;\n  - result data:\n    - `:rel_time` - `Numeric` - time spent to release all locks and related queus;\n    - `:rel_key_cnt` - `Integer` - the number of released Redis keys (queues+locks);\n\n```ruby\nrql.clear_locks\n\n# =\u003e\n{\n  ok: true,\n  result: {\n    rel_time: 3.07,\n    rel_key_cnt: 1234\n  }\n}\n```\n\n---\n\n#### #extend_lock_ttl\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- extends lock ttl by the required number of milliseconds;\n- expects the lock name and the number of milliseconds;\n- accepts:\n  - `lock_name` - (required) `[String]`\n    - the lock name which ttl should be extended;\n  - `milliseconds` - (required) `[Integer]`\n    - how many milliseconds should be added to the lock's TTL;\n  - `:instrumenter` - (optional) `[#notify]`\n    - custom instrumenter object;\n    - pre-configured in `config[:instrumetner]`;\n  - `:instrument` - (optional) `[NilClass,Any]`;\n    - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;\n    - `nil` by default (no additional data);\n  - `:logger` - (optional) `[::Logger,#debug]`\n    - custom logger object;\n    - pre-configured in `config[:logger]`;\n  - `:log_sampling_enabled` - (optional) `[Boolean]`\n    - enables **log sampling**;\n    - pre-configured in `config[:log_sampling_enabled]`;\n  - `:log_sampling_percent` - (optional) `[Integer]`\n    - **log sampling**:the percent of cases that should be logged;\n    - pre-configured in `config[:log_sampling_percent]`;\n  - `:log_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Logging::Sampler\u003e]`\n    - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;\n    - pre-configured in `config[:log_sampler]`;\n  - `log_sample_this` - (optional) `[Boolean]`\n    - marks the method that everything should be logged despite the enabled log sampling;\n    - makes sense when log sampling is enabled;\n    - `false` by default;\n  - `:instr_sampling_enabled` - (optional) `[Boolean]`\n    - enables **instrumentaion sampling**;\n    - pre-configured in `config[:instr_sampling_enabled]`;\n  - `instr_sampling_percent` - (optional) `[Integer]`\n    - the percent of cases that should be instrumented;\n    - pre-configured in `config[:instr_sampling_percent]`;\n  - `instr_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Instrument::Sampler\u003e]`\n    - percent-based log sampler that decides should be RQL case instrumented or not;\n    - pre-configured in `config[:instr_sampler]`;\n  - `instr_sample_this` - (optional) `[Boolean]`\n    - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;\n    - makes sense when instrumentation sampling is enabled;\n    - `false` by default;\n- returns `{ ok: true, result: :ttl_extended }` when ttl is extended;\n- returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during\n  some steps of invocation (see **Important** section below);\n- **Important**:\n  - the method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension;\n  - the method consists of two commands:\n    - (1) read current pttl;\n    - (2) set new ttl that is calculated as \"current pttl + additional milliseconds\";\n  - the method uses Redis'es **CAS** (check-and-set) behavior;\n  - what can happen during these steps:\n    - lock is expired between commands or before the first command;\n    - lock is expired before the second command;\n    - lock is expired AND newly acquired by another process (so you will extend the\n      totally new lock with fresh PTTL);\n  - use it at your own risk and consider the async nature when calling this method;\n\n```ruby\nrql.extend_lock_ttl(\"my_lock\", 5_000) # NOTE: add 5_000 milliseconds\n\n# =\u003e `ok` case\n{ ok: true, result: :ttl_extended }\n\n# =\u003e `failed` case\n{ ok: false, result: :async_expire_or_no_lock }\n```\n\n---\n\n#### #locks - get list of obtained locks\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get list of obtained locks;\n- uses redis `SCAN` under the hood;\n- accepts:\n  - `:scan_size` - `Integer` - (`config[:key_extraction_batch_size]` by default);\n  - `:with_info` - `Boolean` - `false` by default (for details see [#locks_info](#locks_info---get-list-of-locks-with-their-info));\n- returns:\n  - `Set\u003cString\u003e` (for `with_info: false`);\n  - `Set\u003cHash\u003cSymbol,Any\u003e\u003e` (for `with_info: true`). See [#locks_info](#locks_info---get-list-of-locks-with-their-info) for details;\n\n```ruby\nrql.locks # or rql.locks(scan_size: 123)\n\n=\u003e\n#\u003cSet:\n {\"rql:lock:locklock75\",\n  \"rql:lock:locklock9\",\n  \"rql:lock:locklock108\",\n  \"rql:lock:locklock7\",\n  \"rql:lock:locklock48\",\n  \"rql:lock:locklock104\",\n  \"rql:lock:locklock13\",\n  \"rql:lock:locklock62\",\n  \"rql:lock:locklock80\",\n  \"rql:lock:locklock28\",\n  ...}\u003e\n```\n\n---\n\n#### #queues - get list of lock request queues\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get list of lock request queues;\n- uses redis `SCAN` under the hood;\n- accepts\n  - `:scan_size` - `Integer` - (`config[:key_extraction_batch_size]` by default);\n  - `:with_info` - `Boolean` - `false` by default (for details see [#queues_info](#queues_info---get-list-of-queues-with-their-info));\n- returns:\n  - `Set\u003cString\u003e` (for `with_info: false`);\n  - `Set\u003cHash\u003cSymbol,Any\u003e\u003e` (for `with_info: true`). See [#locks_info](#locks_info---get-list-of-locks-with-their-info) for details;\n\n```ruby\nrql.queues # or rql.queues(scan_size: 123)\n\n=\u003e\n#\u003cSet:\n {\"rql:lock_queue:locklock75\",\n  \"rql:lock_queue:locklock9\",\n  \"rql:lock_queue:locklock108\",\n  \"rql:lock_queue:locklock7\",\n  \"rql:lock_queue:locklock48\",\n  \"rql:lock_queue:locklock104\",\n  \"rql:lock_queue:locklock13\",\n  \"rql:lock_queue:locklock62\",\n  \"rql:lock_queue:locklock80\",\n  \"rql:lock_queue:locklock28\",\n  ...}\u003e\n```\n\n---\n\n#### #keys - get list of taken locks and queues\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get list of taken locks and queues;\n- uses redis `SCAN` under the hood;\n- accepts:\n  `:scan_size` - `Integer` - (`config[:key_extraction_batch_size]` by default);\n- returns: `Set\u003cString\u003e`\n\n```ruby\nrql.keys # or rql.keys(scan_size: 123)\n\n=\u003e\n#\u003cSet:\n {\"rql:lock_queue:locklock75\",\n  \"rql:lock_queue:locklock9\",\n  \"rql:lock:locklock9\",\n  \"rql:lock_queue:locklock108\",\n  \"rql:lock_queue:locklock7\",\n  \"rql:lock:locklock7\",\n  \"rql:lock_queue:locklock48\",\n  \"rql:lock_queue:locklock104\",\n  \"rql:lock:locklock104\",\n  \"rql:lock_queue:locklock13\",\n  \"rql:lock_queue:locklock62\",\n  \"rql:lock_queue:locklock80\",\n  \"rql:lock:locklock80\",\n  \"rql:lock_queue:locklock28\",\n  ...}\u003e\n```\n\n---\n\n#### #locks_info - get list of locks with their info\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get list of locks with their info;\n- uses redis `SCAN` under the hod;\n- accepts `scan_size:`/`Integer` option (`config[:key_extraction_batch_size]` by default);\n- returns `Set\u003cHash\u003cSymbol,Any\u003e\u003e` (see [#lock_info](#lock_info) and examples below for details).\n  - contained data: `{ lock: String, status: Symbol, info: Hash\u003cString,Any\u003e }`;\n  - `:lock` - `String` - lock key in Redis;\n  - `:status` - `Symbol`- `:released` or `:alive`\n    - the lock may become relased durign the lock info extraction process;\n    - `:info` for `:released` keys is empty (`{}`);\n  - `:info` - `Hash\u003cString,Any\u003e`\n      - lock data stored in the lock key in Redis;\n      - See [#lock_info](#lock_info) for details;\n\n```ruby\nrql.locks_info # or rql.locks_info(scan_size: 123)\n\n# =\u003e\n=\u003e #\u003cSet:\n {{:lock=\u003e\"rql:lock:some-lock-123\",\n   :status=\u003e:alive,\n   :info=\u003e{\n    \"acq_id\"=\u003e\"rql:acq:41478/4320/4340/4360/848818f09d8c3420\",\n    \"hst_id\"=\u003e\"rql:hst:41478/4320/4360/848818f09d8c3420\"\n    \"ts\"=\u003e1711607112.670343,\n    \"ini_ttl\"=\u003e15000,\n    \"rem_ttl\"=\u003e13998}},\n  {:lock=\u003e\"rql:lock:some-lock-456\",\n   :status=\u003e:released,\n   :info=\u003e{},\n  ...}\u003e\n```\n\n---\n\n#### #queues_info - get list of queues with their info\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get list of queues with their info;\n- uses redis `SCAN` under the hod;\n- accepts `scan_size:`/`Integer` option (`config[:key_extraction_batch_size]` by default);\n- returns `Set\u003cHash\u003cSymbol,Any\u003e\u003e` (see [#queue_info](#queue_info) and examples below for details).\n  - contained data: `{ queue: String, requests: Array\u003cHash\u003cString,Any\u003e\u003e }`\n  - `:queue` - `String` - lock key queue in Redis;\n  - `:requests` - `Array\u003cHash\u003cString,Any\u003e\u003e` - lock requests in the que with their acquirer id and score.\n\n```ruby\nrql.queues_info # or rql.qeuues_info(scan_size: 123)\n\n=\u003e #\u003cSet:\n {{:queue=\u003e\"rql:lock_queue:some-lock-123\",\n   :requests=\u003e\n    [{\"acq_id\"=\u003e\"rql:acq:38529/4500/4520/4360/66093702f24a3129\", \"score\"=\u003e1711606640.540842},\n     {\"acq_id\"=\u003e\"rql:acq:38529/4580/4600/4360/66093702f24a3129\", \"score\"=\u003e1711606640.540906},\n     {\"acq_id\"=\u003e\"rql:acq:38529/4620/4640/4360/66093702f24a3129\", \"score\"=\u003e1711606640.5409632}]},\n  {:queue=\u003e\"rql:lock_queue:some-lock-456\",\n   :requests=\u003e\n    [{\"acq_id\"=\u003e\"rql:acq:38529/4380/4400/4360/66093702f24a3129\", \"score\"=\u003e1711606640.540722},\n     {\"acq_id\"=\u003e\"rql:acq:38529/4420/4440/4360/66093702f24a3129\", \"score\"=\u003e1711606640.5407748},\n     {\"acq_id\"=\u003e\"rql:acq:38529/4460/4480/4360/66093702f24a3129\", \"score\"=\u003e1711606640.540808}]},\n  ...}\u003e\n```\n---\n\n#### #clear_dead_requests\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\nIn some cases your lock requests may become \"dead\". It means that your lock request lives in lock queue in Redis without\nany processing. It can happen when your processs that are enqueeud to the lock queue is failed unexpectedly (for some reason)\nbefore the lock acquire moment occurs and when no any other process does not need this lock anymore.\nFor this case your lock reuquest will be cleared only when any process will try\nto acquire this lock again (cuz lock acquirement triggers the removement of expired requests).\n\nIn order to help with these dead requests you may periodically call `#clear_dead_requests`\nwith corresponding `:dead_ttl` option, that is pre-configured by default via `config[:dead_request_ttl]`.\n\n`:dead_ttl` option is required because of it is no any **fast** and **resource-free** way to understand which request\nis dead now and is it really dead cuz each request queue can host their requests with\na custom queue ttl for each request differently.\n\nAccepts:\n- `:dead_ttl` - (optional) `[Integer]`\n  - lock request ttl after which a lock request is considered dead;\n  - has a preconfigured value in `config[:dead_request_ttl]` (1 day by default);\n- `:sacn_size` - (optional) `[Integer]`\n  - the batch of scanned keys for Redis'es SCAN command;\n  - has a preconfigured valie in `config[:lock_release_batch_size]`;\n- `:logger` - (optional) `[::Logger,#debug]`\n  - custom logger object;\n  - pre-configured in `config[:logger]`;\n- `:instrumenter` - (optional) `[#notify]`\n  - custom instrumenter object;\n  - pre-configured in `config[:isntrumenter]`;\n- `:instrument` - (optional) `[NilClass,Any]`\n  - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;\n  - `nil` by default (no additional data);\n- `:log_sampling_enabled` - (optional) `[Boolean]`\n  - enables **log sampling**;\n  - pre-configured in `config[:log_sampling_enabled]`;\n- `:log_sampling_percent` - (optional) `[Integer]`\n  - **log sampling**:the percent of cases that should be logged;\n  - pre-configured in `config[:log_sampling_percent]`;\n- `:log_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Logging::Sampler\u003e]`\n  - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;\n  - pre-configured in `config[:log_sampler]`;\n- `log_sample_this` - (optional) `[Boolean]`\n  - marks the method that everything should be logged despite the enabled log sampling;\n  - makes sense when log sampling is enabled;\n  - `false` by default;\n- `:instr_sampling_enabled` - (optional) `[Boolean]`\n  - enables **instrumentaion sampling**;\n  - pre-configured in `config[:instr_sampling_enabled]`;\n- `instr_sampling_percent` - (optional) `[Integer]`\n  - the percent of cases that should be instrumented;\n  - pre-configured in `config[:instr_sampling_percent]`;\n- `instr_sampler` - (optional) `[#sampling_happened?,Module\u003cRedisQueuedLocks::Instrument::Sampler\u003e]`\n  - percent-based log sampler that decides should be RQL case instrumented or not;\n  - pre-configured in `config[:instr_sampler]`;\n- `instr_sample_this` - (optional) `[Boolean]`\n  - marks the method that everything should be instrumneted despite the enabled instrumentation sampling;\n  - makes sense when instrumentation sampling is enabled;\n  - `false` by default;\n\nReturns: `{ ok: true, processed_queues: Set\u003cString\u003e }` returns the list of processed lock queues;\n\n```ruby\nrql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds\n\n# =\u003e\n{\n  ok: true,\n  processed_queues: [\n    \"rql:lock_queue:some-lock-123\",\n    \"rql:lock_queue:some-lock-456\",\n    \"rql:lock_queue:your-other-lock\",\n    ...\n  ]\n}\n```\n\n---\n\n#### #current_acquirer_id\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get the current acquirer identifier in RQL notation that you can use for debugging purposes during the lock analyzation;\n- acquirer identifier format:\n  ```ruby\n    \"rql:acq:#{process_id}/#{thread_id}/#{fiber_id}/#{ractor_id}/#{identity}\"\n  ```\n- because of the moment that `#lock`/`#lock!` gives you a possibility to customize `process_id`,\n  `fiber_id`, `thread_id`, `ractor_id` and `unique identity` identifiers the `#current_acquirer_id` method provides this possibility too;\n\nAccepts:\n\n- `process_id:` - (optional) `[Integer,Any]`\n  - `::Process.pid` by default;\n- `thread_id:` - (optional) `[Integer,Any]`;\n  - `::Thread.current.object_id` by default;\n- `fiber_id:` - (optional) `[Integer,Any]`;\n  - `::Fiber.current.object_id` by default;\n- `ractor_id:` - (optional) `[Integer,Any]`;\n  - `::Ractor.current.object_id` by default;\n- `identity:` - (optional) `[String,Any]`;\n  - this value is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`;\n  - this value can be accessed from `RedisQueuedLock::Client#uniq_identity`;\n  - [Configuration](#configuration) documentation: see `config[:uniq_identifier]`;\n  - [#lock](#lock---obtain-a-lock) method documentation: see `uniq_identifier`;\n\n```ruby\nrql.current_acquirer_id\n\n# =\u003e\n\"rql:acq:38529/4500/4520/4360/66093702f24a3129\"\n```\n\n---\n\n#### #current_host_id\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- get a current host identifier in RQL notation that you can use for debugging purposes during the lock analyzis;\n- the host is a ruby worker (a combination of process/thread/ractor/identity) that is alive and can obtain locks;\n- the host is limited to `process`/`thread`/`ractor` (without `fiber`) combination cuz we have no abilities to extract\n  all fiber objects from the current ruby process when at least one ractor object is defined (**ObjectSpace** loses\n  abilities to extract `Fiber` and `Thread` objects after the any ractor is created) (`Thread` objects are analyzed\n  via `Thread.list` API which does not lose their abilites);\n- host identifier format:\n  ```ruby\n    \"rql:hst:#{process_id}/#{thread_id}/#{ractor_id}/#{uniq_identity}\"\n  ```\n- because of the moment that `#lock`/`#lock!` gives you a possibility to customize `process_id`,\n  `fiber_id`, `thread_id`, `ractor_id` and `unique identity` identifiers the `#current_host_id` method provides this possibility too\n  (except the `fiber_id` correspondingly);\n\nAccepts:\n\n- `process_id:` - (optional) `[Integer,Any]`\n  - `::Process.pid` by default;\n- `thread_id:` - (optional) `[Integer,Any]`;\n  - `::Thread.current.object_id` by default;\n- `ractor_id:` - (optional) `[Integer,Any]`;\n  - `::Ractor.current.object_id` by default;\n- `identity:` - (optional) `[String]`;\n  - this value is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`;\n  - this value can be accessed from `RedisQueuedLock::Client#uniq_identity`;\n  - [Configuration](#configuration) documentation: see `config[:uniq_identifier]`;\n  - [#lock](#lock---obtain-a-lock) method documentation: see `uniq_identifier`;\n\n```ruby\nrql.current_host_id\n\n# =\u003e\n\"rql:acq:38529/4500/4360/66093702f24a3129\"\n```\n\n---\n\n#### #possible_host_ids\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- return the list (`Array\u003cString\u003e`) of possible host identifiers that can be reached from the current ractor;\n- the host is a ruby worker (a combination of process/thread/ractor/identity) that is alive and can obtain locks;\n- the host is limited to `process`/`thread`/`ractor` (without `fiber`) combination cuz we have no abilities to extract\n  all fiber objects from the current ruby process when at least one ractor object is defined (**ObjectSpace** loses\n  abilities to extract `Fiber` and `Thread` objects after the any ractor is created) (`Thread` objects are analyzed\n  via `Thread.list` API which does not lose their abilites);\n- host identifier format:\n  ```ruby\n    \"rql:hst:#{process_id}/#{thread_id}/#{ractor_id}/#{uniq_identity}\"\n  ```\n\nAccepts:\n\n- `identity` - (optional) `[String]`;\n  - this value is calculated once during `RedisQueuedLock::Client` instantiation and stored in `@uniq_identity`;\n  - this value can be accessed from `RedisQueuedLock::Client#uniq_identity`;\n  - [Configuration](#configuration) documentation: see `config[:uniq_identifier]`;\n  - [#lock](#lock---obtain-a-lock) method documentation: see `uniq_identifier`;\n\n```ruby\nrql.possible_host_ids\n\n# =\u003e\n[\n  \"rql:hst:18814/2300/2280/5ce0c4582fc59c06\", # process id / thread id / ractor id / uniq identity\n  \"rql:hst:18814/2320/2280/5ce0c4582fc59c06\", # ...\n  \"rql:hst:18814/2340/2280/5ce0c4582fc59c06\", # ...\n  \"rql:hst:18814/2360/2280/5ce0c4582fc59c06\", # ...\n  \"rql:hst:18814/2380/2280/5ce0c4582fc59c06\", # ...\n  \"rql:hst:18814/2400/2280/5ce0c4582fc59c06\"\n]\n```\n---\n\n## Swarm Mode and Zombie Locks\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n\u003e Eliminate zombie locks with a swarm.\n\n**This documentation section is in progress!** (see the changelog and the usage preview for details at this moment)\n\n[(work and usage preview (temporary example-based docs))](#work-and-usage-preview-temporary-example-based-docs)\n\n- [How to Swarm](#how-to-swarm)\n  - [configuration](#)\n  - [swarm_status](#swarm_status)\n  - [swarm_info](#swarm_info)\n  - [swarmize!](#swarmize!)\n  - [deswarmize!](#deswarmize!)\n  - [probe_hosts](#probe_hosts)\n  - [flush_zobmies](#flush_zombies)\n- [zombies_info](#zombies_info)\n- [zombie_locks](#zombie_locks)\n- [zombie_hosts](#zombie_hosts)\n- [zombie_acquirers](#zombie_acquirers)\n\n\u003chr\u003e\n\n#### Work and Usage Preview (temporary example-based docs)\n\n\u003csup\u003e\\[[back to top](#swarm-mode-and-zombie-locks)\\]\u003c/sup\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003econfiguration\u003c/summary\u003e\n\n  ```ruby\n  redis_client = RedisClient.config.new_pool # NOTE: provide your own RedisClient instance\n\n  clinet = RedisQueuedLocks::Client.new(redis_client) do |config|\n    # NOTE: auto-swarm your RQL client after initalization (run swarm elements and their supervisor)\n    config.swarm.auto_swarm = false\n\n    # supervisor configs\n    config.swarm.supervisor.liveness_probing_period = 2 # NOTE: in seconds\n\n    # (probe_hosts) host probing configuration\n    config.swarm.probe_hosts.enabled_for_swarm = true # NOTE: run host-probing from or not\n    config.swarm.probe_hosts.probe_period = 2 # NOTE: (in seconds) the period of time when the probing process is triggered\n    # (probe_hosts) individual redis config\n    config.swarm.probe_hosts.redis_config.sentinel = false # NOTE: individual redis config\n    config.swarm.probe_hosts.redis_config.pooled = false # NOTE: individual redis config\n    config.swarm.probe_hosts.redis_config.config = {} # NOTE: individual redis config\n    config.swarm.probe_hosts.redis_config.pool_config = {} # NOTE: individual redis config\n\n    # (flush_zombies) zombie flushing configuration\n    config.swarm.flush_zombies.enabled_for_swarm = true # NOTE: run zombie flushing or not\n    config.swarm.flush_zombies.zombie_flush_period = 10 # NOTE: (in seconds) period of time when the zombie flusher is triggered\n    config.swarm.flush_zombies.zombie_ttl = 15_000 # NOTE: (in milliseconds) when the lock/host/acquirer is considered a zombie\n    config.swarm.flush_zombies.zombie_lock_scan_size = 500 # NOTE: scan sizec during zombie flushing\n    config.swarm.flush_zombies.zombie_queue_scan_size = 500 # NOTE: scan sizec during zombie flushing\n    # (flush_zombies) individual redis config\n    config.swarm.flush_zombies.redis_config.sentinel = false # NOTE: individual redis config\n    config.swarm.flush_zombies.redis_config.pooled = false # NOTE: individual redis config\n    config.swarm.flush_zombies.redis_config.config = {} # NOTE: individual redis config\n    config.swarm.flush_zombies.redis_config.pool_config = {} # NOTE: individual redis config\n  end\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eseed a zombie\u003c/summary\u003e\n\n  - obtain some long living lock and kill the host process which will lead the lock becoming a zombie:\n\n  ```ruby\n  daiver =\u003e ~/Projects/redis_queued_locks  master [$]\n  ➜ bin/console\n  [1] pry(main)\u003e rql = RedisQueuedLocks::Client.new(RedisClient.new);\n  [2] pry(main)\u003e rql.swarmize!\n  /Users/daiver/Projects/redis_queued_locks/lib/redis_queued_locks/swarm/flush_zombies.rb:107: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.\n  =\u003e {:ok=\u003etrue, :result=\u003e:swarming}\n  [3] pry(main)\u003e rql.lock('kekpek', ttl: 1111111111)\n  =\u003e {:ok=\u003etrue,\n   :result=\u003e\n    {:lock_key=\u003e\"rql:lock:kekpek\",\n     :acq_id=\u003e\"rql:acq:17580/2260/2380/2280/3f16b93973612580\",\n     :hst_id=\u003e\"rql:hst:17580/2260/2280/3f16b93973612580\",\n     :ts=\u003e1720305351.069259,\n     :ttl=\u003e1111111111,\n     :process=\u003e:lock_obtaining}}\n  [4] pry(main)\u003e exit\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003efind zombies\u003c/summary\u003e\n\n  - start another process, fetch the swarm info, see that our last process is a zombie now and their hosted lock is a zombie too:\n\n  ```ruby\n  daiver =\u003e ~/Projects/redis_queued_locks  master [$] took 27.2s\n  ➜ bin/console\n  [1] pry(main)\u003e rql = RedisQueuedLocks::Client.new(RedisClient.new);\n  [2] pry(main)\u003e rql.swarm_info\n  =\u003e {\"rql:hst:17580/2260/2280/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 12897/262144 +0300, :last_probe_score=\u003e1720305353.0491982},\n   \"rql:hst:17580/2300/2280/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 211107/4194304 +0300, :last_probe_score=\u003e1720305353.0503318},\n   \"rql:hst:17580/2320/2280/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 106615/2097152 +0300, :last_probe_score=\u003e1720305353.050838},\n   \"rql:hst:17580/2260/2340/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 26239/524288 +0300, :last_probe_score=\u003e1720305353.050047},\n   \"rql:hst:17580/2300/2340/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 106359/2097152 +0300, :last_probe_score=\u003e1720305353.050716},\n   \"rql:hst:17580/2320/2340/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 213633/4194304 +0300, :last_probe_score=\u003e1720305353.050934},\n   \"rql:hst:17580/2360/2280/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 214077/4194304 +0300, :last_probe_score=\u003e1720305353.05104},\n   \"rql:hst:17580/2360/2340/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 214505/4194304 +0300, :last_probe_score=\u003e1720305353.051142},\n   \"rql:hst:17580/2400/2280/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 53729/1048576 +0300, :last_probe_score=\u003e1720305353.05124},\n   \"rql:hst:17580/2400/2340/3f16b93973612580\"=\u003e{:zombie=\u003etrue, :last_probe_time=\u003e2024-07-07 01:35:53 3365/65536 +0300, :last_probe_score=\u003e1720305353.0513458}}\n  [3] pry(main)\u003e rql.swarm_status\n  =\u003e {:auto_swarm=\u003efalse,\n   :supervisor=\u003e{:running=\u003efalse, :state=\u003e\"non_initialized\", :observable=\u003e\"non_initialized\"},\n   :probe_hosts=\u003e{:enabled=\u003etrue, :thread=\u003e{:running=\u003efalse, :state=\u003e\"non_initialized\"}, :main_loop=\u003e{:running=\u003efalse, :state=\u003e\"non_initialized\"}},\n   :flush_zombies=\u003e{:enabled=\u003etrue, :ractor=\u003e{:running=\u003efalse, :state=\u003e\"non_initialized\"}, :main_loop=\u003e{:running=\u003efalse, :state=\u003e\"non_initialized\"}}}\n  [4] pry(main)\u003e rql.zombies_info\n  =\u003e {:zombie_hosts=\u003e\n    #\u003cSet:\n     {\"rql:hst:17580/2260/2280/3f16b93973612580\",\n      \"rql:hst:17580/2300/2280/3f16b93973612580\",\n      \"rql:hst:17580/2320/2280/3f16b93973612580\",\n      \"rql:hst:17580/2260/2340/3f16b93973612580\",\n      \"rql:hst:17580/2300/2340/3f16b93973612580\",\n      \"rql:hst:17580/2320/2340/3f16b93973612580\",\n      \"rql:hst:17580/2360/2280/3f16b93973612580\",\n      \"rql:hst:17580/2360/2340/3f16b93973612580\",\n      \"rql:hst:17580/2400/2280/3f16b93973612580\",\n      \"rql:hst:17580/2400/2340/3f16b93973612580\"}\u003e,\n   :zombie_acquirers=\u003e#\u003cSet: {\"rql:acq:17580/2260/2380/2280/3f16b93973612580\"}\u003e,\n   :zombie_locks=\u003e#\u003cSet: {\"rql:lock:kekpek\"}\u003e}\n  [5] pry(main)\u003e rql.zombie_locks\n  =\u003e #\u003cSet: {\"rql:lock:kekpek\"}\u003e\n  [6] pry(main)\u003e rql.zombie_acquirers\n  =\u003e #\u003cSet: {\"rql:acq:17580/2260/2380/2280/3f16b93973612580\"}\u003e\n  [7] pry(main)\u003e rql.zombie_hosts\n  =\u003e #\u003cSet:\n   {\"rql:hst:17580/2260/2280/3f16b93973612580\",\n    \"rql:hst:17580/2300/2280/3f16b93973612580\",\n    \"rql:hst:17580/2320/2280/3f16b93973612580\",\n    \"rql:hst:17580/2260/2340/3f16b93973612580\",\n    \"rql:hst:17580/2300/2340/3f16b93973612580\",\n    \"rql:hst:17580/2320/2340/3f16b93973612580\",\n    \"rql:hst:17580/2360/2280/3f16b93973612580\",\n    \"rql:hst:17580/2360/2340/3f16b93973612580\",\n    \"rql:hst:17580/2400/2280/3f16b93973612580\",\n    \"rql:hst:17580/2400/2340/3f16b93973612580\"}\u003e\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003ekill zombies in a background\u003c/summary\u003e\n\n  - swarmize the new current ruby process that should run the flush zombies element that will drop zombie locks, zombie hosts and their lock requests in a background:\n\n  ```ruby\n  [8] pry(main)\u003e rql.swarmize!\n  /Users/daiver/Projects/redis_queued_locks/lib/redis_queued_locks/swarm/flush_zombies.rb:107: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.\n  =\u003e {:ok=\u003etrue, :result=\u003e:swarming}\n  [9] pry(main)\u003e rql.swarm_info\n  =\u003e {\"rql:hst:17752/2260/2280/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 4012577/4194304 +0300, :last_probe_score=\u003e1720305399.956673},\n   \"rql:hst:17752/2300/2280/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 4015233/4194304 +0300, :last_probe_score=\u003e1720305399.9573061},\n   \"rql:hst:17752/2320/2280/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 4016755/4194304 +0300, :last_probe_score=\u003e1720305399.957669},\n   \"rql:hst:17752/2260/2340/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 1003611/1048576 +0300, :last_probe_score=\u003e1720305399.957118},\n   \"rql:hst:17752/2300/2340/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 2008027/2097152 +0300, :last_probe_score=\u003e1720305399.957502},\n   \"rql:hst:17752/2320/2340/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 2008715/2097152 +0300, :last_probe_score=\u003e1720305399.95783},\n   \"rql:hst:17752/2360/2280/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 4018063/4194304 +0300, :last_probe_score=\u003e1720305399.9579809},\n   \"rql:hst:17752/2360/2340/89beef198021f16d\"=\u003e{:zombie=\u003efalse, :last_probe_time=\u003e2024-07-07 01:36:39 1004673/1048576 +0300, :last_probe_score=\u003e1720305399.9581308}}\n  [10] pry(main)\u003e rql.swarm_status\n  =\u003e {:auto_swarm=\u003efalse,\n   :supervisor=\u003e{:running=\u003etrue, :state=\u003e\"sleep\", :observable=\u003e\"initialized\"},\n   :probe_hosts=\u003e{:enabled=\u003etrue, :thread=\u003e{:running=\u003etrue, :state=\u003e\"sleep\"}, :main_loop=\u003e{:running=\u003etrue, :state=\u003e\"sleep\"}},\n   :flush_zombies=\u003e{:enabled=\u003etrue, :ractor=\u003e{:running=\u003etrue, :state=\u003e\"running\"}, :main_loop=\u003e{:running=\u003etrue, :state=\u003e\"sleep\"}}}\n  [11] pry(main)\u003e rql.zombies_info\n  =\u003e {:zombie_hosts=\u003e#\u003cSet: {}\u003e, :zombie_acquirers=\u003e#\u003cSet: {}\u003e, :zombie_locks=\u003e#\u003cSet: {}\u003e}\n  [12] pry(main)\u003e rql.zombie_acquirers\n  =\u003e #\u003cSet: {}\u003e\n  [13] pry(main)\u003e rql.zombie_hosts\n  =\u003e #\u003cSet: {}\u003e\n  [14] pry(main)\u003e\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eswarm hosts key in Redis\u003c/summary\u003e\n\n  ```ruby\n  \"rql:swarm:hsts\"\n  ```\n\u003c/details\u003e\n\n---\n\n## Lock Access Strategies\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- **this documentation section is in progress**;\n- (little details for a context of the current implementation and feautres):\n  - defines the way in which the lock should be obitained;\n  - by default it is configured to obtain a lock in classic `queued` way: you should wait your position in queue in order to obtain a lock;\n  - can be customized in methods `#lock` and `#lock!` via `:access_strategy` attribute (see method signatures of #lock and #lock! methods);\n  - supports different strategies:\n    - `:queued` (FIFO): the classic queued behavior (default), your lock will be obitaned if you are first in queue and the required lock is free;\n    - `:random` (RANDOM): obtain a lock without checking the positions in the queue (but with checking the limist, retries, timeouts and so on). if lock is free to obtain - it will be obtained;\n  - for current implementation detalis check:\n    - [Configuration](#configuration) documentation: see `config.default_access_strategy` config docs;\n    - [#lock](#lock---obtain-a-lock) method documentation: see `access_strategy` attribute docs;\n\n---\n\n## Deadlocks and Reentrant locks\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- **this documentation section is in progress**;\n- (little details for a context of the current implementation and feautres):\n  - at this moment we support only **reentrant locks**: they works via customizable conflict strategy behavior\n    (`:wait_for_lock` (default), `:work_through`, `:extendable_work_through`, `:dead_locking`);\n  - by default behavior (`:wait_for_lock`) your lock obtaining process will work in a classic way (limits, retries, etc);\n  - `:work_through`, `:extendable_work_through` works with limits too (timeouts, delays, etc), but the decision of\n    \"is your lock are obtained or not\" is made as you work with **reentrant locks** (your process continues to use the lock without/with\n    lock's TTL extension accordingly);\n  - for current implementation details check:\n    - [Configuration](#configuration) documentation: see `config.default_conflict_strategy` config docs;\n    - [#lock](#lock---obtain-a-lock) method documentation: see `conflict_strategy` attribute docs and the method result data;\n\n---\n\n## Logging\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- [Logging Configuration](#logging-configuration)\n\n\n- default logs (raised from `#lock`/`#lock!`):\n\n```ruby\n\"[redis_queued_locks.start_lock_obtaining]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.start_try_to_lock_cycle]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.dead_score_reached__reset_acquirer_position]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.lock_obtained]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\");\n\"[redis_queued_locks.extendable_reentrant_lock_obtained]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"acq_time\");\n\"[redis_queued_locks.reentrant_lock_obtained]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"acq_time\");\n\"[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.expire_lock]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.decrease_lock]\" # (logs \"lock_key\", \"decreased_ttl\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n```\n\n- additional logs (raised from `#lock`/`#lock!` with `confg[:log_lock_try] == true`):\n\n```ruby\n\"[redis_queued_locks.try_lock.start]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.try_lock.rconn_fetched]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.try_lock.same_process_conflict_detected]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.try_lock.same_process_conflict_analyzed]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\");\n\"[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", \"last_ext_ttl\", \"last_ext_ts\");\n\"[redis_queued_locks.try_lock.reentrant_lock__work_through]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", last_spc_ts);\n\"[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", \"last_spc_ts\");\n\"[redis_queued_locks.try_lock.acq_added_to_queue]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.try_lock.remove_expired_acqs]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.try_lock.get_first_from_queue]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\");\n\"[redis_queued_locks.try_lock.exit__queue_ttl_reached]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n\"[redis_queued_locks.try_lock.exit__no_first]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\", \"\u003ccurrent_lock_data\u003e\");\n\"[redis_queued_locks.try_lock.exit__lock_still_obtained]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\", \"locked_by_acq_id\", \"\u003ccurrent_lock_data\u003e\");\n\"[redis_queued_locks.try_lock.obtain__free_to_acquire]\" # (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n```\n\n---\n\n### Logging Configuration\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n**NOTICE**: logging can be sampled via:\n- `config.log_samplign_enabled = true` (**false** by default);\n- `config.log_sampler = RedisQueuedLocks::Logging::Sampler` (used by default);\n- see **RedisQueuedLocks::Logging::Sampler** implementation in source code for customization details;\n\n```ruby\n# (default: RedisQueuedLocks::Logging::VoidLogger)\n# - the logger object;\n# - should implement `debug(progname = nil, \u0026block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;\n# - supports `SemanticLogger::Logger` (see \"semantic_logger\" gem)\n# - at this moment the only debug logs are realised in following cases:\n#   - \"[redis_queued_locks.start_lock_obtaining]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.start_try_to_lock_cycle]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.dead_score_reached__reset_acquirer_position]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.lock_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\", \"acs_strat\");\n#   - \"[redis_queued_locks.extendable_reentrant_lock_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\", \"acs_strat\");\n#   - \"[redis_queued_locks.reentrant_lock_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acq_time\", \"acs_strat\");\n#   - \"[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.expire_lock]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.decrease_lock]\" (logs \"lock_key\", \"decreased_ttl\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n# - by default uses VoidLogger that does nothing;\nconfig.logger = RedisQueuedLocks::Logging::VoidLogger\n\n# (default: false)\n# - adds additional debug logs;\n# - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);\n# - it adds following debug logs in addition to the existing:\n#   - \"[redis_queued_locks.try_lock.start]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.try_lock.rconn_fetched]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.try_lock.same_process_conflict_detected]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.try_lock.same_process_conflict_analyzed]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\");\n#   - \"[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", \"last_ext_ttl\", \"last_ext_ts\");\n#   - \"[redis_queued_locks.try_lock.reentrant_lock__work_through]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"spc_status\", last_spc_ts);\n#   - \"[redis_queued_locks.try_lock.acq_added_to_queue]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\")\";\n#   - \"[redis_queued_locks.try_lock.remove_expired_acqs]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.try_lock.get_first_from_queue]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\");\n#   - \"[redis_queued_locks.try_lock.exit__queue_ttl_reached]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\n#   - \"[redis_queued_locks.try_lock.exit__no_first]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\", \"\u003ccurrent_lock_data\u003e\");\n#   - \"[redis_queued_locks.try_lock.exit__lock_still_obtained]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\", \"first_acq_id_in_queue\", \"locked_by_acq_id\", \"\u003ccurrent_lock_data\u003e\");\n#   - \"[redis_queued_locks.try_lock.obtain__free_to_acquire]\" (logs \"lock_key\", \"queue_ttl\", \"acq_id\", \"hst_id\", \"acs_strat\");\nconfig.log_lock_try = false\n\n# (default: false)\n# - enables \u003clog sampling\u003e: only the configured percent of RQL cases will be logged;\n# - disabled by default;\n# - works in tandem with \u003cconfig.log_sampling_percent\u003e and \u003clog.sampler\u003e configs;\nconfig.log_sampling_enabled = false\n\n# (default: 15)\n# - the percent of cases that should be logged;\n# - take an effect when \u003cconfig.log_sampling_enalbed\u003e is true;\n# - works in tandem with \u003cconfig.log_sampling_enabled\u003e and \u003cconfig.log_sampler\u003e configs;\nconfig.log_sampling_percent = 15\n\n# (default: RedisQueuedLocks::Logging::Sampler)\n# - percent-based log sampler that decides should be RQL case logged or not;\n# - works in tandem with \u003cconfig.log_sampling_enabled\u003e and \u003cconfig.log_sampling_percent\u003e configs;\n# - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand\n#   method so the algorithm error is ~(0%..13%);\n# - you can provide your own log sampler with bettter algorithm that should realize\n#   `sampling_happened?(percent) =\u003e boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);\nconfig.log_sampler = RedisQueuedLocks::Logging::Sampler\n```\n\n---\n\n## Instrumentation\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- [Instrumentation Events](#instrumentation-events)\n- [Instrumentation Configuration](#instrumentation-configuration)\n\nAn instrumentation layer is incapsulated in `instrumenter` object stored in [config](#configuration) (`RedisQueuedLocks::Client#config[:instrumenter]`).\n\nInstrumentation can be sampled. See [Instrumentation Configuration](#instrumentation-configuration) section for details.\n\nInstrumenter object should provide `notify(event, payload)` method with the following signarue:\n\n- `event` - `string`;\n- `payload` - `hash\u003cSymbol,Any\u003e`;\n\n`redis_queued_locks` provides two instrumenters:\n\n- `RedisQueuedLocks::Instrument::ActiveSupport` - **ActiveSupport::Notifications** instrumenter\n  that instrument events via **ActiveSupport::Notifications** API;\n- `RedisQueuedLocks::Instrument::VoidNotifier` - instrumenter that does nothing;\n\nBy default `RedisQueuedLocks::Client` is configured with the void notifier (which means \"instrumentation is disabled\").\n\n---\n\n### Instrumentation Configuration\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n**NOTICE**: instrumentation can be sampled via:\n- `config.instr_sampling_enabled = true` (**false** by default);\n- `config.instr_sampler = RedisQueuedLocks::Instrument::Sampler` (used by default);\n- see **RedisQueuedLocks::Instrument::Sampler** implementation in source code for customization details;\n\n```ruby\n# (default: RedisQueuedLocks::Instrument::VoidNotifier)\n# - instrumentation layer;\n# - you can provide your own instrumenter that should realize `#notify(event, payload = {})` interface:\n#   - event: \u003cstring\u003e requried;\n#   - payload: \u003chash\u003e requried;\n# - disabled by default via `VoidNotifier`;\nconfig.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport\n\n# (default: false)\n# - enables \u003cinstrumentaion sampling\u003e: only the configured percent of RQL cases will be instrumented;\n# - disabled by default;\n# - works in tandem with \u003cconfig.instr_sampling_percent and \u003clog.instr_sampler\u003e;\nconfig.instr_sampling_enabled = false\n\n# (default: 15)\n# - the percent of cases that should be instrumented;\n# - take an effect when \u003cconfig.instr_sampling_enalbed\u003e is true;\n# - works in tandem with \u003cconfig.instr_sampling_enabled\u003e and \u003cconfig.instr_sampler\u003e configs;\nconfig.instr_sampling_percent = 15\n\n# (default: RedisQueuedLocks::Instrument::Sampler)\n# - percent-based log sampler that decides should be RQL case instrumented or not;\n# - works in tandem with \u003cconfig.instr_sampling_enabled\u003e and \u003cconfig.instr_sampling_percent\u003e configs;\n# - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand\n#   method so the algorithm error is ~(0%..13%);\n# - you can provide your own log sampler with bettter algorithm that should realize\n#   `sampling_happened?(percent) =\u003e boolean` interface (see `RedisQueuedLocks::Instrument::Sampler` for example);\nconfig.instr_sampler = RedisQueuedLocks::Instrument::Sampler\n```\n\n---\n\n### Instrumentation Events\n\n\u003csup\u003e\\[[back to top](#instrumentation)\\]\u003c/sup\u003e\n\nList of instrumentation events\n\n- `redis_queued_locks.lock_obtained`;\n- `redis_queued_locks.extendable_reentrant_lock_obtained`;\n- `redis_queued_locks.reentrant_lock_obtained`;\n- `redis_queued_locks.lock_hold_and_release`;\n- `redis_queued_locks.reentrant_lock_hold_completes`;\n- `redis_queued_locks.explicit_lock_release`;\n- `redis_queued_locks.explicit_all_locks_release`;\n\nDetalized event semantics and payload structure:\n\n- `\"redis_queued_locks.lock_obtained\"`\n  - a moment when the lock was obtained;\n  - raised from `#lock`/`#lock!`;\n  - payload:\n    - `:ttl` - `integer`/`milliseconds` - lock ttl;\n    - `:acq_id` - `string` - lock acquirer identifier;\n    - `:hst_id` - `string` - lock's host identifier;\n    - `:lock_key` - `string` - lock name;\n    - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend;\n    - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;\n    - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;\n\n- `\"redis_queued_locks.extendable_reentrant_lock_obtained\"`\n  - an event signalizes about the \"extendable reentrant lock\" obtaining is happened;\n  - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;\n  - payload:\n    - `:lock_key` - `string` - lock name;\n    - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;\n    - `:acq_id` - `string` - lock acquirer identifier;\n    - `:hst_id` - `string` - lock's host identifier;\n    - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as extendable reentrant lock;\n    - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;\n    - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;\n\n- `\"redis_queued_locks.reentrant_lock_obtained\"`\n  - an event signalizes about the \"reentrant lock\" obtaining is happened (without TTL extension);\n  - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;\n  - payload:\n    - `:lock_key` - `string` - lock name;\n    - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;\n    - `:acq_id` - `string` - lock acquirer identifier;\n    - `:hst_id` - `string` - lock's host identifier;\n    - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;\n    - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;\n    - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;\n\n- `\"redis_queued_locks.lock_hold_and_release\"`\n  - an event signalizes about the \"hold+and+release\" process is finished;\n  - raised from `#lock`/`#lock!` when invoked with a block of code;\n  - payload:\n    - `:hold_time` - `float`/`milliseconds` - lock hold time;\n    - `:ttl` - `integer`/`milliseconds` - lock ttl;\n    - `:acq_id` - `string` - lock acquirer identifier;\n    - `:hst_id` - `string` - lock's host identifier;\n    - `:lock_key` - `string` - lock name;\n    - `:ts` - `numeric`/`epoch` - the time when lock was obtained;\n    - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;\n    - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;\n\n- `\"redis_queued_locks.reentrant_lock_hold_completes\"`\n  - an event signalizes about the \"reentrant lock hold\" is complete (both extendable and non-extendable);\n  - lock re-entering can happen many times and this event happens for each of them separately;\n  - raised from `#lock`/`#lock!` when the lock was obtained as reentrant lock;\n  - payload:\n    - `:hold_time` - `float`/`milliseconds` - lock hold time;\n    - `:ttl` - `integer`/`milliseconds` - last lock ttl by reentrant locking;\n    - `:acq_id` - `string` - lock acquirer identifier;\n    - `:hst_id` - `string` - lock's host identifier;\n    - `:ts` - `numeric`/`epoch` - the time when the lock was obtaiend as reentrant lock;\n    - `:lock_key` - `string` - lock name;\n    - `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;\n    - `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;\n\n- `\"redis_queued_locks.explicit_lock_release\"`\n  - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);\n  - raised from `#unlock`;\n  - payload:\n    - `:at` - `float`/`epoch` - the time when the lock was released;\n    - `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;\n    - `:lock_key` - `string` - released lock (lock name);\n    - `:lock_key_queue` - `string` - released lock queue (lock queue name);\n\n- `\"redis_queued_locks.explicit_all_locks_release\"`\n  - an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);\n  - raised from `#clear_locks`;\n  - payload:\n    - `:rel_time` - `float`/`milliseconds` - time spent on \"realese all locks\" operation;\n    - `:at` - `float`/`epoch` - the time when the operation has ended;\n    - `:rel_keys` - `integer` - released redis keys count (`released queue keys` + `released lock keys`);\n\n---\n\n## Roadmap\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- **Major**:\n  - Swarm:\n    - circuit-breaker for long-living failures of your infrastructure inside the swarm elements and supervisor:\n      the supervisor will stop (for some period of time or while the some factor will return `true`)\n      trying to ressurect unexpectedly terminated swarm elements, and will notify about this;\n    - swarm logs (thread/ractor has some limitations so the initial implementation does not include swarm logging);\n    - swarm instrumentation (thread/ractor has some limitations so the initial implementation does not include swarm instrumentation);\n  - isolated timeouts which are independent of internal Ruby's timeout implementation (where all timeouts are hostend inside\n    the global \"timeout request\" queue and managed by a single global \"timeout wathcer\" thread). it should prevent any logic and timeout intersections,\n    some GVL-related things and problem situations when the global watcher thread is \"dead\";\n  - lock request prioritization;\n  - **strict redlock algorithm support** (support for many `RedisClient` instances that are fully independent (distributed redis instances));\n  - `#lock_series` - acquire a series of locks:\n    ```ruby\n    rql.lock_series('lock_a', 'lock_b', 'lock_c') { puts 'locked' }\n    ```\n ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0exp%2Fredis_queued_locks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0exp%2Fredis_queued_locks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0exp%2Fredis_queued_locks/lists"}