redis-read-write-locks 0.4.0 → 0.5.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 +4 -4
- data/README.md +5 -2
- data/lib/redis-read-write-locks.rb +2 -0
- data/lib/redis_read_write_locks/base_lock.rb +57 -5
- data/lib/redis_read_write_locks/client.rb +5 -2
- data/lib/redis_read_write_locks/errors.rb +13 -3
- data/lib/redis_read_write_locks/lock_scripts.rb +81 -16
- data/lib/redis_read_write_locks/read_lock.rb +13 -3
- data/lib/redis_read_write_locks/version.rb +3 -1
- data/lib/redis_read_write_locks/write_lock.rb +27 -2
- data/lib/redis_read_write_locks.rb +2 -0
- data/redis-read-write-locks.gemspec +2 -0
- metadata +29 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c9e310b7b077bb24539719d830b79839dde4f00dc0634d4a2ccbb9e91809105a
|
|
4
|
+
data.tar.gz: fffb72914195bd66e69ebb800fb0a2fe5bb0a39b79b3d73e912335e4fc968ce3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d1f9430aa04d71be97759004e36786de7f99aa845b6379d776718fc9adac65c486765a2ce6096ff4a22d4c273f14c5d40b4c128b782071b0a80c4ed4fc1fae3
|
|
7
|
+
data.tar.gz: 03a01ad0f6fa2906d2934fcdc23ae12bef0b77448936de949d7422c61d6ec98d004b58648e31517e1edf5466ed6a33633958b92e6bb9ab6c28e885a2011f053e
|
data/README.md
CHANGED
|
@@ -49,6 +49,7 @@ client = RedisReadWriteLocks::Client.new(redis, default_ttl: 60_000)
|
|
|
49
49
|
| Option | Default | Description |
|
|
50
50
|
|--------|---------|-------------|
|
|
51
51
|
| `ttl` | `30` | Lock TTL in seconds. Lock auto-expires if holder crashes. |
|
|
52
|
+
| `prefer_writer` | `false` | Write lock only. When `true`, a writer blocked on acquire registers its intent so new readers are refused until it acquires or gives up. Only takes effect when `retry_count` is set — a non-retrying writer isn't waiting, so it registers and immediately clears its intent, making the flag a no-op. |
|
|
52
53
|
|
|
53
54
|
### Errors
|
|
54
55
|
|
|
@@ -60,8 +61,10 @@ client = RedisReadWriteLocks::Client.new(redis, default_ttl: 60_000)
|
|
|
60
61
|
## Redis key structure
|
|
61
62
|
|
|
62
63
|
```
|
|
63
|
-
rw_lock:writer:<name>
|
|
64
|
-
rw_lock:readers:<name>
|
|
64
|
+
rw_lock:writer:<name> # String, holds owner token, TTL = lock TTL
|
|
65
|
+
rw_lock:readers:<name> # Sorted set, member = token, score = expiry timestamp
|
|
66
|
+
rw_lock:pending_writers:<name> # Sorted set, member = token, score = pending-intent expiry timestamp.
|
|
67
|
+
# Populated only by writers using `prefer_writer: true`.
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
## Requirements
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "securerandom"
|
|
2
4
|
|
|
3
5
|
module RedisReadWriteLocks
|
|
4
6
|
class BaseLock
|
|
5
7
|
DEFAULT_TTL = 30_000
|
|
6
8
|
DEFAULT_RETRY_DELAY = 100
|
|
9
|
+
PENDING_WRITER_TTL = 30_000
|
|
7
10
|
|
|
8
11
|
attr_reader :name, :token
|
|
9
12
|
|
|
@@ -20,31 +23,72 @@ module RedisReadWriteLocks
|
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def acquire(retry_count: nil, retry_delay: DEFAULT_RETRY_DELAY)
|
|
26
|
+
@retry_delay = retry_count.nil? ? 0 : retry_delay
|
|
27
|
+
|
|
23
28
|
return try_acquire if retry_count.nil?
|
|
24
29
|
|
|
25
30
|
return true if try_acquire
|
|
31
|
+
|
|
26
32
|
retry_count.times do
|
|
27
33
|
sleep retry_delay / 1000.0
|
|
28
34
|
return true if try_acquire
|
|
29
35
|
end
|
|
30
36
|
raise LockTimeoutError, "Could not acquire #{lock_type} lock '#{@name}' after #{retry_count} retries"
|
|
37
|
+
ensure
|
|
38
|
+
begin
|
|
39
|
+
abandon_pending unless acquired?
|
|
40
|
+
rescue StandardError
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
31
43
|
end
|
|
32
44
|
|
|
45
|
+
WATCHDOG_REFRESH_INTERVAL = 10
|
|
46
|
+
WATCHDOG_SLEEP_INTERVAL = 0.5
|
|
47
|
+
|
|
33
48
|
def synchronize(retry_count: nil, retry_delay: DEFAULT_RETRY_DELAY, &block)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
acquire || raise(LockNotAcquiredError, "Could not acquire #{lock_type} lock '#{@name}'")
|
|
38
|
-
end
|
|
49
|
+
acquire_or_raise(retry_count: retry_count, retry_delay: retry_delay)
|
|
50
|
+
stopped = false
|
|
51
|
+
watchdog = start_watchdog(Thread.current, -> { stopped })
|
|
39
52
|
begin
|
|
40
53
|
block.call
|
|
41
54
|
ensure
|
|
55
|
+
stopped = true
|
|
56
|
+
watchdog.join
|
|
42
57
|
release
|
|
43
58
|
end
|
|
44
59
|
end
|
|
45
60
|
|
|
46
61
|
private
|
|
47
62
|
|
|
63
|
+
def acquire_or_raise(retry_count:, retry_delay:)
|
|
64
|
+
return acquire(retry_count: retry_count, retry_delay: retry_delay) if retry_count
|
|
65
|
+
|
|
66
|
+
acquire || raise(LockNotAcquiredError, "Could not acquire #{lock_type} lock '#{@name}'")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def start_watchdog(main_thread, stopped)
|
|
70
|
+
Thread.new do
|
|
71
|
+
elapsed = 0.0
|
|
72
|
+
until stopped.call
|
|
73
|
+
sleep WATCHDOG_SLEEP_INTERVAL
|
|
74
|
+
elapsed += WATCHDOG_SLEEP_INTERVAL
|
|
75
|
+
next if stopped.call
|
|
76
|
+
next unless elapsed >= WATCHDOG_REFRESH_INTERVAL
|
|
77
|
+
|
|
78
|
+
elapsed = 0.0
|
|
79
|
+
begin
|
|
80
|
+
unless refresh
|
|
81
|
+
main_thread.raise(LockRefreshError, "Could not refresh #{lock_type} lock '#{@name}'")
|
|
82
|
+
break
|
|
83
|
+
end
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
main_thread.raise(e)
|
|
86
|
+
break
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
48
92
|
def writer_key
|
|
49
93
|
"rw_lock:writer:#{@name}"
|
|
50
94
|
end
|
|
@@ -53,6 +97,14 @@ module RedisReadWriteLocks
|
|
|
53
97
|
"rw_lock:readers:#{@name}"
|
|
54
98
|
end
|
|
55
99
|
|
|
100
|
+
def pending_writers_key
|
|
101
|
+
"rw_lock:pending_writers:#{@name}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def abandon_pending
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
56
108
|
def lock_type
|
|
57
109
|
self.class.name.split("::").last.sub("Lock", "").downcase
|
|
58
110
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RedisReadWriteLocks
|
|
2
4
|
class Client
|
|
3
5
|
def initialize(redis, default_ttl: BaseLock::DEFAULT_TTL)
|
|
@@ -10,8 +12,9 @@ module RedisReadWriteLocks
|
|
|
10
12
|
block ? lock.synchronize(retry_count: retry_count, retry_delay: retry_delay, &block) : lock
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
def write_lock(name, ttl: @default_ttl, retry_count: nil, retry_delay: BaseLock::DEFAULT_RETRY_DELAY,
|
|
14
|
-
|
|
15
|
+
def write_lock(name, ttl: @default_ttl, retry_count: nil, retry_delay: BaseLock::DEFAULT_RETRY_DELAY,
|
|
16
|
+
prefer_writer: false, &block)
|
|
17
|
+
lock = WriteLock.new(redis: @redis, name: name, ttl: ttl, prefer_writer: prefer_writer)
|
|
15
18
|
block ? lock.synchronize(retry_count: retry_count, retry_delay: retry_delay, &block) : lock
|
|
16
19
|
end
|
|
17
20
|
end
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RedisReadWriteLocks
|
|
2
|
-
Error
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class LockNotAcquiredError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class LockTimeoutError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class LockRefreshError < Error
|
|
14
|
+
end
|
|
5
15
|
end
|
|
@@ -1,26 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RedisReadWriteLocks
|
|
2
4
|
module LockScripts
|
|
3
|
-
# KEYS[1] = writer_key, KEYS[2] = readers_key
|
|
4
|
-
# ARGV[1] = token, ARGV[2] = expiry (unix ts), ARGV[3] = now (unix ts)
|
|
5
|
+
# KEYS[1] = writer_key, KEYS[2] = readers_key, KEYS[3] = pending_writers_key
|
|
6
|
+
# ARGV[1] = token, ARGV[2] = expiry (unix ts), ARGV[3] = now (unix ts), ARGV[4] = ttl (ms)
|
|
5
7
|
# Returns 1 = acquired, 0 = blocked
|
|
6
|
-
ACQUIRE_READ = <<~LUA
|
|
8
|
+
ACQUIRE_READ = <<~LUA
|
|
7
9
|
local writer_key = KEYS[1]
|
|
8
10
|
local readers_key = KEYS[2]
|
|
11
|
+
local pending_writers_key = KEYS[3]
|
|
9
12
|
local token = ARGV[1]
|
|
10
13
|
local expiry = tonumber(ARGV[2])
|
|
11
14
|
local now = tonumber(ARGV[3])
|
|
15
|
+
local ttl_ms = tonumber(ARGV[4])
|
|
12
16
|
|
|
13
17
|
redis.call('ZREMRANGEBYSCORE', readers_key, '-inf', now)
|
|
18
|
+
redis.call('ZREMRANGEBYSCORE', pending_writers_key, '-inf', now)
|
|
14
19
|
|
|
15
20
|
if redis.call('EXISTS', writer_key) == 1 then
|
|
16
21
|
return 0
|
|
17
22
|
end
|
|
18
23
|
|
|
24
|
+
if redis.call('ZCARD', pending_writers_key) > 0 then
|
|
25
|
+
return 0
|
|
26
|
+
end
|
|
27
|
+
|
|
19
28
|
redis.call('ZADD', readers_key, expiry, token)
|
|
20
29
|
|
|
21
|
-
local
|
|
22
|
-
if
|
|
23
|
-
redis.call('
|
|
30
|
+
local current_pttl = redis.call('PTTL', readers_key)
|
|
31
|
+
if current_pttl == -1 or current_pttl < ttl_ms then
|
|
32
|
+
redis.call('PEXPIRE', readers_key, ttl_ms)
|
|
24
33
|
end
|
|
25
34
|
|
|
26
35
|
return 1
|
|
@@ -28,44 +37,100 @@ module RedisReadWriteLocks
|
|
|
28
37
|
|
|
29
38
|
# KEYS[1] = readers_key
|
|
30
39
|
# ARGV[1] = token
|
|
31
|
-
RELEASE_READ = <<~LUA
|
|
40
|
+
RELEASE_READ = <<~LUA
|
|
32
41
|
redis.call('ZREM', KEYS[1], ARGV[1])
|
|
33
42
|
return 1
|
|
34
43
|
LUA
|
|
35
44
|
|
|
36
|
-
# KEYS[1] = writer_key, KEYS[2] = readers_key
|
|
37
|
-
# ARGV[1] = token, ARGV[2] = ttl (
|
|
45
|
+
# KEYS[1] = writer_key, KEYS[2] = readers_key, KEYS[3] = pending_writers_key
|
|
46
|
+
# ARGV[1] = token, ARGV[2] = ttl (ms), ARGV[3] = now (unix ts),
|
|
47
|
+
# ARGV[4] = prefer (1 = writer preference on), ARGV[5] = pending expiry (unix ts),
|
|
48
|
+
# ARGV[6] = pending ttl (ms)
|
|
38
49
|
# Returns 1 = acquired, 0 = blocked
|
|
39
|
-
ACQUIRE_WRITE = <<~LUA
|
|
50
|
+
ACQUIRE_WRITE = <<~LUA
|
|
40
51
|
local writer_key = KEYS[1]
|
|
41
52
|
local readers_key = KEYS[2]
|
|
53
|
+
local pending_writers_key = KEYS[3]
|
|
42
54
|
local token = ARGV[1]
|
|
43
55
|
local ttl = tonumber(ARGV[2])
|
|
44
56
|
local now = tonumber(ARGV[3])
|
|
57
|
+
local prefer = tonumber(ARGV[4])
|
|
58
|
+
local pending_expiry = tonumber(ARGV[5])
|
|
59
|
+
local pending_ttl_ms = tonumber(ARGV[6])
|
|
45
60
|
|
|
46
61
|
redis.call('ZREMRANGEBYSCORE', readers_key, '-inf', now)
|
|
62
|
+
redis.call('ZREMRANGEBYSCORE', pending_writers_key, '-inf', now)
|
|
47
63
|
|
|
48
|
-
if redis.call('ZCARD', readers_key) > 0 then
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
if redis.call('ZCARD', readers_key) > 0 or redis.call('EXISTS', writer_key) == 1 then
|
|
65
|
+
if prefer == 1 then
|
|
66
|
+
redis.call('ZADD', pending_writers_key, pending_expiry, token)
|
|
67
|
+
|
|
68
|
+
local current_pttl = redis.call('PTTL', pending_writers_key)
|
|
69
|
+
if current_pttl == -1 or current_pttl < pending_ttl_ms then
|
|
70
|
+
redis.call('PEXPIRE', pending_writers_key, pending_ttl_ms)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
51
73
|
|
|
52
|
-
if redis.call('EXISTS', writer_key) == 1 then
|
|
53
74
|
return 0
|
|
54
75
|
end
|
|
55
76
|
|
|
56
|
-
redis.call('SET', writer_key, token, '
|
|
77
|
+
redis.call('SET', writer_key, token, 'PX', ttl)
|
|
78
|
+
redis.call('ZREM', pending_writers_key, token)
|
|
57
79
|
return 1
|
|
58
80
|
LUA
|
|
59
81
|
|
|
60
82
|
# KEYS[1] = writer_key
|
|
61
83
|
# ARGV[1] = token
|
|
62
84
|
# Returns 1 = released, 0 = not owner
|
|
63
|
-
RELEASE_WRITE = <<~LUA
|
|
85
|
+
RELEASE_WRITE = <<~LUA
|
|
64
86
|
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
65
87
|
redis.call('DEL', KEYS[1])
|
|
66
88
|
return 1
|
|
67
89
|
end
|
|
68
90
|
return 0
|
|
69
91
|
LUA
|
|
92
|
+
|
|
93
|
+
# KEYS[1] = pending_writers_key
|
|
94
|
+
# ARGV[1] = token
|
|
95
|
+
# Returns 1 = cleared
|
|
96
|
+
CLEAR_PENDING_WRITE = <<~LUA
|
|
97
|
+
redis.call('ZREM', KEYS[1], ARGV[1])
|
|
98
|
+
return 1
|
|
99
|
+
LUA
|
|
100
|
+
|
|
101
|
+
# KEYS[1] = writer_key
|
|
102
|
+
# ARGV[1] = token, ARGV[2] = ttl (milliseconds)
|
|
103
|
+
# Returns 1 = refreshed, 0 = not owner
|
|
104
|
+
REFRESH_WRITE = <<~LUA
|
|
105
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
106
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[2])
|
|
107
|
+
return 1
|
|
108
|
+
end
|
|
109
|
+
return 0
|
|
110
|
+
LUA
|
|
111
|
+
|
|
112
|
+
# KEYS[1] = readers_key
|
|
113
|
+
# ARGV[1] = token, ARGV[2] = expiry (unix ts), ARGV[3] = now (unix ts), ARGV[4] = ttl (ms)
|
|
114
|
+
# Returns 1 = refreshed, 0 = not in set
|
|
115
|
+
REFRESH_READ = <<~LUA
|
|
116
|
+
local readers_key = KEYS[1]
|
|
117
|
+
local token = ARGV[1]
|
|
118
|
+
local expiry = tonumber(ARGV[2])
|
|
119
|
+
local now = tonumber(ARGV[3])
|
|
120
|
+
local ttl_ms = tonumber(ARGV[4])
|
|
121
|
+
|
|
122
|
+
if redis.call('ZSCORE', readers_key, token) == false then
|
|
123
|
+
return 0
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
redis.call('ZADD', readers_key, expiry, token)
|
|
127
|
+
|
|
128
|
+
local current_pttl = redis.call('PTTL', readers_key)
|
|
129
|
+
if current_pttl == -1 or current_pttl < ttl_ms then
|
|
130
|
+
redis.call('PEXPIRE', readers_key, ttl_ms)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
return 1
|
|
134
|
+
LUA
|
|
70
135
|
end
|
|
71
136
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RedisReadWriteLocks
|
|
2
4
|
class ReadLock < BaseLock
|
|
3
5
|
def release
|
|
@@ -8,16 +10,24 @@ module RedisReadWriteLocks
|
|
|
8
10
|
true
|
|
9
11
|
end
|
|
10
12
|
|
|
13
|
+
def refresh
|
|
14
|
+
return false unless @acquired
|
|
15
|
+
|
|
16
|
+
now = Time.now.to_i
|
|
17
|
+
expiry = now + (@ttl / 1000.0).ceil
|
|
18
|
+
eval_script(LockScripts::REFRESH_READ, keys: [readers_key], argv: [@token, expiry, now, @ttl]) == 1
|
|
19
|
+
end
|
|
20
|
+
|
|
11
21
|
private
|
|
12
22
|
|
|
13
23
|
def try_acquire
|
|
14
24
|
now = Time.now.to_i
|
|
15
|
-
expiry = now + @ttl / 1000
|
|
25
|
+
expiry = now + (@ttl / 1000.0).ceil
|
|
16
26
|
|
|
17
27
|
result = eval_script(
|
|
18
28
|
LockScripts::ACQUIRE_READ,
|
|
19
|
-
keys: [writer_key, readers_key],
|
|
20
|
-
argv: [@token, expiry, now]
|
|
29
|
+
keys: [writer_key, readers_key, pending_writers_key],
|
|
30
|
+
argv: [@token, expiry, now, @ttl]
|
|
21
31
|
)
|
|
22
32
|
|
|
23
33
|
@acquired = result == 1
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RedisReadWriteLocks
|
|
2
4
|
class WriteLock < BaseLock
|
|
5
|
+
def initialize(redis:, name:, ttl: DEFAULT_TTL, prefer_writer: false)
|
|
6
|
+
super(redis: redis, name: name, ttl: ttl)
|
|
7
|
+
@prefer_writer = prefer_writer
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
def release
|
|
4
11
|
return false unless @acquired
|
|
5
12
|
|
|
@@ -8,16 +15,34 @@ module RedisReadWriteLocks
|
|
|
8
15
|
true
|
|
9
16
|
end
|
|
10
17
|
|
|
18
|
+
def refresh
|
|
19
|
+
return false unless @acquired
|
|
20
|
+
|
|
21
|
+
eval_script(LockScripts::REFRESH_WRITE, keys: [writer_key], argv: [@token, @ttl]) == 1
|
|
22
|
+
end
|
|
23
|
+
|
|
11
24
|
private
|
|
12
25
|
|
|
13
26
|
def try_acquire
|
|
27
|
+
now = Time.now.to_i
|
|
28
|
+
pending_ttl_ms = [PENDING_WRITER_TTL, @retry_delay.to_i * 3].max
|
|
29
|
+
pending_expiry = now + (pending_ttl_ms / 1000.0).ceil
|
|
30
|
+
|
|
14
31
|
result = eval_script(
|
|
15
32
|
LockScripts::ACQUIRE_WRITE,
|
|
16
|
-
keys: [writer_key, readers_key],
|
|
17
|
-
argv: [
|
|
33
|
+
keys: [writer_key, readers_key, pending_writers_key],
|
|
34
|
+
argv: [
|
|
35
|
+
@token, @ttl, now, @prefer_writer ? 1 : 0, pending_expiry, pending_ttl_ms
|
|
36
|
+
]
|
|
18
37
|
)
|
|
19
38
|
|
|
20
39
|
@acquired = result == 1
|
|
21
40
|
end
|
|
41
|
+
|
|
42
|
+
def abandon_pending
|
|
43
|
+
return unless @prefer_writer
|
|
44
|
+
|
|
45
|
+
eval_script(LockScripts::CLEAR_PENDING_WRITE, keys: [pending_writers_key], argv: [@token])
|
|
46
|
+
end
|
|
22
47
|
end
|
|
23
48
|
end
|
|
@@ -18,4 +18,6 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.add_development_dependency "redis-client"
|
|
19
19
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
20
20
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
21
|
+
spec.add_development_dependency "rubocop", "~> 1.0"
|
|
22
|
+
spec.add_development_dependency "rubocop-rspec", "~> 3.0"
|
|
21
23
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redis-read-write-locks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Umbrellio
|
|
@@ -65,6 +65,34 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '13.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rubocop-rspec
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
68
96
|
description: Redis-backed distributed read-write locks. Multiple concurrent readers,
|
|
69
97
|
exclusive writers.
|
|
70
98
|
email:
|