redis_queued_locks 1.9.0 → 1.10.0

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 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