redis_queued_locks 1.9.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f787fbfe22ef8fe8dec78e5b17d4305621a4b9e2df7d3582648959e68dabef09
4
- data.tar.gz: 8d874d80738959f41985e6e52d6668aa63d8232ca4964006be82f5d67ea7e1a6
3
+ metadata.gz: a2028a137ac36d716a0f9728656f536c373d21691fe64bf5fa1970db160fbe51
4
+ data.tar.gz: 3d065601fac650cf7d2a8fcb63745ffbf91ecd2cc553d936c693b2e45fee752f
5
5
  SHA512:
6
- metadata.gz: eb726c270398079b142d68e3e45dd93226e1f17b52f370107db2f4cc47bfa6b0ce010f55dec029443bfb932d73f905f61040087577f47dca9cef5ad505fca430
7
- data.tar.gz: 129089ba8f764ecabae8d0598af0f627a1156bb8b1a3e845697f1f32863d7ccd3eb2cd64cb80406408e8518b9809b35cce36e1253598744d52529d32cb092598
6
+ metadata.gz: e7d6698032bab56ebb0cc85abf3b13bcffe499c019708b845962bb626ac0e27d3da35c27462816c3c37d664117ba3c6a8c916dca231fb9fffdb862695cf2c261
7
+ data.tar.gz: 7b139c4bff4587783dc0df0f67bc7b985569ff5590aaaa8cb4370cdf2d3f9f9954bf9c99ade1a60e47e48ac4c450b58c308dce32ab10e0e1b5a40d6003ed5019
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.3
1
+ 3.3.4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.10.0] - 2024-08-11
4
+ ### Changed
5
+ - Timed invocations (`"timeed blocks of code"` / `timed: true`):
6
+ - the way of timeout error interception has been changed:
7
+ - instead of the `rescue Timeout::Error` around the block of code the timeout interception
8
+ now uses `Timeout#timeout`'s custom exception class/message replacement API;
9
+ - `rescue Timeout::Error` can lead to incorrect exception interception: intercept block's-related
10
+ Timeout::Error that is not RQL-related error;
11
+ ### Added
12
+ - `RedisQueuedLocks::Swarm`: missing YARDocs;
13
+ - Separated `Logging Configuration` readme section (that is placed inside the main configuration section already);
14
+ - Separated `Instrumentation Configuration` readme section (that is placed inside the main configuration section already);
15
+ ### Removed
16
+ - `RedisQueudLocks::Swarm`: some useless YARDocs;
17
+
3
18
  ## [1.9.0] - 2024-07-15
4
19
  ### Added
5
20
  - Brand New Extremely Major Feature: **Swarm Mode** - eliminate zombie locks with a swarm:
data/README.md CHANGED
@@ -54,9 +54,11 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
54
54
  - [Lock Access Strategies](#lock-access-strategies)
55
55
  - [queued](#lock-access-strategies)
56
56
  - [random](#lock-access-strategies)
57
- - [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks)
57
+ - [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks)
58
58
  - [Logging](#logging)
59
+ - [Logging Configuration](#logging-configuration)
59
60
  - [Instrumentation](#instrumentation)
61
+ - [Instrumentation Configuration](#instrumentation-configuration)
60
62
  - [Instrumentation Events](#instrumentation-events)
61
63
  - [Roadmap](#roadmap)
62
64
  - [Contributing](#contributing)
@@ -467,7 +469,7 @@ def lock(
467
469
  - `:extendable_work_through` - continue working under the lock **with** lock's TTL extension;
468
470
  - `:wait_for_lock` - (default) - work in classic way (with timeouts, retry delays, retry limits, etc - in classic way :));
469
471
  - `:dead_locking` - fail with deadlock exception;
470
- - See [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) documentation section for details;
472
+ - See [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) documentation section for details;
471
473
  - `identity` - (optional) `[String]`
472
474
  - An unique string that is unique per `RedisQueuedLock::Client` instance. Resolves the
473
475
  collisions between the same process_id/thread_id/fiber_id/ractor_id identifiers on different
@@ -586,7 +588,7 @@ Return value:
586
588
  - `:extendable_conflict_work_through` - reentrant lock acquiring process with lock's TTL extension. Suitable for `conflict_strategy: :extendable_work_through`;
587
589
  - `:conflict_work_through` - reentrant lock acquiring process without lock's TTL extension. Suitable for `conflict_strategy: :work_through`;
588
590
  - `:dead_locking` - current process tries to acquire a lock that is already acquired by them. Suitalbe for `conflict_startegy: :dead_locking`;
589
- - For more details see [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) readme section;
591
+ - For more details see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) readme section;
590
592
  - For successful lock obtaining:
591
593
  ```ruby
592
594
  {
@@ -786,7 +788,7 @@ rql.lock('my_lock', retry_delay: 3000, ttl: 3000, access_strategy: :random)
786
788
  - (`RedisQueuedLocks::LockAlreadyObtainedError`) when `fail_fast` is `true` and lock is already obtained;
787
789
  - (`RedisQueuedLocks::LockAcquiermentTimeoutError`) `timeout` limit reached before lock is obtained;
788
790
  - (`RedisQueuedLocks::LockAcquiermentRetryLimitError`) `retry_count` limit reached before lock is obtained;
789
- - (`RedisQueuedLocks::ConflictLockObtainError`) when `conflict_strategy: :dead_locking` is used and the "same-process-dead-lock" is happened (see [Dead locks and Reentrant locks](#dead-locks-and-reentrant-locks) for details);
791
+ - (`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);
790
792
 
791
793
  ```ruby
792
794
  def lock!(
@@ -1479,7 +1481,7 @@ rql.current_acquirer_id
1479
1481
  <sup>\[[back to top](#usage)\]</sup>
1480
1482
 
1481
1483
  - get a current host identifier in RQL notation that you can use for debugging purposes during the lock analyzis;
1482
- - the host is a ruby worker (a combination of process/thread/ractor) that is alive and can obtain locks;
1484
+ - the host is a ruby worker (a combination of process/thread/ractor/identity) that is alive and can obtain locks;
1483
1485
  - the host is limited to `process`/`thread`/`ractor` (without `fiber`) combination cuz we have no abilities to extract
1484
1486
  all fiber objects from the current ruby process when at least one ractor object is defined (**ObjectSpace** loses
1485
1487
  abilities to extract `Fiber` and `Thread` objects after the any ractor is created) (`Thread` objects are analyzed
@@ -1559,7 +1561,7 @@ rql.possible_host_ids
1559
1561
 
1560
1562
  > Eliminate zombie locks with a swarm.
1561
1563
 
1562
- **This documentation section is in progress!**
1564
+ **This documentation section is in progress!** (see the changelog and the usage preview for details at this moment)
1563
1565
 
1564
1566
  [(work and usage preview (temporary example-based docs))](#work-and-usage-preview-temporary-example-based-docs)
1565
1567
 
@@ -1615,6 +1617,7 @@ rql.possible_host_ids
1615
1617
  config.swarm.flush_zombies.redis_config.pooled = false # NOTE: individual redis config
1616
1618
  config.swarm.flush_zombies.redis_config.config = {} # NOTE: individual redis config
1617
1619
  config.swarm.flush_zombies.redis_config.pool_config = {} # NOTE: individual redis config
1620
+ end
1618
1621
  ```
1619
1622
  </details>
1620
1623
 
@@ -1763,7 +1766,7 @@ rql.possible_host_ids
1763
1766
 
1764
1767
  ---
1765
1768
 
1766
- ## Dead locks and Reentrant locks
1769
+ ## Deadlocks and Reentrant locks
1767
1770
 
1768
1771
  <sup>\[[back to top](#table-of-contents)\]</sup>
1769
1772
 
@@ -1785,6 +1788,9 @@ rql.possible_host_ids
1785
1788
 
1786
1789
  <sup>\[[back to top](#table-of-contents)\]</sup>
1787
1790
 
1791
+ - [Logging Configuration](#logging-configuration)
1792
+
1793
+
1788
1794
  - default logs (raised from `#lock`/`#lock!`):
1789
1795
 
1790
1796
  ```ruby
@@ -1820,14 +1826,87 @@ rql.possible_host_ids
1820
1826
 
1821
1827
  ---
1822
1828
 
1829
+ ### Logging Configuration
1830
+
1831
+ <sup>\[[back to top](#table-of-contents)\]</sup>
1832
+
1833
+ **NOTICE**: logging can be sampled via:
1834
+ - `config.log_samplign_enabled = true` (**false** by default);
1835
+ - `config.log_sampler = RedisQueuedLocks::Logging::Sampler` (used by default);
1836
+ - see **RedisQueuedLocks::Logging::Sampler** implementation in source code for customization details;
1837
+
1838
+ ```ruby
1839
+ # (default: RedisQueuedLocks::Logging::VoidLogger)
1840
+ # - the logger object;
1841
+ # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
1842
+ # - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
1843
+ # - at this moment the only debug logs are realised in following cases:
1844
+ # - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1845
+ # - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1846
+ # - "[redis_queued_locks.dead_score_reached__reset_acquier_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1847
+ # - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
1848
+ # - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
1849
+ # - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acq_time", "acs_strat");
1850
+ # - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1851
+ # - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1852
+ # - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1853
+ # - by default uses VoidLogger that does nothing;
1854
+ config.logger = RedisQueuedLocks::Logging::VoidLogger
1855
+
1856
+ # (default: false)
1857
+ # - adds additional debug logs;
1858
+ # - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
1859
+ # - it adds following debug logs in addition to the existing:
1860
+ # - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1861
+ # - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1862
+ # - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1863
+ # - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status");
1864
+ # - "[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");
1865
+ # - "[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);
1866
+ # - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat")";
1867
+ # - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1868
+ # - "[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");
1869
+ # - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1870
+ # - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "first_acq_id_in_queue", "<current_lock_data>");
1871
+ # - "[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", "<current_lock_data>");
1872
+ # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
1873
+ config.log_lock_try = false
1874
+
1875
+ # (default: false)
1876
+ # - enables <log sampling>: only the configured percent of RQL cases will be logged;
1877
+ # - disabled by default;
1878
+ # - works in tandem with <config.log_sampling_percent> and <log.sampler> configs;
1879
+ config.log_sampling_enabled = false
1880
+
1881
+ # (default: 15)
1882
+ # - the percent of cases that should be logged;
1883
+ # - take an effect when <config.log_sampling_enalbed> is true;
1884
+ # - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
1885
+ config.log_sampling_percent = 15
1886
+
1887
+ # (default: RedisQueuedLocks::Logging::Sampler)
1888
+ # - percent-based log sampler that decides should be RQL case logged or not;
1889
+ # - works in tandem with <config.log_sampling_enabled> and <config.log_sampling_percent> configs;
1890
+ # - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
1891
+ # method so the algorithm error is ~(0%..13%);
1892
+ # - you can provide your own log sampler with bettter algorithm that should realize
1893
+ # `sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);
1894
+ config.log_sampler = RedisQueuedLocks::Logging::Sampler
1895
+ ```
1896
+
1897
+ ---
1898
+
1823
1899
  ## Instrumentation
1824
1900
 
1825
1901
  <sup>\[[back to top](#table-of-contents)\]</sup>
1826
1902
 
1827
1903
  - [Instrumentation Events](#instrumentation-events)
1904
+ - [Instrumentation Configuration](#instrumentation-configuration)
1828
1905
 
1829
1906
  An instrumentation layer is incapsulated in `instrumenter` object stored in [config](#configuration) (`RedisQueuedLocks::Client#config[:instrumenter]`).
1830
1907
 
1908
+ Instrumentation can be sampled. See [Instrumentation Configuration](#instrumentation-configuration) section for details.
1909
+
1831
1910
  Instrumenter object should provide `notify(event, payload)` method with the following signarue:
1832
1911
 
1833
1912
  - `event` - `string`;
@@ -1843,6 +1922,48 @@ By default `RedisQueuedLocks::Client` is configured with the void notifier (whic
1843
1922
 
1844
1923
  ---
1845
1924
 
1925
+ ### Instrumentation Configuration
1926
+
1927
+ <sup>\[[back to top](#table-of-contents)\]</sup>
1928
+
1929
+ **NOTICE**: instrumentation can be sampled via:
1930
+ - `config.instr_sampling_enabled = true` (**false** by default);
1931
+ - `config.instr_sampler = RedisQueuedLocks::Instrument::Sampler` (used by default);
1932
+ - see **RedisQueuedLocks::Instrument::Sampler** implementation in source code for customization details;
1933
+
1934
+ ```ruby
1935
+ # (default: RedisQueuedLocks::Instrument::VoidNotifier)
1936
+ # - instrumentation layer;
1937
+ # - you can provide your own instrumenter that should realize `#notify(event, payload = {})` interface:
1938
+ # - event: <string> requried;
1939
+ # - payload: <hash> requried;
1940
+ # - disabled by default via `VoidNotifier`;
1941
+ config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport
1942
+
1943
+ # (default: false)
1944
+ # - enables <instrumentaion sampling>: only the configured percent of RQL cases will be instrumented;
1945
+ # - disabled by default;
1946
+ # - works in tandem with <config.instr_sampling_percent and <log.instr_sampler>;
1947
+ config.instr_sampling_enabled = false
1948
+
1949
+ # (default: 15)
1950
+ # - the percent of cases that should be instrumented;
1951
+ # - take an effect when <config.instr_sampling_enalbed> is true;
1952
+ # - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampler> configs;
1953
+ config.instr_sampling_percent = 15
1954
+
1955
+ # (default: RedisQueuedLocks::Instrument::Sampler)
1956
+ # - percent-based log sampler that decides should be RQL case instrumented or not;
1957
+ # - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampling_percent> configs;
1958
+ # - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
1959
+ # method so the algorithm error is ~(0%..13%);
1960
+ # - you can provide your own log sampler with bettter algorithm that should realize
1961
+ # `sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Instrument::Sampler` for example);
1962
+ config.instr_sampler = RedisQueuedLocks::Instrument::Sampler
1963
+ ```
1964
+
1965
+ ---
1966
+
1846
1967
  ### Instrumentation Events
1847
1968
 
1848
1969
  <sup>\[[back to top](#instrumentation)\]</sup>
@@ -109,18 +109,26 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
109
109
  #
110
110
  # @api private
111
111
  # @since 1.3.0
112
- # @version 1.9.0
112
+ # @version 1.10.0
113
113
  def yield_with_timeout(timeout, lock_key, lock_ttl, acquier_id, host_id, meta, &block)
114
- ::Timeout.timeout(timeout, &block)
115
- rescue ::Timeout::Error
116
- raise(
114
+ ::Timeout.timeout(
115
+ timeout,
117
116
  RedisQueuedLocks::TimedLockTimeoutError,
117
+ # NOTE:
118
+ # - this string is generated on each invocation cuz we need to raise custom exception
119
+ # from the Timeout API in order to prevent incorred Timeout::Error interception when
120
+ # the intercepted error is caused from the block not from the RQL;
121
+ # - we can't omit this string object creation at the moment via original Ruby's Timeout API;
122
+ # TODO:
123
+ # - refine Timeout#timeout (via Ruby's Refinement API) in order to provide lazy exception
124
+ # message initialization;
118
125
  "Passed <timed> block of code exceeded the lock TTL " \
119
126
  "(lock: \"#{lock_key}\", " \
120
127
  "ttl: #{lock_ttl}, " \
121
128
  "meta: #{meta ? meta.inspect : '<no-meta>'}, " \
122
129
  "acq_id: \"#{acquier_id}\", " \
123
- "hst_id: \"#{host_id}\")"
130
+ "hst_id: \"#{host_id}\")",
131
+ &block
124
132
  )
125
133
  end
126
134
  end
@@ -130,6 +130,8 @@ class RedisQueuedLocks::Swarm
130
130
  )
131
131
  end
132
132
 
133
+ # @option zombie_ttl [Integer]
134
+ # @option lock_scan_size [Integer]
133
135
  # @return [Set<String>]
134
136
  #
135
137
  # @api public
@@ -145,6 +147,8 @@ class RedisQueuedLocks::Swarm
145
147
  )
146
148
  end
147
149
 
150
+ # @option zombie_ttl [Integer]
151
+ # @option lock_scan_size [Integer]
148
152
  # @return [Set<String>]
149
153
  #
150
154
  # @api ppublic
@@ -160,6 +164,7 @@ class RedisQueuedLocks::Swarm
160
164
  )
161
165
  end
162
166
 
167
+ # @option zombie_ttl [Integer]
163
168
  # @return [Set<String>]
164
169
  #
165
170
  # @api public
@@ -168,6 +173,8 @@ class RedisQueuedLocks::Swarm
168
173
  RedisQueuedLocks::Swarm::ZombieInfo.zombie_hosts(rql_client.redis_client, zombie_ttl)
169
174
  end
170
175
 
176
+ # @option zombie_ttl [Integer]
177
+ # @option lock_sacn_size [Integer]
171
178
  # @return [Hash<Symbol,Set<String>>]
172
179
  # Format: {
173
180
  # zombie_hosts: <Set<String>>,
@@ -188,7 +195,6 @@ class RedisQueuedLocks::Swarm
188
195
  )
189
196
  end
190
197
 
191
- # @option silently [Boolean]
192
198
  # @return [NilClass,Hash<Symbol,Symbol|Boolean>]
193
199
  #
194
200
  # @api public
@@ -220,7 +226,6 @@ class RedisQueuedLocks::Swarm
220
226
  end
221
227
  end
222
228
 
223
- # @option silently [Boolean]
224
229
  # @return [NilClass,Hash<Symbol,Symbol|Boolean>]
225
230
  #
226
231
  # @api public
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 1.9.0
9
- VERSION = '1.9.0'
8
+ # @version 1.10.0
9
+ VERSION = '1.10.0'
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-15 00:00:00.000000000 Z
11
+ date: 2024-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client