redis-single-file 0.1.2 → 0.1.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/.rubocop.yml +4 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +34 -1
- data/README.md +119 -20
- data/benchmark.rb +1 -1
- data/lib/redis_single_file/cluster_client_builder.rb +22 -22
- data/lib/redis_single_file/configuration.rb +14 -2
- data/lib/redis_single_file/semaphore.rb +20 -13
- data/lib/redis_single_file/version.rb +1 -1
- data/test.rb +6 -4
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31564fcc0baa415b72345ffc55ff089881bf8329b7cf391b44fb4cac4b57fcc3
|
4
|
+
data.tar.gz: aab08c49cbd524899bfc3176cdfb9e88f953d5e28b38d2a1a43de281e2283622
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4457fb73a80ff69ac740cee3bf4edcca0f986001218b90a855a56aaea7099c8ecf039ab93d8d447890833b570e6053d29426cb69bdf78a9949b52fb333a183c
|
7
|
+
data.tar.gz: 45eee4ae50c3793bd45619685f68b23ed3c9d50287064aefae4d3ddb1c420ae40823adfac47b2677a2572c71f5c7794c61154aca2e2de7f45f6f0cd0ada1f562
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.6
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,37 @@
|
|
1
|
-
## [
|
1
|
+
## [0.1.3] - 2025-02-08
|
2
|
+
|
3
|
+
## What's Changed
|
4
|
+
|
5
|
+
**Full Changelog**: https://github.com/lifeBCE/redis-single-file/compare/v0.1.2...v0.1.3
|
6
|
+
|
7
|
+
---
|
8
|
+
<br />
|
9
|
+
|
10
|
+
## [0.1.2] - 2025-02-08
|
11
|
+
|
12
|
+
## What's Changed
|
13
|
+
* publish workflow by @lifeBCE in https://github.com/lifeBCE/redis-single-file/pull/3
|
14
|
+
* IGNORE_VERSION: "true" by @lifeBCE in https://github.com/lifeBCE/redis-single-file/pull/4
|
15
|
+
* cluster support - v0.1.2 by @lifeBCE in https://github.com/lifeBCE/redis-single-file/pull/5
|
16
|
+
|
17
|
+
**Full Changelog**: https://github.com/lifeBCE/redis-single-file/compare/v0.1.1...v0.1.2
|
18
|
+
|
19
|
+
---
|
20
|
+
<br />
|
21
|
+
|
22
|
+
## [0.1.1] - 2025-02-03
|
23
|
+
|
24
|
+
## What's Changed
|
25
|
+
* github workflows by @lifeBCE in https://github.com/lifeBCE/redis-single-file/pull/1
|
26
|
+
* badges by @lifeBCE in https://github.com/lifeBCE/redis-single-file/pull/2
|
27
|
+
|
28
|
+
## New Contributors
|
29
|
+
* @lifeBCE made their first contribution in https://github.com/lifeBCE/redis-single-file/pull/1
|
30
|
+
|
31
|
+
**Full Changelog**: https://github.com/lifeBCE/redis-single-file/commits/v0.1.1
|
32
|
+
|
33
|
+
---
|
34
|
+
<br />
|
2
35
|
|
3
36
|
## [0.1.0] - 2025-01-31
|
4
37
|
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
[](https://github.com/lifeBCE/redis-single-file/actions/workflows/build.yml)
|
2
2
|
[](https://github.com/lifeBCE/redis-single-file/actions/workflows/rspec.yml)
|
3
|
-
[](https://github.com/lifeBCE/redis-single-file/actions/workflows/codeql.yml)
|
4
3
|
[](https://github.com/lifeBCE/redis-single-file/actions/workflows/rubocop.yml)
|
5
|
-
[](https://github.com/lifeBCE/redis-single-file/actions/workflows/codeql.yml)
|
6
5
|
|
7
6
|
# Redis Single File - Distributed Execution Synchronization
|
8
7
|
|
@@ -37,6 +36,7 @@ RedisSingleFile.configuration do |config|
|
|
37
36
|
# config.port = '6379'
|
38
37
|
# config.name = 'default'
|
39
38
|
# config.expire_in = 300
|
39
|
+
# config.concurrency = 1
|
40
40
|
end
|
41
41
|
```
|
42
42
|
|
@@ -66,6 +66,14 @@ end
|
|
66
66
|
end
|
67
67
|
```
|
68
68
|
|
69
|
+
#### Support concurrent worker processing
|
70
|
+
```ruby
|
71
|
+
semaphore = RedisSingleFile.new(name: :concurrent_queue)
|
72
|
+
semaphore.synchronize(concurrency: 3) do
|
73
|
+
# synchronized logic defined here...
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
69
77
|
#### Use your own redis client instance
|
70
78
|
```ruby
|
71
79
|
redis = Redis.new(...)
|
@@ -79,27 +87,29 @@ end
|
|
79
87
|
|
80
88
|
### Distributed Queue Design
|
81
89
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
90
|
+
> [!IMPORTANT]
|
91
|
+
> The redis `blpop` command will attempt to pop (delete and return) a value from
|
92
|
+
> a queue but will block when no values are present in the queue. A timeout can
|
93
|
+
> be provided to prevent deadlock situations.
|
94
|
+
>
|
95
|
+
> To unblock (unlock) an instance, add/push an item to the queue. This is done
|
96
|
+
> one at a time to controll the serialization of the distrubuted execution. Redis
|
97
|
+
> selects the instance waiting the longest each time a new token is added.
|
89
98
|
|
90
99
|
### Auto Expiration
|
91
100
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
> [!NOTE]
|
102
|
+
> All redis keys are expired and automatically removed after a certain period
|
103
|
+
> but will be recreated again on the next use. Each new client should face one
|
104
|
+
> of two scenarios when entering synchronization.
|
105
|
+
>
|
106
|
+
> 1. The mutex key is not set causing the client to create the keys and prime
|
107
|
+
> the queue with its first token unlocking it for the first execution.
|
108
|
+
>
|
109
|
+
> 2. The mutex key is already set so the client will skip the priming and enter
|
110
|
+
> directly into the queue where it should immediately find a token left by
|
111
|
+
> the last client upon completion or block waiting for the current client to
|
112
|
+
> finish execution.
|
103
113
|
|
104
114
|
### Considerations over redlock approach
|
105
115
|
|
@@ -178,6 +188,95 @@ Comparison:
|
|
178
188
|
forked (10x): 56.6 i/s - 76.90x slower
|
179
189
|
```
|
180
190
|
|
191
|
+
## Cluster Management
|
192
|
+
|
193
|
+
After installing redis locally, you can use the provided `bin/cluster` script to manage a local cluster. To customize your local cluster, edit the `bin/cluster` script to provide your own values for the following script variables.
|
194
|
+
|
195
|
+
```bash
|
196
|
+
#
|
197
|
+
# configurable settings
|
198
|
+
#
|
199
|
+
HOST=127.0.0.1
|
200
|
+
PORT=30000
|
201
|
+
MASTERS=3 # min 3 for cluster
|
202
|
+
REPLICAS=2 # replicas per master
|
203
|
+
TIMEOUT=2000
|
204
|
+
PROTECTED_MODE=yes
|
205
|
+
ADDITIONAL_OPTIONS=""
|
206
|
+
```
|
207
|
+
|
208
|
+
<details>
|
209
|
+
<summary><strong>Start cluster nodes</strong></summary>
|
210
|
+
|
211
|
+
$ bin/cluster start
|
212
|
+
|
213
|
+
```console
|
214
|
+
Starting 30001
|
215
|
+
Starting 30002
|
216
|
+
Starting 30003
|
217
|
+
Starting 30004
|
218
|
+
Starting 30005
|
219
|
+
Starting 30006
|
220
|
+
Starting 30007
|
221
|
+
Starting 30008
|
222
|
+
Starting 30009
|
223
|
+
```
|
224
|
+
</details>
|
225
|
+
|
226
|
+
<details>
|
227
|
+
<summary><strong>Create cluster configuration</strong></summary>
|
228
|
+
|
229
|
+
$ bin/cluster create -f
|
230
|
+
|
231
|
+
```console
|
232
|
+
>>> Performing hash slots allocation on 9 nodes...
|
233
|
+
Master[0] -> Slots 0 - 5460
|
234
|
+
Master[1] -> Slots 5461 - 10922
|
235
|
+
Master[2] -> Slots 10923 - 16383
|
236
|
+
Adding replica 127.0.0.1:30005 to 127.0.0.1:30001
|
237
|
+
Adding replica 127.0.0.1:30006 to 127.0.0.1:30001
|
238
|
+
Adding replica 127.0.0.1:30007 to 127.0.0.1:30002
|
239
|
+
Adding replica 127.0.0.1:30008 to 127.0.0.1:30002
|
240
|
+
Adding replica 127.0.0.1:30009 to 127.0.0.1:30003
|
241
|
+
Adding replica 127.0.0.1:30004 to 127.0.0.1:30003
|
242
|
+
```
|
243
|
+
</details>
|
244
|
+
|
245
|
+
<details>
|
246
|
+
<summary><strong>Stop cluster nodes</strong></summary>
|
247
|
+
|
248
|
+
$ bin/cluster stop
|
249
|
+
|
250
|
+
```console
|
251
|
+
Stopping 30001
|
252
|
+
Stopping 30002
|
253
|
+
Stopping 30003
|
254
|
+
Stopping 30004
|
255
|
+
Stopping 30005
|
256
|
+
Stopping 30006
|
257
|
+
Stopping 30007
|
258
|
+
Stopping 30008
|
259
|
+
Stopping 30009
|
260
|
+
```
|
261
|
+
</details>
|
262
|
+
|
263
|
+
<details>
|
264
|
+
<summary><strong>Clean local cluster files</strong></summary>
|
265
|
+
|
266
|
+
$ bin/cluster clean
|
267
|
+
|
268
|
+
```console
|
269
|
+
Cleaning *.log
|
270
|
+
Cleaning appendonlydir-*
|
271
|
+
Cleaning dump-*.rdb
|
272
|
+
Cleaning nodes-*.conf
|
273
|
+
```
|
274
|
+
</details>
|
275
|
+
|
276
|
+
After the cluster is running and configured, you can direct the `test.rb` and `benchmark.rb` scripts at the cluster by setting the port on execution.
|
277
|
+
|
278
|
+
$ REDIS_PORT=30001 bundle exec ruby benchmark.rb
|
279
|
+
|
181
280
|
## Disclaimer
|
182
281
|
|
183
282
|
> [!WARNING]
|
data/benchmark.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'benchmark/ips'
|
4
4
|
require 'redis_single_file'
|
5
5
|
|
6
|
-
PORT = ENV['
|
6
|
+
PORT = ENV['REDIS_PORT'] || 6379
|
7
7
|
|
8
8
|
scenario_1_semaphore = RedisSingleFile.new(name: :scenario1, port: PORT)
|
9
9
|
scenario_2_semaphore = RedisSingleFile.new(name: :scenario2, port: PORT)
|
@@ -15,7 +15,7 @@ module RedisSingleFile
|
|
15
15
|
#
|
16
16
|
# Delegates class method calls to instance method
|
17
17
|
#
|
18
|
-
# @param [...] params
|
18
|
+
# @param [...] params passed directly to constructor
|
19
19
|
# @return [Redis::Cluster] redis cluster instance
|
20
20
|
def call(...) = new(...).call
|
21
21
|
end
|
@@ -40,15 +40,21 @@ module RedisSingleFile
|
|
40
40
|
def call
|
41
41
|
raise ClusterDisabledError, 'cluster not detected' unless cluster_enabled?
|
42
42
|
|
43
|
-
# use extracted client options with parsed nodes
|
44
|
-
Redis::Cluster.new(**client_options, nodes:)
|
43
|
+
# use extracted client options with parsed master nodes
|
44
|
+
Redis::Cluster.new(**client_options, nodes: master_nodes)
|
45
45
|
end
|
46
46
|
|
47
47
|
private # ==================================================================
|
48
48
|
|
49
49
|
attr_reader :redis
|
50
50
|
|
51
|
-
def
|
51
|
+
def cluster_enabled?
|
52
|
+
redis.info('cluster')['cluster_enabled'] == '1'
|
53
|
+
rescue Redis::CommandError
|
54
|
+
false # assume cluster mode is disabled
|
55
|
+
end
|
56
|
+
|
57
|
+
def master_nodes
|
52
58
|
cluster_nodes.filter_map do |node|
|
53
59
|
next unless node[:flags].include?('master')
|
54
60
|
|
@@ -56,10 +62,18 @@ module RedisSingleFile
|
|
56
62
|
end
|
57
63
|
end
|
58
64
|
|
59
|
-
def
|
60
|
-
redis.
|
61
|
-
|
62
|
-
|
65
|
+
def client_options
|
66
|
+
config = redis._client.config
|
67
|
+
params = %i[
|
68
|
+
db ssl host port path custom username password protocol
|
69
|
+
ssl_params read_timeout write_timeout connect_timeout
|
70
|
+
]
|
71
|
+
|
72
|
+
params_hash = params.each.with_object({}) do |key, memo|
|
73
|
+
memo[key] = config.public_send(key)
|
74
|
+
end
|
75
|
+
|
76
|
+
params_hash.merge(url: config.server_url)
|
63
77
|
end
|
64
78
|
|
65
79
|
def cluster_nodes
|
@@ -82,19 +96,5 @@ module RedisSingleFile
|
|
82
96
|
}
|
83
97
|
end
|
84
98
|
end
|
85
|
-
|
86
|
-
def client_options
|
87
|
-
config = redis._client.config
|
88
|
-
params = %i[
|
89
|
-
db ssl host port path custom username password protocol
|
90
|
-
ssl_params read_timeout write_timeout connect_timeout
|
91
|
-
]
|
92
|
-
|
93
|
-
params_hash = params.each.with_object({}) do |key, memo|
|
94
|
-
memo[key] = config.public_send(key)
|
95
|
-
end
|
96
|
-
|
97
|
-
params_hash.merge(url: config.server_url)
|
98
|
-
end
|
99
99
|
end
|
100
100
|
end
|
@@ -25,6 +25,7 @@ module RedisSingleFile
|
|
25
25
|
DEFAULT_EXPIRE_IN = 300 # 5 mins
|
26
26
|
DEFAULT_MUTEX_KEY = 'RedisSingleFile/Mutex/%s'
|
27
27
|
DEFAULT_QUEUE_KEY = 'RedisSingleFile/Queue/%s'
|
28
|
+
DEFAULT_CONCURRENCY = 1 # single slot enabled
|
28
29
|
|
29
30
|
# class delegation methods to singleton instance
|
30
31
|
#
|
@@ -33,13 +34,21 @@ module RedisSingleFile
|
|
33
34
|
# Configuration.port => Configuration.instance.port
|
34
35
|
#
|
35
36
|
class << self
|
36
|
-
%i[
|
37
|
+
%i[
|
38
|
+
host
|
39
|
+
port
|
40
|
+
name
|
41
|
+
expire_in
|
42
|
+
concurrency
|
43
|
+
mutex_key
|
44
|
+
queue_key
|
45
|
+
].each do |attr|
|
37
46
|
define_method(attr) { instance.send(attr) }
|
38
47
|
end
|
39
48
|
end
|
40
49
|
|
41
50
|
# writers used in config block to set new values
|
42
|
-
attr_writer :host, :port, :name, :expire_in
|
51
|
+
attr_writer :host, :port, :name, :expire_in, :concurrency
|
43
52
|
|
44
53
|
# @return [String] redis server hostname value
|
45
54
|
def host = @host || DEFAULT_HOST
|
@@ -53,6 +62,9 @@ module RedisSingleFile
|
|
53
62
|
# @return [String] redis keys expiration value
|
54
63
|
def expire_in = @expire_in || DEFAULT_EXPIRE_IN
|
55
64
|
|
65
|
+
# @return [String] redis lock concurrency value
|
66
|
+
def concurrency = @concurrency || DEFAULT_CONCURRENCY
|
67
|
+
|
56
68
|
# @note This attr is not configurable
|
57
69
|
# @return [String] synchronization mutex key name
|
58
70
|
def mutex_key = @mutex_key || DEFAULT_MUTEX_KEY
|
@@ -12,6 +12,7 @@ module RedisSingleFile
|
|
12
12
|
# @attr name [String] custom sync queue name
|
13
13
|
# @attr host [String] host for redis server
|
14
14
|
# @attr port [String] port for redis server
|
15
|
+
# @attr concurrency [Integer] simultaneous slots allowed
|
15
16
|
#
|
16
17
|
# @example Default lock name and infinite blocking
|
17
18
|
# semaphore = RedisSingleFile::Semaphore.new
|
@@ -65,23 +66,26 @@ module RedisSingleFile
|
|
65
66
|
redis: nil, # provide your own redis instance
|
66
67
|
name: Configuration.name, # designate queue name per session
|
67
68
|
host: Configuration.host, # designate redis host per session
|
68
|
-
port: Configuration.port
|
69
|
+
port: Configuration.port, # designate redis port per session
|
70
|
+
concurrency: Configuration.concurrency # concurrent workers
|
69
71
|
)
|
70
72
|
@redis = redis || Redis.new(host:, port:)
|
71
73
|
|
72
74
|
@mutex_val = name
|
73
75
|
@mutex_key = format(Configuration.mutex_key, @mutex_val)
|
74
76
|
@queue_key = format(Configuration.queue_key, @mutex_val)
|
77
|
+
@concurrency = concurrency.to_i
|
75
78
|
end
|
76
79
|
|
77
80
|
# Queues up client and waits for turn to execute. Returns nil
|
78
81
|
# when queue wait time expires.
|
79
82
|
#
|
80
83
|
# @param timeout [Integer] seconds for client to wait in queue
|
84
|
+
# @param concurrency [Integer] override concurrent workers
|
81
85
|
# @yieldreturn [...] response from synchronized block execution
|
82
86
|
# @return [nil] redis blpop timeout
|
83
|
-
def synchronize(timeout: 0, &)
|
84
|
-
synchronize!(timeout:, &)
|
87
|
+
def synchronize(timeout: 0, concurrency: @concurrency, &)
|
88
|
+
synchronize!(timeout:, concurrency:, &)
|
85
89
|
rescue QueueTimeoutError => _e
|
86
90
|
nil
|
87
91
|
end
|
@@ -90,14 +94,15 @@ module RedisSingleFile
|
|
90
94
|
# when queue wait time expires.
|
91
95
|
#
|
92
96
|
# @param timeout [Integer] seconds for blpop to wait in queue
|
97
|
+
# @param concurrency [Integer] override concurrent workers
|
93
98
|
# @yieldreturn [...] response from synchronized block execution
|
94
99
|
# @raise [QueueTimeoutError] redis blpop timeout
|
95
|
-
def synchronize!(timeout: 0)
|
100
|
+
def synchronize!(timeout: 0, concurrency: @concurrency)
|
96
101
|
return unless block_given?
|
97
102
|
|
98
103
|
with_retry_protection do
|
99
|
-
prime_queue unless redis.getset(mutex_key, mutex_val)
|
100
|
-
raise QueueTimeoutError
|
104
|
+
prime_queue(concurrency) unless redis.getset(mutex_key, mutex_val)
|
105
|
+
raise QueueTimeoutError unless redis.blpop(queue_key, timeout:)
|
101
106
|
|
102
107
|
redis.multi do
|
103
108
|
redis.persist(mutex_key) # unexpire during execution
|
@@ -108,7 +113,7 @@ module RedisSingleFile
|
|
108
113
|
yield
|
109
114
|
ensure
|
110
115
|
# always cycle the queue when exiting
|
111
|
-
unlock_queue if block_given?
|
116
|
+
unlock_queue(concurrency) if block_given?
|
112
117
|
end
|
113
118
|
|
114
119
|
private #===================================================================
|
@@ -119,20 +124,22 @@ module RedisSingleFile
|
|
119
124
|
@expire_in ||= Configuration.expire_in
|
120
125
|
end
|
121
126
|
|
122
|
-
def prime_queue
|
127
|
+
def prime_queue(concurrency)
|
123
128
|
with_retry_protection do
|
124
129
|
redis.multi do
|
125
|
-
redis.del(queue_key)
|
126
|
-
|
130
|
+
redis.del(queue_key) # remove existing queue
|
131
|
+
concurrency.times do # create and prime new queue
|
132
|
+
redis.lpush(queue_key, '1')
|
133
|
+
end
|
127
134
|
end
|
128
135
|
end
|
129
136
|
end
|
130
137
|
|
131
|
-
def unlock_queue
|
138
|
+
def unlock_queue(concurrency)
|
132
139
|
with_retry_protection do
|
133
140
|
redis.multi do
|
134
|
-
# queue next client execution if queue
|
135
|
-
redis.lpush(queue_key, '1') if redis.llen(queue_key)
|
141
|
+
# queue next client execution if queue has space (concurrency)
|
142
|
+
redis.lpush(queue_key, '1') if redis.llen(queue_key) < concurrency
|
136
143
|
redis.expire(mutex_key, expire_in) # set expiration for auto removal
|
137
144
|
redis.expire(queue_key, expire_in) # set expiration for auto removal
|
138
145
|
end
|
data/test.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'pry'
|
4
|
+
require 'securerandom'
|
4
5
|
require 'redis_single_file'
|
5
6
|
|
6
|
-
|
7
|
+
PORT = ENV['REDIS_PORT'] || 6379
|
8
|
+
RUN_ID = SecureRandom.uuid
|
7
9
|
|
8
10
|
ITERATIONS = (ARGV[0] || 10).to_i
|
9
11
|
WORK_LOAD = (ARGV[1] || 1).to_i
|
10
12
|
TIMEOUT = ITERATIONS * WORK_LOAD
|
11
13
|
|
12
|
-
#semaphore = RedisSingleFile.new(name: RUN_ID, port:
|
14
|
+
#semaphore = RedisSingleFile.new(name: RUN_ID, port: PORT)
|
13
15
|
#semaphore.synchronize!(timeout: 10) do
|
14
16
|
# puts "Hello World!"
|
15
17
|
# sleep 1
|
@@ -19,7 +21,7 @@ TIMEOUT = ITERATIONS * WORK_LOAD
|
|
19
21
|
|
20
22
|
#10.times.map do
|
21
23
|
# fork do
|
22
|
-
# semaphore = RedisSingleFile.new(name: RUN_ID)
|
24
|
+
# semaphore = RedisSingleFile.new(name: RUN_ID, port: PORT)
|
23
25
|
# semaphore.synchronize!(timeout: TIMEOUT) do
|
24
26
|
# puts "Hello World!"
|
25
27
|
# sleep WORK_LOAD
|
@@ -35,7 +37,7 @@ TIMEOUT = ITERATIONS * WORK_LOAD
|
|
35
37
|
|
36
38
|
threads = ITERATIONS.times.map do
|
37
39
|
thread = Thread.new do
|
38
|
-
semaphore = RedisSingleFile.new(name: RUN_ID, port:
|
40
|
+
semaphore = RedisSingleFile.new(name: RUN_ID, port: PORT)
|
39
41
|
semaphore.synchronize(timeout: TIMEOUT) do
|
40
42
|
puts "Hello World!"
|
41
43
|
sleep WORK_LOAD
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-single-file
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LifeBCE
|
8
|
-
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
9
10
|
cert_chain: []
|
10
|
-
date: 2025-
|
11
|
+
date: 2025-10-04 00:00:00.000000000 Z
|
11
12
|
dependencies:
|
12
13
|
- !ruby/object:Gem::Dependency
|
13
14
|
name: redis
|
@@ -46,6 +47,7 @@ extra_rdoc_files: []
|
|
46
47
|
files:
|
47
48
|
- ".rspec"
|
48
49
|
- ".rubocop.yml"
|
50
|
+
- ".ruby-version"
|
49
51
|
- CHANGELOG.md
|
50
52
|
- LICENSE.txt
|
51
53
|
- README.md
|
@@ -63,7 +65,10 @@ licenses:
|
|
63
65
|
- MIT
|
64
66
|
metadata:
|
65
67
|
homepage_uri: https://github.com/lifeBCE/redis-single-file
|
66
|
-
|
68
|
+
allowed_push_host: https://rubygems.org
|
69
|
+
changelog_uri: https://github.com/lifeBCE/redis-single-file/blob/main/CHANGELOG.md
|
70
|
+
rubygems_mfa_required: 'false'
|
71
|
+
post_install_message:
|
67
72
|
rdoc_options: []
|
68
73
|
require_paths:
|
69
74
|
- lib
|
@@ -78,7 +83,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
83
|
- !ruby/object:Gem::Version
|
79
84
|
version: '0'
|
80
85
|
requirements: []
|
81
|
-
rubygems_version: 3.
|
86
|
+
rubygems_version: 3.5.22
|
87
|
+
signing_key:
|
82
88
|
specification_version: 4
|
83
89
|
summary: Distributed semaphore implementation with redis.
|
84
90
|
test_files: []
|