- Updated development dependencies (
armitage-rubocop
,rbs
,steep
); - CI is splitted to "mainstream ruby version" and "previous actually maintaned ruby versions";
Acquier
->Acquirer
,Acquierment
->Acquirement
(typos):- [Breaking] Constant renaming: all constants and constant parts were renamed from
Acquier
toAcquirer
; - [Breaking] Method and variable names renaming: all
acquier
text parts of method/variable names were renamed toacquirer
; - [Breaking] Logs: all
acquier
text parts of each log message type were renamed toacquirer
; - [Breaking] Instrumentation: all
acquier
text parts of each event name were renamed toacquirer
; - [Breaking] Exceptions: all
Acquierment
exception constant name parts were renamed toAcquirement
;
- [Breaking] Constant renaming: all constants and constant parts were renamed from
- [Breaking]
RedisQueuedLocks::Data
is not used (temporary) as result type of some APIs. The reason is that this type can not be used as a record type insteep
/rbs
(working on it);
- Type Checking:
- library codebase is fully typed;
- Integrated
RBS
withSteep
that is configured on strict mode (seesig
directory,Steepfile
config,sig/manifest.yml
andrbs_collection.yml
for dependencies); - Added
TypeCheck
CI/CD step;
- Test coverage (via
simplecov
withhtml
andlcov
formats).minimum_coverage
config is temporary disabled (and the CI step is not configured yet) cuz we need to refactor tests in first; - CI:
rspec-retry
is temporary added until the tests are fully refactored; - Support for
ActiveSupport::BroadcastLogger
logger instances;
- Internal Private API: an internal Reentrant's Lock utility (
RedisQueuedLocks::Utilities::Lock
) basis is changed from::Mutex
to::Monitor
in order to use Ruby's Core C-based implementation (prevent some Ruby-runtime based GVL-oriented locking) of re-entrant locks instead of the custom Ruby-based implementation;
redis_queued_locks.lock_hold_and_release
instrumentation event has incorrectacq_time
payload value (it incorrectly storeslock_key
payload value);
- Timed blocks of code and their timeout errors: reduced error message object allocations;
- Lock Acquirement Timeout (
acq_timeout
/queue_ttl
): more correct timeout error interception inside theRedisQueuedLocks::Acquier::AcquireLock::WithAcqTimeout
logic that now raises and intercepts an internal timeout error class in order to prevent interceptions of other timeouts that can be raised from the wrapped block of code;
- Timed invocations (
"timeed blocks of code"
/timed: true
):- the way of timeout error interception has been changed:
- instead of the
rescue Timeout::Error
around the block of code the timeout interception now usesTimeout#timeout
's custom exception class/message replacement API; rescue Timeout::Error
can lead to incorrect exception interception: intercept block's-related Timeout::Error that is not RQL-related error;
- instead of the
- the way of timeout error interception has been changed:
- Updated development dependencies;
- Some minor readme updates;
RedisQueuedLocks::Swarm
: missing YARDocs;- Separated
Logging Configuration
readme section (that is placed inside the main configuration section already); - Separated
Instrumentation Configuration
readme section (that is placed inside the main configuration section already);
RedisQueudLocks::Swarm
: some useless YARDocs;
- Brand New Extremely Major Feature: Swarm Mode - eliminate zombie locks with a swarm:
- works by
supervisor
+actor model
abstractions; - all your ruby workers can become an element of the processs swarm;
- each ruby worker of the swarm probes himself that he is alive;
- worker that does not probes himselfs treats as a zombie;
- worekr becomes dead when your ruby process is dead, or thread is dead or your ractor is dead;
- each zombie's lock, acquirer and position in queue are flushed in background via
flush_zombies
swarm element; - the supervisor module keeps up and running each swarm melement (
probe_hosts
andflush_zombies
):- cuz each element works in background and can fail by any unexpected exception the supervisor guarantees that your elements will ressurect after that;
- each element can be deeply configured (and enabled/disabled);
- abilities:
- configurable swarming and deswarming (
#swarmize!
,#deswarmize!
); - encapsulated swarm interface;
- two fully isolated swarm elements:
probe_hosts
andflush_zombies
; - supervisor that keeps all elements running and wokring;
- an ability to check the swarm status (
#swarm_status
): who is working, who is dead, running status, internal main loop states, etc; - an abiltiy to check the swarm information (
#swarm_info
): showing the current swarm hosts and their last probes and current zombie status; - an ability to find zombie locks, zombie acquirers and zombie hosts (
#zombie_locks
,#zombie_acquiers
,#zombie_hosts
); - an ability to extract the full zombie information (
#zombies_info
/#zombies
); - each zombie lock will be flushed in background by appropriated swarm element (
flush_zombies
); - deeply configurable zombie factors: zombie ttl, host probing period, supervisor check period;
- an ability to manually probe hosts;
- an ability to flush zombies manually;
- you can made
swarm
-based logic by yourself (by manually runnable#flush_zombies
and#probe_hosts
);
- configurable swarming and deswarming (
- summarized interface:
#swarmize!
,#deswarmize!
;#swarm_status
/#swarm_state
,#swarm_info
#zombie_locks
,#zmobie_acquiers
,#zombie_hosts
,#zombies_info
/#zombies
;- manual abilities:
#probe_hosts
,#flush_zombies
;
- general note: each swarm element should have their own
RedisClient
instance so each have their own redis-client configuration and each of the can be configured separately (RedisClient multithreading limitation and Ractor limitations);
- works by
- Added the
lock host
abstraction (hst_id
):- each lock is hosted by some ruby workers so this fact is abstracted into the host identifier represended as a combination of
process_id
/thread_id
/ractor_id
/uniq_identity
; - the ruby worker is a combination of
process_id
/thread_id
/ractor_id
/uniq_identity
); - each lock stores the host id (
hst_id
field) indisde their data (for debugging purposes and zombie identification purposes); - every lock information method now includes
hst_id
field:#lock_info
,#lock_data
,#locks_info
; - an ability to fetch the current host id (your ruby worker host id):
#current_host_id
; - an ability to fetch all possible host ids in the current Ractor (all possible and reachable ruby workers from the current ractor):
#possible_host_ids
; - extended
RedisQueuedLocks::TimedLocktimeoutError
message: addedhst_id
field data from the lock data;
- each lock is hosted by some ruby workers so this fact is abstracted into the host identifier represended as a combination of
- Instrumentation updates:
- added the
hst_id
field to each locking-process-related instrumentation event;
- added the
- Logging updates:
- added
hst_id
field to each locking-process-related log;
- added
- Logging/Instrumentation Sampling updates:
- an ability to mark any loggable/instrumentable method as sampled for instrumentation/logging despite of the enabled instrumentation/log sampling
by providing the
log_sample_this: true
attribute andinstr_sample_this: true
attribute respectively;
- an ability to mark any loggable/instrumentable method as sampled for instrumentation/logging despite of the enabled instrumentation/log sampling
by providing the
- an alias for
#clear_locks
:#release_locks
; - an alias for
#unlock
:#release_lock
;
- A configurable option that enables the adding additional lock/queue data to "Acquirement Timeout"-related error messages for better debugging;
- Configurable option is used beacuse of the extra error data requires some additional Redis requests that can be costly for memory/cpu/etc resources;
- An ability to get the current acquirer id (
RedisQueuedLocks::Client#current_acquirer_id
);
- Added additional lock information to some exceptions that does not require extra Redis requests;
- New feature: Lock Access Strategy: you can obtain a lock in different ways:
queued
(classic queued FIFO),random
(get the lock immideatly if lock is free)::queued
is used by default (classicredis_queued_locks
behavior);:random
: obtain a lock without checking the positions in the queue => if lock is free to obtain - it will be obtained;
- Some logging refactorings, some instrumentation refactorings: the code that uses them is more readable and supportable;
- New Feature: Instrumentation Sampling: configurable instrumentation sampling based on
weight
algorithm (where the weight is a percentage of RQL cases that should be logged); - Missing instrumenter customization in public
RedisQueuedLocks::Client
methods; - Documentation updates;
- New Feature: Log sampling - configurable log sampling based on
weight
algorithm (where the weight is a percentage of RQL cases that should be logged);
#lock
/#lock!
: reduced memory allocaiton during:meta
attribute type checking;
- More accurate time analyzis operations;
:meta
attribute type validation of#lock
/#lock!
was incorrect;
- documentation updates and clarifications;
- Major Feature: support for Reentrant Locks;
- The result of lock obtaining now includes
:process
key that shows the type of logical process that obtains the lock (:lock_obtaining
,:extendable_conflict_work_through
,:conflict_work_through
,:dead_locking
); - Added reentrant lock details to
RedisQueuedLocks::Client#lock_info
andRedisQueuedLocks::Client#locks
method results; - Documentation updates;
- Logging:
redis_queued_locks.fail_fast_or_limits_reached__dequeue
log is renamed toredis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue
in order to reflect the lock conflict failures too;
- Documentation updates;
- Logging: support for
semantic_logger
loggers (see: https://logger.rocketjob.io/) (https://github.com/reidmorrison/semantic_logger)
- Documentation updates:
- more
#lock
examples; - added missing docs for
config.dead_request_ttl
; - some minor updates;
- more
#clear_dead_requests
::scan_size
is equal toconfig[:lock_release_batch_size]
now (instead of toconfig[:key_extraction_batch_size]
), cuz#clear_dead_requests
works with lock releasing;
- First Major Release;
RedisQueuedLocks::Client#clear_dead_requests
implementation;- Logger and instrumentation are passed everywhere where any changes in Redis (with locks and queus) are expected;
- New config
is_timed_by_default
(boolean,false
by default) that reflects thetimed
option of#lock
and#lock!
methods; - Ther result of
#unlock
is changed: added:lock_res
and:queue_res
result data in order to reflect what happened inside (:released
or:nothing_to_release
) and to adopt to the case when you trying to unlock the non-existent lock; - A lot of documentation updates;
- Github CI Workflow;
:rel_key_cnt
result of#clear_locks
is more accurate now;
- Logging:
- added new log
[redis_queued_locks.fail_fast_or_limits_reached__dequeue]
;
- added new log
- Client:
#extend_lock_ttl
implementation;
- Removed
RadisQueuedLocks::Debugger.debug(...)
injections; - Instrumentation:
- the
:at
payload field of"redis_queued_locks.explicit_lock_release"
event and"redis_queued_locks.explicit_all_locks_release"
event is changed fromInteger
toFloat
in order to reflect micro/nano seconds too for more accurate time value;
- the
- Lock information:
- the lock infrmation extracting now uses
RedisClient#pipelined
instead ofRedisClient#mutli
cuz it is more reasonable for information-oriented logic (the queue information extraction works viapipelined
invocations for example);
- the lock infrmation extracting now uses
- Logging:
- log message is used as a
message
(notpragma
) according toLogger#debug
signature;
- log message is used as a
- Minor update (dropped useless constant);
#queues_info
::contains
is renamed to:reqeusts
in order to reflect it's domain area;
- Requirements:
- redis version:
>= 7.x
; - redis protocol:
RESP3
;
- redis version:
- Additional debugging methods:
#locks_info
(or#locks(with_info: true)
) - get obtained locks with their info;#queus_info
(or#queues(with_info: true
) - get active lock queues with their info;
- The random unique client instance identifier now uses 16-byte strings instead of 10-bytes in order to prevent potential collisions;
- Removing the acquirer from the request queue during the lock obtaining logic is using more proper and accurate
ZREM
now instead ofZPOPMIN
;
- Logging: added current lock data info to the detailed
#try_to_lock
log to the cases when lock is still obtained. It is suitable when you pass a custom metadata with lock obtainer (for example: the current string of code) and want to see this information in logs when you can not acquire the concrete lock long time;
- Support for custom metadata that merged to the lock data. This data also returned from
RedisQueudLocks::Client#lock_info
method;- Custom metadata should be represented as a
key => value
Hash
(nil
by default); - Custom metadata values is returned as raw data from Redis (commonly as strings);
- Custom metadata can not contain reserved lock data keys;
- Custom metadata should be represented as a
- Reduced some memory consuption;
RedisQueuedLocks::Client#lock_info
: hash key types of method result is changed fromSymbol
type toString
type;RedisQueuedLocks::Client#queue_info
: hash key types of method result is changed fromSymbol
type toString
type;
:metadata
renamed to:instrument
in order to reflect it's domain area;:metadata
is renamed to:meta
and reserved for future updates;
- Re-enqueue problem: fixed a problem when the expired lock requests were infinitly re-added to the lock queue and immediately removed from the lock queue rather than being re-positioned. It happens when the lock request ttl reached the queue ttl, and the new request now had the dead score forever (fix: it's score now will be correctly recalculated from the current time at the dead score time moment);
- Logging: more detailed logs to the
RedisQueuedLocks::Acquier::AcquierLock
logic and it's sub-modules:- added new logs;
- added
queue_ttl
to each log;
- Logging: added more detailed logs to
RedisQueuedLocks::Acquier::AcquireLock::TryToLock
;
- Logging: added
acq_id
to every log message; - Logging: updated documentation;
- Better acquirer position accuracy: acquirer position in lock queue should be represented as EPOCH in seconds+microseconds (previously: simply in seconds);
- Logging: add
acquier_id
;
- Minor logs stylization;
- An optional ability to log each try of lock obtaining (see
RedisQueuedLocks::Acquier::AcquireLock::TryToLock.try_to_lock
);
- Composed redis commands are invoked from the same one conenction (instead of mutiple connection fetching from redis connection pool on each redis command);
- Logging infrastructure. Initial implementation includes the only debugging features.
- Refactored
RedisQueuedLocks::Acquier
;
- An ability to provide custom metadata to
lock
andlock!
methods that will be passed to the instrumentation level inside thepayload
parameter with:meta
key;
- An ability to set the invocation time period to the block of code invoked under the obtained lock;
- Semantic results for methods returning
{ ok: true/false, result: Any }
hash objects. Now these objects are represented asRedisQueuedLocks::Data
objects inherited fromHash
;
RedisQueuedLocks::Client#locks
- list of obtained locks;RedisQueuedLocks::Client#queues
- list of existing lock request queus;RedisQueuedLocks::Client#keys
- get list of taken locks and queues;
- Execution delay formula returns the value "in seconds" (should be "in milliseconds");
- An ability to fail fast if the required lock is already obtained;
- Minor documentation updates;
- Minor development updates;
- Deleted
redis expiration error
(1 millisecond time drift) from lock ttl calculation;
- Minor documentation updates;
- Minor documentation updates;
- The lock acquirer identifier (
acq_id
) now includes the fiber id, the ractor id and an unique per-process 10 byte string. It is added in order to prevent collisions between different processes/pods that will have the same process id / thread id identifiers (cuz it is an object_id integers) that can lead to the same position with the sameacq_id
for different processes/pods in the lock request queue.
RedisQueuedLock::Client#locked?
RedisQueuedLock::Client#queued?
RedisQueuedLock::Client#lock_info
RedisQueuedLock::Client#queue_info
- Minor documentation updates;
- Major documentation updates;
RedisQueuedLock#release_lock!
now returns detaield semantic result;RediSQueuedLock#release_all_locks!
now returns detailed semantic result;
- Minor gem update with documentation and configuration updates inside.
- changed default configuration values of
RedisQueuedLocks::Client
config;
- Instrumentation events:
"redis_queued_locks.explicit_all_locks_release"
- re-factored with fully pipelined invocation;
- removed
rel_queue_cnt
andrel_lock_cnt
because of the pipelined invocation misses the concrete results and now we can receive only "released redis keys count"; - adde
rel_keys
payload data (released redis keys);
- Instrumentation events:
"redis_queued_locks.lock_obtained"
;"redis_queued_locks.lock_hold_and_release"
;"redis_queued_locks.explicit_lock_release"
;"redis_queued_locks.explicit_all_locks_release"
;
- Still the initial release version;
- Downgrade the minimal Ruby version requirement from 3.2 to 3.1;
- Initial release