redis_queued_locks 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +3 -9
- data/lib/redis_queued_locks/acquier/delay.rb +2 -0
- data/lib/redis_queued_locks/acquier/expire.rb +4 -4
- data/lib/redis_queued_locks/acquier/release.rb +14 -20
- data/lib/redis_queued_locks/acquier.rb +48 -35
- data/lib/redis_queued_locks/client.rb +6 -6
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5dbd11cefb9d18a816eb4c63fe1178f87325c30d75efc2bc806d29f41994fc2c
         | 
| 4 | 
            +
              data.tar.gz: 9697f4bf452aaaa7cade45320c50d16b510c657074ba97513c5f8de9c51f6c49
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b28ef92375c7eef4d6c43fccb5e44105d653fb00e052edcf67316f8da13578ad7b39086b3799782592b38bf046760f8faa1238b92de293b83eb1e4a7c441413e
         | 
| 7 | 
            +
              data.tar.gz: dfd3891ae8d0303b88ac95268056addc3b4e03f862a5200166de9553b412eee78b77f42193f133f9dd340793b67928806e2e05451517585c2282fb7e4c871d1f
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,14 @@ | |
| 1 1 | 
             
            ## [Unreleased]
         | 
| 2 2 |  | 
| 3 | 
            +
            ## [0.0.3] - 2024-02-26
         | 
| 4 | 
            +
            ### Changed
         | 
| 5 | 
            +
            - Instrumentation events:
         | 
| 6 | 
            +
              - `"redis_queued_locks.explicit_all_locks_release"`
         | 
| 7 | 
            +
                - re-factored with fully pipelined invocation;
         | 
| 8 | 
            +
                - removed `rel_queue_cnt` and `rel_lock_cnt` because of the pipelined invocation
         | 
| 9 | 
            +
                  misses the concrete results and now we can receive only "released redis keys count";
         | 
| 10 | 
            +
                - adde `rel_keys` payload data (released redis keys);
         | 
| 11 | 
            +
             | 
| 3 12 | 
             
            ## [0.0.2] - 2024-02-26
         | 
| 4 13 | 
             
            ### Added
         | 
| 5 14 | 
             
            - Instrumentation events:
         | 
    
        data/README.md
    CHANGED
    
    | @@ -22,7 +22,6 @@ Distributed lock implementation with "lock acquisition queue" capabilities based | |
| 22 22 | 
             
                - `lock_key` - `string` - lock name;
         | 
| 23 23 | 
             
                - `ts` - `integer`/`epoch` - the time when lock was obtained;
         | 
| 24 24 | 
             
                - `acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
         | 
| 25 | 
            -
                - `rel_time` - `float`/`milliseconds` - time spent on lock "holding + releasing";
         | 
| 26 25 | 
             
            - `"redis_queued_locks.explicit_lock_release"`
         | 
| 27 26 | 
             
              - an event signalizes about the explicit lock release (invoked via `RedisQueuedLock#unlock`);
         | 
| 28 27 | 
             
              - payload:
         | 
| @@ -31,13 +30,8 @@ Distributed lock implementation with "lock acquisition queue" capabilities based | |
| 31 30 | 
             
                - `lock_key` - `string` - released lock (lock name);
         | 
| 32 31 | 
             
                - `lock_key_queue` - `string` - released lock queue (lock queue name);
         | 
| 33 32 | 
             
            - `"redis_queued_locks.explicit_all_locks_release"`
         | 
| 34 | 
            -
              - an event signalizes about the explicit all locks release (invoked  | 
| 33 | 
            +
              - an event signalizes about the explicit all locks release (invoked via `RedisQueuedLock#clear_locks`);
         | 
| 35 34 | 
             
              - payload:
         | 
| 36 | 
            -
                - `rel_time` - `float`/`milliseconds` - time  | 
| 35 | 
            +
                - `rel_time` - `float`/`milliseconds` - time spent on the lock release;
         | 
| 37 36 | 
             
                - `at` - `integer`/`epoch` - the time when the operation has ended;
         | 
| 38 | 
            -
                - ` | 
| 39 | 
            -
                - `rel_queue_cnt` - `integer` - released queue count;
         | 
| 40 | 
            -
             | 
| 41 | 
            -
            ## Todo
         | 
| 42 | 
            -
             | 
| 43 | 
            -
            - CI (github actions);
         | 
| 37 | 
            +
                - `rel_keys` - `integer` - released redis keys count (`released queu keys` + `released lock keys`);
         | 
| @@ -3,6 +3,8 @@ | |
| 3 3 | 
             
            # @api private
         | 
| 4 4 | 
             
            # @since 0.1.0
         | 
| 5 5 | 
             
            module RedisQueuedLocks::Acquier::Delay
         | 
| 6 | 
            +
              # Sleep with random time-shifting (it is necessary for empty-time-slot lock acquirement).
         | 
| 7 | 
            +
              #
         | 
| 6 8 | 
             
              # @param retry_delay [Integer] In milliseconds
         | 
| 7 9 | 
             
              # @param retry_jitter [Integer] In milliseconds
         | 
| 8 10 | 
             
              # @return [void]
         | 
| @@ -3,10 +3,10 @@ | |
| 3 3 | 
             
            # @api private
         | 
| 4 4 | 
             
            # @since 0.1.0
         | 
| 5 5 | 
             
            module RedisQueuedLocks::Acquier::Expire
         | 
| 6 | 
            -
              # @param redis [RedisClient]
         | 
| 7 | 
            -
              # @param lock_key [String]
         | 
| 8 | 
            -
              # @param block [Block]
         | 
| 9 | 
            -
              # @return [ | 
| 6 | 
            +
              # @param redis [RedisClient] Redis connection manager.
         | 
| 7 | 
            +
              # @param lock_key [String] Lock key to be expired.
         | 
| 8 | 
            +
              # @param block [Block] Custom logic that should be invoked unter the obtained lock.
         | 
| 9 | 
            +
              # @return [Any,NilClass] nil is returned no block parametr is provided.
         | 
| 10 10 | 
             
              #
         | 
| 11 11 | 
             
              # @api private
         | 
| 12 12 | 
             
              # @since 0.1.0
         | 
| @@ -30,37 +30,31 @@ module RedisQueuedLocks::Acquier::Release | |
| 30 30 | 
             
              # @api private
         | 
| 31 31 | 
             
              # @since 0.1.0
         | 
| 32 32 | 
             
              def fully_release_all_locks(redis, batch_size)
         | 
| 33 | 
            -
                 | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
                   | 
| 40 | 
            -
             | 
| 41 | 
            -
                ) do |lock_queue|
         | 
| 42 | 
            -
                  rel_queue_cnt += 1
         | 
| 43 | 
            -
                  rel_lock_cnt += 1
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  redis.pipelined do |pipeline|
         | 
| 33 | 
            +
                result = redis.pipelined do |pipeline|
         | 
| 34 | 
            +
                  # Step A: release all queus and their related locks
         | 
| 35 | 
            +
                  redis.scan(
         | 
| 36 | 
            +
                    'MATCH',
         | 
| 37 | 
            +
                    RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
         | 
| 38 | 
            +
                    count: batch_size
         | 
| 39 | 
            +
                  ) do |lock_queue|
         | 
| 40 | 
            +
                    puts "RELEASE (lock_queue): #{lock_queue}"
         | 
| 46 41 | 
             
                    pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
         | 
| 47 | 
            -
                    pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue))
         | 
| 42 | 
            +
                    pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), "0")
         | 
| 48 43 | 
             
                  end
         | 
| 49 | 
            -
                end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                # Step B: release all locks
         | 
| 52 | 
            -
                redis.pipelined do |pipeline|
         | 
| 53 | 
            -
                  rel_lock_cnt += 1
         | 
| 54 44 |  | 
| 45 | 
            +
                  # Step B: release all locks
         | 
| 55 46 | 
             
                  redis.scan(
         | 
| 56 47 | 
             
                    'MATCH',
         | 
| 57 48 | 
             
                    RedisQueuedLocks::Resource::LOCK_PATTERN,
         | 
| 58 49 | 
             
                    count: batch_size
         | 
| 59 50 | 
             
                  ) do |lock_key|
         | 
| 51 | 
            +
                    puts "RELEASE (lock_key): #{lock_key}"
         | 
| 60 52 | 
             
                    pipeline.call('EXPIRE', lock_key, '0')
         | 
| 61 53 | 
             
                  end
         | 
| 62 54 | 
             
                end
         | 
| 63 55 |  | 
| 64 | 
            -
                 | 
| 56 | 
            +
                rel_keys = result.count { |red_res| red_res == 0 }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                { ok: true, result: { rel_keys: rel_keys } }
         | 
| 65 59 | 
             
              end
         | 
| 66 60 | 
             
            end
         | 
| @@ -43,8 +43,8 @@ module RedisQueuedLocks::Acquier | |
| 43 43 | 
             
                #   Lifetime of the acuier's lock request. In seconds.
         | 
| 44 44 | 
             
                # @option timeout [Integer]
         | 
| 45 45 | 
             
                #   Time period whe should try to acquire the lock (in seconds).
         | 
| 46 | 
            -
                # @option retry_count [Integer]
         | 
| 47 | 
            -
                #   How many times we should try to acquire a lock.
         | 
| 46 | 
            +
                # @option retry_count [Integer,NilClass]
         | 
| 47 | 
            +
                #   How many times we should try to acquire a lock. Nil means "infinite retries".
         | 
| 48 48 | 
             
                # @option retry_delay [Integer]
         | 
| 49 49 | 
             
                #   A time-interval between the each retry (in milliseconds).
         | 
| 50 50 | 
             
                # @option retry_jitter [Integer]
         | 
| @@ -113,7 +113,7 @@ module RedisQueuedLocks::Acquier | |
| 113 113 | 
             
                      ) => { ok:, result: }
         | 
| 114 114 |  | 
| 115 115 | 
             
                      acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 116 | 
            -
                      acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil
         | 
| 116 | 
            +
                      acq_time = ((acq_end_time - acq_start_time) * 1_000).ceil(2)
         | 
| 117 117 |  | 
| 118 118 | 
             
                      # Step X: save the intermediate results to the result observer
         | 
| 119 119 | 
             
                      acq_process[:result] = result
         | 
| @@ -139,20 +139,20 @@ module RedisQueuedLocks::Acquier | |
| 139 139 | 
             
                        acq_process[:acquired] = true
         | 
| 140 140 | 
             
                        acq_process[:should_try] = false
         | 
| 141 141 | 
             
                        acq_process[:acq_time] = acq_time
         | 
| 142 | 
            +
                        acq_process[:acq_end_time] = acq_end_time
         | 
| 142 143 | 
             
                      else
         | 
| 143 144 | 
             
                        # Step 2.1.b: failed acquirement => retry
         | 
| 144 145 | 
             
                        acq_process[:tries] += 1
         | 
| 145 146 |  | 
| 146 | 
            -
                        if acq_process[:tries] >= retry_count
         | 
| 147 | 
            +
                        if retry_count != nil && acq_process[:tries] >= retry_count
         | 
| 147 148 | 
             
                          # NOTE: reached the retry limit => quit from the loop
         | 
| 148 149 | 
             
                          acq_process[:should_try] = false
         | 
| 149 150 | 
             
                          # NOTE: reached the retry limit => dequeue from the lock queue
         | 
| 150 151 | 
             
                          acq_dequeue.call
         | 
| 151 | 
            -
                         | 
| 152 | 
            +
                        elsif delay_execution(retry_delay, retry_jitter)
         | 
| 152 153 | 
             
                          # NOTE:
         | 
| 153 154 | 
             
                          #   delay the exceution in order to prevent chaotic attempts
         | 
| 154 155 | 
             
                          #   and to allow other processes and threads to obtain the lock too.
         | 
| 155 | 
            -
                          delay_execution(retry_delay, retry_jitter)
         | 
| 156 156 | 
             
                        end
         | 
| 157 157 | 
             
                      end
         | 
| 158 158 | 
             
                    end
         | 
| @@ -163,23 +163,24 @@ module RedisQueuedLocks::Acquier | |
| 163 163 | 
             
                    # Step 3.a: acquired successfully => run logic or return the result of acquirement
         | 
| 164 164 | 
             
                    if block_given?
         | 
| 165 165 | 
             
                      begin
         | 
| 166 | 
            -
                        yield_with_expire(redis, lock_key,  | 
| 166 | 
            +
                        yield_with_expire(redis, lock_key, &block)
         | 
| 167 167 | 
             
                      ensure
         | 
| 168 168 | 
             
                        acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 169 | 
            -
                        acq_process[:hold_time] = ( | 
| 170 | 
            -
                          acq_process[:rel_time] - acq_process[: | 
| 171 | 
            -
                        ) | 
| 169 | 
            +
                        acq_process[:hold_time] = (
         | 
| 170 | 
            +
                          (acq_process[:rel_time] - acq_process[:acq_end_time]) * 1000
         | 
| 171 | 
            +
                        ).ceil(2)
         | 
| 172 172 |  | 
| 173 173 | 
             
                        # Step X (instrumentation): lock_hold_and_release
         | 
| 174 | 
            -
                         | 
| 175 | 
            -
                           | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 174 | 
            +
                        run_non_critical do
         | 
| 175 | 
            +
                          instrumenter.notify('redis_queued_locks.lock_hold_and_release', {
         | 
| 176 | 
            +
                            hold_time: acq_process[:hold_time],
         | 
| 177 | 
            +
                            ttl: acq_process[:lock_info][:ttl],
         | 
| 178 | 
            +
                            acq_id: acq_process[:lock_info][:acq_id],
         | 
| 179 | 
            +
                            ts: acq_process[:lock_info][:ts],
         | 
| 180 | 
            +
                            lock_key: acq_process[:lock_info][:lock_key],
         | 
| 181 | 
            +
                            acq_time: acq_process[:acq_time]
         | 
| 182 | 
            +
                          })
         | 
| 183 | 
            +
                        end
         | 
| 183 184 | 
             
                      end
         | 
| 184 185 | 
             
                    else
         | 
| 185 186 | 
             
                      { ok: true, result: acq_process[:lock_info] }
         | 
| @@ -211,17 +212,19 @@ module RedisQueuedLocks::Acquier | |
| 211 212 | 
             
                  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
         | 
| 212 213 |  | 
| 213 214 | 
             
                  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 214 | 
            -
                   | 
| 215 | 
            +
                  fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
         | 
| 215 216 | 
             
                  time_at = Time.now.to_i
         | 
| 216 217 | 
             
                  rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 217 | 
            -
                  rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
         | 
| 218 | 
            +
                  rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
         | 
| 218 219 |  | 
| 219 | 
            -
                   | 
| 220 | 
            -
                     | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 220 | 
            +
                  run_non_critical do
         | 
| 221 | 
            +
                    instrumenter.notify('redis_queued_locks.explicit_lock_release', {
         | 
| 222 | 
            +
                      lock_key: lock_key,
         | 
| 223 | 
            +
                      lock_key_queue: lock_key_queue,
         | 
| 224 | 
            +
                      rel_time: rel_time,
         | 
| 225 | 
            +
                      at: time_at
         | 
| 226 | 
            +
                    })
         | 
| 227 | 
            +
                  end
         | 
| 225 228 |  | 
| 226 229 | 
             
                  { ok: true, result: result }
         | 
| 227 230 | 
             
                end
         | 
| @@ -239,17 +242,18 @@ module RedisQueuedLocks::Acquier | |
| 239 242 | 
             
                # @since 0.1.0
         | 
| 240 243 | 
             
                def release_all_locks!(redis, batch_size, instrumenter)
         | 
| 241 244 | 
             
                  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 242 | 
            -
                   | 
| 245 | 
            +
                  fully_release_all_locks(redis, batch_size) => { ok:, result: }
         | 
| 243 246 | 
             
                  time_at = Time.now.to_i
         | 
| 244 247 | 
             
                  rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 245 | 
            -
                  rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil
         | 
| 248 | 
            +
                  rel_time = ((rel_end_time - rel_start_time) * 1_000).ceil(2)
         | 
| 246 249 |  | 
| 247 | 
            -
                   | 
| 248 | 
            -
                     | 
| 249 | 
            -
             | 
| 250 | 
            -
             | 
| 251 | 
            -
             | 
| 252 | 
            -
             | 
| 250 | 
            +
                  run_non_critical do
         | 
| 251 | 
            +
                    instrumenter.notify('redis_queued_locks.explicit_all_locks_release', {
         | 
| 252 | 
            +
                      at: time_at,
         | 
| 253 | 
            +
                      rel_time: rel_time,
         | 
| 254 | 
            +
                      rel_keys: result[:rel_keys]
         | 
| 255 | 
            +
                    })
         | 
| 256 | 
            +
                  end
         | 
| 253 257 |  | 
| 254 258 | 
             
                  { ok: true, result: result }
         | 
| 255 259 | 
             
                end
         | 
| @@ -279,6 +283,15 @@ module RedisQueuedLocks::Acquier | |
| 279 283 | 
             
                    ERROR_MESSAGE
         | 
| 280 284 | 
             
                  end
         | 
| 281 285 | 
             
                end
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                # @param block [Block]
         | 
| 288 | 
            +
                # @return [Any]
         | 
| 289 | 
            +
                #
         | 
| 290 | 
            +
                # @api private
         | 
| 291 | 
            +
                # @since 0.1.0
         | 
| 292 | 
            +
                def run_non_critical(&block)
         | 
| 293 | 
            +
                  yield rescue nil
         | 
| 294 | 
            +
                end
         | 
| 282 295 | 
             
              end
         | 
| 283 296 | 
             
              # rubocop:enable Metrics/ClassLength
         | 
| 284 297 | 
             
            end
         | 
| @@ -12,15 +12,15 @@ class RedisQueuedLocks::Client | |
| 12 12 | 
             
                setting :retry_jitter, 50 # NOTE: milliseconds
         | 
| 13 13 | 
             
                setting :default_timeout, 10 # NOTE: seconds
         | 
| 14 14 | 
             
                setting :exp_precision, 1 # NOTE: milliseconds
         | 
| 15 | 
            -
                setting :default_lock_ttl,  | 
| 16 | 
            -
                setting :default_queue_ttl,  | 
| 15 | 
            +
                setting :default_lock_ttl, 5_000 # NOTE: milliseconds
         | 
| 16 | 
            +
                setting :default_queue_ttl, 30 # NOTE: seconds
         | 
| 17 17 | 
             
                setting :lock_release_batch_size, 100
         | 
| 18 18 | 
             
                setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
         | 
| 19 19 |  | 
| 20 20 | 
             
                # TODO: setting :logger, Logger.new(IO::NULL)
         | 
| 21 21 | 
             
                # TODO: setting :debug, true/false
         | 
| 22 22 |  | 
| 23 | 
            -
                validate('retry_count' | 
| 23 | 
            +
                validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
         | 
| 24 24 | 
             
                validate('retry_delay', :integer)
         | 
| 25 25 | 
             
                validate('retry_jitter', :integer)
         | 
| 26 26 | 
             
                validate('default_timeout', :integer)
         | 
| @@ -28,7 +28,7 @@ class RedisQueuedLocks::Client | |
| 28 28 | 
             
                validate('default_lock_tt', :integer)
         | 
| 29 29 | 
             
                validate('default_queue_ttl', :integer)
         | 
| 30 30 | 
             
                validate('lock_release_batch_size', :integer)
         | 
| 31 | 
            -
                validate('instrumenter') { | | 
| 31 | 
            +
                validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
         | 
| 32 32 | 
             
              end
         | 
| 33 33 |  | 
| 34 34 | 
             
              # @return [RedisClient]
         | 
| @@ -63,8 +63,8 @@ class RedisQueuedLocks::Client | |
| 63 63 | 
             
              #   Lifetime of the acuier's lock request. In seconds.
         | 
| 64 64 | 
             
              # @option timeout [Integer,NilClass]
         | 
| 65 65 | 
             
              #   Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
         | 
| 66 | 
            -
              # @option retry_count [Integer]
         | 
| 67 | 
            -
              #   How many times we should try to acquire a lock.
         | 
| 66 | 
            +
              # @option retry_count [Integer,NilClass]
         | 
| 67 | 
            +
              #   How many times we should try to acquire a lock. Nil means "infinite retries".
         | 
| 68 68 | 
             
              # @option retry_delay [Integer]
         | 
| 69 69 | 
             
              #   A time-interval between the each retry (in milliseconds).
         | 
| 70 70 | 
             
              # @option retry_jitter [Integer]
         |