redis 4.1.0 → 4.1.2
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 +5 -5
- data/CHANGELOG.md +17 -0
- data/README.md +49 -0
- data/lib/redis.rb +127 -132
- data/lib/redis/client.rb +33 -13
- data/lib/redis/cluster/slot.rb +1 -1
- data/lib/redis/connection/ruby.rb +22 -1
- data/lib/redis/pipeline.rb +30 -5
- data/lib/redis/version.rb +1 -1
- metadata +4 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c0b16163b5714933ba20edb9595d7669c7270502586546f3e8758192039113c1
|
4
|
+
data.tar.gz: 150dacb8ae77a51536e3f2bc6813144b0176415d008adaa5d5e6c4d7f25624a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90a9fcfdefc41f5feaedfea2bde2be1ad39308dee10f0045ec227577d067d795aaaede4aec200ed6f5777167842540ff5adb6a927fe6a0a0e5ac46ea8eeaf737
|
7
|
+
data.tar.gz: 90c74c87892ef96d0d6ef2bd5934f8e3064d30064a9310fc2b1e31aba066ccb6a005d030458a35d9ad09ed403749e3356df3072927e6a749fc21caedee75f936
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 4.1.2
|
4
|
+
|
5
|
+
* Fix the client hanging forever when connecting with SSL to a non-SSL server. See #835.
|
6
|
+
* Fix several authentication problems with sentinel. See #850 and #856.
|
7
|
+
* Explicitly drop Ruby 2.2 support.
|
8
|
+
|
9
|
+
|
10
|
+
# 4.1.1
|
11
|
+
|
12
|
+
* Fix error handling in multi blocks. See #754.
|
13
|
+
* Fix geoadd to accept arrays like georadius and georadiusbymember. See #841.
|
14
|
+
* Fix georadius command failing when long == lat. See #841.
|
15
|
+
* Fix timeout error in xread block: 0. See #837.
|
16
|
+
* Fix incompatibility issue with redis-objects. See #834.
|
17
|
+
* Properly handle Errno::EADDRNOTAVAIL on connect.
|
18
|
+
* Fix password authentication to sentinel instances. See #813.
|
19
|
+
|
3
20
|
# 4.1.0
|
4
21
|
|
5
22
|
* Add Redis Cluster support. See #716.
|
data/README.md
CHANGED
@@ -95,6 +95,55 @@ but a few so that if one is down the client will try the next one. The client
|
|
95
95
|
is able to remember the last Sentinel that was able to reply correctly and will
|
96
96
|
use it for the next requests.
|
97
97
|
|
98
|
+
If you want to [authenticate](https://redis.io/topics/sentinel#configuring-sentinel-instances-with-authentication) Sentinel itself, you must specify the `password` option per instance.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
SENTINELS = [{ host: '127.0.0.1', port: 26380, password: 'mysecret' },
|
102
|
+
{ host: '127.0.0.1', port: 26381, password: 'mysecret' }]
|
103
|
+
|
104
|
+
redis = Redis.new(host: 'mymaster', sentinels: SENTINELS, role: :master)
|
105
|
+
```
|
106
|
+
|
107
|
+
## Cluster support
|
108
|
+
|
109
|
+
`redis-rb` supports [clustering](https://redis.io/topics/cluster-spec).
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# Nodes can be passed to the client as an array of connection URLs.
|
113
|
+
nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" }
|
114
|
+
redis = Redis.new(cluster: nodes)
|
115
|
+
|
116
|
+
# You can also specify the options as a Hash. The options are the same as for a single server connection.
|
117
|
+
(7000..7005).map { |port| { host: '127.0.0.1', port: port } }
|
118
|
+
```
|
119
|
+
|
120
|
+
You can also specify only a subset of the nodes, and the client will discover the missing ones using the [CLUSTER NODES](https://redis.io/commands/cluster-nodes) command.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
Redis.new(cluster: %w[redis://127.0.0.1:7000])
|
124
|
+
```
|
125
|
+
|
126
|
+
If you want [the connection to be able to read from any replica](https://redis.io/commands/readonly), you must pass the `replica: true`. Note that this connection won't be usable to write keys.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
Redis.new(cluster: nodes, replica: true)
|
130
|
+
```
|
131
|
+
|
132
|
+
The calling code is responsible for [avoiding cross slot commands](https://redis.io/topics/cluster-spec#keys-distribution-model).
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])
|
136
|
+
|
137
|
+
redis.mget('key1', 'key2')
|
138
|
+
#=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)
|
139
|
+
|
140
|
+
redis.mget('{key}1', '{key}2')
|
141
|
+
#=> [nil, nil]
|
142
|
+
```
|
143
|
+
|
144
|
+
* The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening.
|
145
|
+
* The client supports `MOVED` and `ASK` redirections transparently.
|
146
|
+
|
98
147
|
## Storing objects
|
99
148
|
|
100
149
|
Redis only stores strings as values. If you want to store an object, you
|
data/lib/redis.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "monitor"
|
2
4
|
require_relative "redis/errors"
|
3
5
|
|
@@ -104,7 +106,12 @@ class Redis
|
|
104
106
|
def commit
|
105
107
|
synchronize do |client|
|
106
108
|
begin
|
107
|
-
|
109
|
+
pipeline = Pipeline.new(client)
|
110
|
+
@queue[Thread.current.object_id].each do |command|
|
111
|
+
pipeline.call(command)
|
112
|
+
end
|
113
|
+
|
114
|
+
client.call_pipelined(pipeline)
|
108
115
|
ensure
|
109
116
|
@queue.delete(Thread.current.object_id)
|
110
117
|
end
|
@@ -2403,7 +2410,8 @@ class Redis
|
|
2403
2410
|
def pipelined
|
2404
2411
|
synchronize do |client|
|
2405
2412
|
begin
|
2406
|
-
|
2413
|
+
pipeline = Pipeline.new(@client)
|
2414
|
+
original, @client = @client, pipeline
|
2407
2415
|
yield(self)
|
2408
2416
|
original.call_pipeline(@client)
|
2409
2417
|
ensure
|
@@ -2448,7 +2456,7 @@ class Redis
|
|
2448
2456
|
client.call([:multi])
|
2449
2457
|
else
|
2450
2458
|
begin
|
2451
|
-
pipeline = Pipeline::Multi.new
|
2459
|
+
pipeline = Pipeline::Multi.new(@client)
|
2452
2460
|
original, @client = @client, pipeline
|
2453
2461
|
yield(self)
|
2454
2462
|
original.call_pipeline(pipeline)
|
@@ -2815,10 +2823,10 @@ class Redis
|
|
2815
2823
|
#
|
2816
2824
|
# @param [String] key
|
2817
2825
|
# @param [Array] member arguemnts for member or members: longitude, latitude, name
|
2818
|
-
# @return [
|
2826
|
+
# @return [Integer] number of elements added to the sorted set
|
2819
2827
|
def geoadd(key, *member)
|
2820
2828
|
synchronize do |client|
|
2821
|
-
client.call([:geoadd, key, member])
|
2829
|
+
client.call([:geoadd, key, *member])
|
2822
2830
|
end
|
2823
2831
|
end
|
2824
2832
|
|
@@ -2977,24 +2985,6 @@ class Redis
|
|
2977
2985
|
synchronize { |client| client.call(args) }
|
2978
2986
|
end
|
2979
2987
|
|
2980
|
-
# Fetches entries of the stream.
|
2981
|
-
#
|
2982
|
-
# @example Without options
|
2983
|
-
# redis.xrange('mystream')
|
2984
|
-
# @example With first entry id option
|
2985
|
-
# redis.xrange('mystream', first: '0-1')
|
2986
|
-
# @example With first and last entry id options
|
2987
|
-
# redis.xrange('mystream', first: '0-1', last: '0-3')
|
2988
|
-
# @example With count options
|
2989
|
-
# redis.xrange('mystream', count: 10)
|
2990
|
-
#
|
2991
|
-
# @param key [String] the stream key
|
2992
|
-
# @param start [String] first entry id of range, default value is `+`
|
2993
|
-
# @param end [String] last entry id of range, default value is `-`
|
2994
|
-
# @param count [Integer] the number of entries as limit
|
2995
|
-
#
|
2996
|
-
# @return [Hash{String => Hash}] the entries
|
2997
|
-
|
2998
2988
|
# Fetches entries of the stream in ascending order.
|
2999
2989
|
#
|
3000
2990
|
# @example Without options
|
@@ -3327,131 +3317,135 @@ private
|
|
3327
3317
|
# Commands returning 1 for true and 0 for false may be executed in a pipeline
|
3328
3318
|
# where the method call will return nil. Propagate the nil instead of falsely
|
3329
3319
|
# returning false.
|
3330
|
-
Boolify =
|
3331
|
-
|
3332
|
-
|
3333
|
-
|
3320
|
+
Boolify = lambda { |value|
|
3321
|
+
case value
|
3322
|
+
when 1
|
3323
|
+
true
|
3324
|
+
when 0
|
3325
|
+
false
|
3326
|
+
else
|
3327
|
+
value
|
3328
|
+
end
|
3329
|
+
}
|
3334
3330
|
|
3335
|
-
BoolifySet =
|
3336
|
-
|
3337
|
-
|
3338
|
-
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
3331
|
+
BoolifySet = lambda { |value|
|
3332
|
+
case value
|
3333
|
+
when "OK"
|
3334
|
+
true
|
3335
|
+
when nil
|
3336
|
+
false
|
3337
|
+
else
|
3338
|
+
value
|
3339
|
+
end
|
3340
|
+
}
|
3343
3341
|
|
3344
|
-
Hashify =
|
3345
|
-
|
3346
|
-
|
3347
|
-
|
3348
|
-
|
3349
|
-
|
3350
|
-
|
3351
|
-
|
3342
|
+
Hashify = lambda { |value|
|
3343
|
+
if value.respond_to?(:each_slice)
|
3344
|
+
value.each_slice(2).to_h
|
3345
|
+
else
|
3346
|
+
value
|
3347
|
+
end
|
3348
|
+
}
|
3349
|
+
|
3350
|
+
Floatify = lambda { |value|
|
3351
|
+
case value
|
3352
|
+
when "inf"
|
3353
|
+
Float::INFINITY
|
3354
|
+
when "-inf"
|
3355
|
+
-Float::INFINITY
|
3356
|
+
when String
|
3357
|
+
Float(value)
|
3358
|
+
else
|
3359
|
+
value
|
3360
|
+
end
|
3361
|
+
}
|
3352
3362
|
|
3353
|
-
|
3354
|
-
|
3355
|
-
if str
|
3356
|
-
if (inf = str.match(/^(-)?inf/i))
|
3357
|
-
(inf[1] ? -1.0 : 1.0) / 0.0
|
3358
|
-
else
|
3359
|
-
Float(str)
|
3360
|
-
end
|
3361
|
-
end
|
3362
|
-
}
|
3363
|
+
FloatifyPairs = lambda { |value|
|
3364
|
+
return value unless value.respond_to?(:each_slice)
|
3363
3365
|
|
3364
|
-
|
3365
|
-
|
3366
|
-
|
3367
|
-
|
3368
|
-
end
|
3369
|
-
}
|
3366
|
+
value.each_slice(2).map do |member, score|
|
3367
|
+
[member, Floatify.call(score)]
|
3368
|
+
end
|
3369
|
+
}
|
3370
3370
|
|
3371
|
-
HashifyInfo =
|
3372
|
-
|
3373
|
-
|
3374
|
-
|
3375
|
-
|
3376
|
-
|
3371
|
+
HashifyInfo = lambda { |reply|
|
3372
|
+
lines = reply.split("\r\n").grep_v(/^(#|$)/)
|
3373
|
+
lines.map! { |line| line.split(':', 2) }
|
3374
|
+
lines.compact!
|
3375
|
+
lines.to_h
|
3376
|
+
}
|
3377
3377
|
|
3378
|
-
HashifyStreams =
|
3379
|
-
|
3380
|
-
|
3381
|
-
|
3382
|
-
|
3383
|
-
|
3384
|
-
|
3378
|
+
HashifyStreams = lambda { |reply|
|
3379
|
+
case reply
|
3380
|
+
when nil
|
3381
|
+
{}
|
3382
|
+
else
|
3383
|
+
reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
|
3384
|
+
end
|
3385
|
+
}
|
3385
3386
|
|
3386
|
-
HashifyStreamEntries =
|
3387
|
-
|
3388
|
-
|
3389
|
-
|
3390
|
-
|
3387
|
+
HashifyStreamEntries = lambda { |reply|
|
3388
|
+
reply.map do |entry_id, values|
|
3389
|
+
[entry_id, values.each_slice(2).to_h]
|
3390
|
+
end
|
3391
|
+
}
|
3392
|
+
|
3393
|
+
HashifyStreamPendings = lambda { |reply|
|
3394
|
+
{
|
3395
|
+
'size' => reply[0],
|
3396
|
+
'min_entry_id' => reply[1],
|
3397
|
+
'max_entry_id' => reply[2],
|
3398
|
+
'consumers' => reply[3].nil? ? {} : reply[3].to_h
|
3391
3399
|
}
|
3400
|
+
}
|
3392
3401
|
|
3393
|
-
|
3394
|
-
|
3402
|
+
HashifyStreamPendingDetails = lambda { |reply|
|
3403
|
+
reply.map do |arr|
|
3395
3404
|
{
|
3396
|
-
'
|
3397
|
-
'
|
3398
|
-
'
|
3399
|
-
'
|
3405
|
+
'entry_id' => arr[0],
|
3406
|
+
'consumer' => arr[1],
|
3407
|
+
'elapsed' => arr[2],
|
3408
|
+
'count' => arr[3]
|
3400
3409
|
}
|
3401
|
-
|
3410
|
+
end
|
3411
|
+
}
|
3402
3412
|
|
3403
|
-
|
3404
|
-
|
3405
|
-
|
3406
|
-
|
3407
|
-
|
3408
|
-
|
3409
|
-
|
3410
|
-
|
3411
|
-
|
3412
|
-
|
3413
|
+
HashifyClusterNodeInfo = lambda { |str|
|
3414
|
+
arr = str.split(' ')
|
3415
|
+
{
|
3416
|
+
'node_id' => arr[0],
|
3417
|
+
'ip_port' => arr[1],
|
3418
|
+
'flags' => arr[2].split(','),
|
3419
|
+
'master_node_id' => arr[3],
|
3420
|
+
'ping_sent' => arr[4],
|
3421
|
+
'pong_recv' => arr[5],
|
3422
|
+
'config_epoch' => arr[6],
|
3423
|
+
'link_state' => arr[7],
|
3424
|
+
'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
|
3413
3425
|
}
|
3426
|
+
}
|
3414
3427
|
|
3415
|
-
|
3416
|
-
|
3417
|
-
|
3428
|
+
HashifyClusterSlots = lambda { |reply|
|
3429
|
+
reply.map do |arr|
|
3430
|
+
first_slot, last_slot = arr[0..1]
|
3431
|
+
master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
|
3432
|
+
replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
|
3418
3433
|
{
|
3419
|
-
'
|
3420
|
-
'
|
3421
|
-
'
|
3422
|
-
'
|
3423
|
-
'ping_sent' => arr[4],
|
3424
|
-
'pong_recv' => arr[5],
|
3425
|
-
'config_epoch' => arr[6],
|
3426
|
-
'link_state' => arr[7],
|
3427
|
-
'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
|
3434
|
+
'start_slot' => first_slot,
|
3435
|
+
'end_slot' => last_slot,
|
3436
|
+
'master' => master,
|
3437
|
+
'replicas' => replicas
|
3428
3438
|
}
|
3429
|
-
|
3430
|
-
|
3431
|
-
HashifyClusterSlots =
|
3432
|
-
lambda { |reply|
|
3433
|
-
reply.map do |arr|
|
3434
|
-
first_slot, last_slot = arr[0..1]
|
3435
|
-
master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
|
3436
|
-
replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
|
3437
|
-
{
|
3438
|
-
'start_slot' => first_slot,
|
3439
|
-
'end_slot' => last_slot,
|
3440
|
-
'master' => master,
|
3441
|
-
'replicas' => replicas
|
3442
|
-
}
|
3443
|
-
end
|
3444
|
-
}
|
3439
|
+
end
|
3440
|
+
}
|
3445
3441
|
|
3446
|
-
HashifyClusterNodes =
|
3447
|
-
|
3448
|
-
|
3449
|
-
}
|
3442
|
+
HashifyClusterNodes = lambda { |reply|
|
3443
|
+
reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
|
3444
|
+
}
|
3450
3445
|
|
3451
|
-
HashifyClusterSlaves =
|
3452
|
-
|
3453
|
-
|
3454
|
-
}
|
3446
|
+
HashifyClusterSlaves = lambda { |reply|
|
3447
|
+
reply.map { |str| HashifyClusterNodeInfo.call(str) }
|
3448
|
+
}
|
3455
3449
|
|
3456
3450
|
Noop = ->(reply) { reply }
|
3457
3451
|
|
@@ -3459,8 +3453,7 @@ private
|
|
3459
3453
|
args.push sort if sort
|
3460
3454
|
args.push 'count', count if count
|
3461
3455
|
args.push options if options
|
3462
|
-
|
3463
|
-
args.uniq
|
3456
|
+
args
|
3464
3457
|
end
|
3465
3458
|
|
3466
3459
|
def _subscription(method, timeout, channels, block)
|
@@ -3488,6 +3481,8 @@ private
|
|
3488
3481
|
synchronize do |client|
|
3489
3482
|
if blocking_timeout_msec.nil?
|
3490
3483
|
client.call(args, &HashifyStreams)
|
3484
|
+
elsif blocking_timeout_msec.to_f.zero?
|
3485
|
+
client.call_without_timeout(args, &HashifyStreams)
|
3491
3486
|
else
|
3492
3487
|
timeout = client.timeout.to_f + blocking_timeout_msec.to_f / 1000.0
|
3493
3488
|
client.call_with_timeout(args, timeout, &HashifyStreams)
|
data/lib/redis/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "errors"
|
2
4
|
require "socket"
|
3
5
|
require "cgi"
|
@@ -155,12 +157,11 @@ class Redis
|
|
155
157
|
end
|
156
158
|
|
157
159
|
def call_pipeline(pipeline)
|
158
|
-
|
159
|
-
return [] if commands.empty?
|
160
|
+
return [] if pipeline.futures.empty?
|
160
161
|
|
161
162
|
with_reconnect pipeline.with_reconnect? do
|
162
163
|
begin
|
163
|
-
pipeline.finish(call_pipelined(
|
164
|
+
pipeline.finish(call_pipelined(pipeline)).tap do
|
164
165
|
self.db = pipeline.db if pipeline.db
|
165
166
|
end
|
166
167
|
rescue ConnectionError => e
|
@@ -173,8 +174,8 @@ class Redis
|
|
173
174
|
end
|
174
175
|
end
|
175
176
|
|
176
|
-
def call_pipelined(
|
177
|
-
return [] if
|
177
|
+
def call_pipelined(pipeline)
|
178
|
+
return [] if pipeline.futures.empty?
|
178
179
|
|
179
180
|
# The method #ensure_connected (called from #process) reconnects once on
|
180
181
|
# I/O errors. To make an effort in making sure that commands are not
|
@@ -184,6 +185,8 @@ class Redis
|
|
184
185
|
# already successfully executed commands. To circumvent this, don't retry
|
185
186
|
# after the first reply has been read successfully.
|
186
187
|
|
188
|
+
commands = pipeline.commands
|
189
|
+
|
187
190
|
result = Array.new(commands.size)
|
188
191
|
reconnect = @reconnect
|
189
192
|
|
@@ -191,8 +194,12 @@ class Redis
|
|
191
194
|
exception = nil
|
192
195
|
|
193
196
|
process(commands) do
|
194
|
-
|
195
|
-
reply =
|
197
|
+
pipeline.timeouts.each_with_index do |timeout, i|
|
198
|
+
reply = if timeout
|
199
|
+
with_socket_timeout(timeout) { read }
|
200
|
+
else
|
201
|
+
read
|
202
|
+
end
|
196
203
|
result[i] = reply
|
197
204
|
@reconnect = false
|
198
205
|
exception = reply if exception.nil? && reply.is_a?(CommandError)
|
@@ -343,12 +350,14 @@ class Redis
|
|
343
350
|
@pending_reads = 0
|
344
351
|
rescue TimeoutError,
|
345
352
|
SocketError,
|
353
|
+
Errno::EADDRNOTAVAIL,
|
346
354
|
Errno::ECONNREFUSED,
|
347
355
|
Errno::EHOSTDOWN,
|
348
356
|
Errno::EHOSTUNREACH,
|
349
357
|
Errno::ENETUNREACH,
|
350
358
|
Errno::ENOENT,
|
351
|
-
Errno::ETIMEDOUT
|
359
|
+
Errno::ETIMEDOUT,
|
360
|
+
Errno::EINVAL
|
352
361
|
|
353
362
|
raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
|
354
363
|
end
|
@@ -407,7 +416,8 @@ class Redis
|
|
407
416
|
options[key] = options[key.to_s] if options.has_key?(key.to_s)
|
408
417
|
end
|
409
418
|
|
410
|
-
url = options[:url]
|
419
|
+
url = options[:url]
|
420
|
+
url = defaults[:url] if url == nil
|
411
421
|
|
412
422
|
# Override defaults from URL if given
|
413
423
|
if url
|
@@ -525,7 +535,6 @@ class Redis
|
|
525
535
|
def initialize(options)
|
526
536
|
super(options)
|
527
537
|
|
528
|
-
@options[:password] = DEFAULTS.fetch(:password)
|
529
538
|
@options[:db] = DEFAULTS.fetch(:db)
|
530
539
|
|
531
540
|
@sentinels = @options.delete(:sentinels).dup
|
@@ -568,6 +577,7 @@ class Redis
|
|
568
577
|
client = Client.new(@options.merge({
|
569
578
|
:host => sentinel[:host],
|
570
579
|
:port => sentinel[:port],
|
580
|
+
password: sentinel[:password],
|
571
581
|
:reconnect_attempts => 0,
|
572
582
|
}))
|
573
583
|
|
@@ -599,9 +609,19 @@ class Redis
|
|
599
609
|
def resolve_slave
|
600
610
|
sentinel_detect do |client|
|
601
611
|
if reply = client.call(["sentinel", "slaves", @master])
|
602
|
-
|
603
|
-
|
604
|
-
{
|
612
|
+
slaves = reply.map { |s| s.each_slice(2).to_h }
|
613
|
+
slaves.each { |s| s['flags'] = s.fetch('flags').split(',') }
|
614
|
+
slaves.reject! { |s| s.fetch('flags').include?('s_down') }
|
615
|
+
|
616
|
+
if slaves.empty?
|
617
|
+
raise CannotConnectError, 'No slaves available.'
|
618
|
+
else
|
619
|
+
slave = slaves.sample
|
620
|
+
{
|
621
|
+
host: slave.fetch('ip'),
|
622
|
+
port: slave.fetch('port'),
|
623
|
+
}
|
624
|
+
end
|
605
625
|
end
|
606
626
|
end
|
607
627
|
end
|
data/lib/redis/cluster/slot.rb
CHANGED
@@ -266,7 +266,28 @@ class Redis
|
|
266
266
|
|
267
267
|
ssl_sock = new(tcp_sock, ctx)
|
268
268
|
ssl_sock.hostname = host
|
269
|
-
|
269
|
+
|
270
|
+
begin
|
271
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
272
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
273
|
+
# indicating the connection is in progress.
|
274
|
+
# Unlike waiting for a tcp socket to connect, you can't time out ssl socket
|
275
|
+
# connections during the connect phase properly, because IO.select only partially works.
|
276
|
+
# Instead, you have to retry.
|
277
|
+
ssl_sock.connect_nonblock
|
278
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
279
|
+
if IO.select([ssl_sock], nil, nil, timeout)
|
280
|
+
retry
|
281
|
+
else
|
282
|
+
raise TimeoutError
|
283
|
+
end
|
284
|
+
rescue IO::WaitWritable
|
285
|
+
if IO.select(nil, [ssl_sock], nil, timeout)
|
286
|
+
retry
|
287
|
+
else
|
288
|
+
raise TimeoutError
|
289
|
+
end
|
290
|
+
end
|
270
291
|
|
271
292
|
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
272
293
|
ssl_sock.post_connection_check(host)
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
class Redis
|
2
2
|
class Pipeline
|
3
3
|
attr_accessor :db
|
4
|
+
attr_reader :client
|
4
5
|
|
5
6
|
attr :futures
|
6
7
|
|
7
|
-
def initialize
|
8
|
+
def initialize(client)
|
9
|
+
@client = client.is_a?(Pipeline) ? client.client : client
|
8
10
|
@with_reconnect = true
|
9
11
|
@shutdown = false
|
10
12
|
@futures = []
|
11
13
|
end
|
12
14
|
|
15
|
+
def timeout
|
16
|
+
client.timeout
|
17
|
+
end
|
18
|
+
|
13
19
|
def with_reconnect?
|
14
20
|
@with_reconnect
|
15
21
|
end
|
@@ -26,15 +32,19 @@ class Redis
|
|
26
32
|
@futures.empty?
|
27
33
|
end
|
28
34
|
|
29
|
-
def call(command, &block)
|
35
|
+
def call(command, timeout: nil, &block)
|
30
36
|
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
31
37
|
# the connection is gone.
|
32
38
|
@shutdown = true if command.first == :shutdown
|
33
|
-
future = Future.new(command, block)
|
39
|
+
future = Future.new(command, block, timeout)
|
34
40
|
@futures << future
|
35
41
|
future
|
36
42
|
end
|
37
43
|
|
44
|
+
def call_with_timeout(command, timeout, &block)
|
45
|
+
call(command, timeout: timeout, &block)
|
46
|
+
end
|
47
|
+
|
38
48
|
def call_pipeline(pipeline)
|
39
49
|
@shutdown = true if pipeline.shutdown?
|
40
50
|
@futures.concat(pipeline.futures)
|
@@ -43,7 +53,11 @@ class Redis
|
|
43
53
|
end
|
44
54
|
|
45
55
|
def commands
|
46
|
-
@futures.map
|
56
|
+
@futures.map(&:_command)
|
57
|
+
end
|
58
|
+
|
59
|
+
def timeouts
|
60
|
+
@futures.map(&:timeout)
|
47
61
|
end
|
48
62
|
|
49
63
|
def with_reconnect(val=true)
|
@@ -89,6 +103,14 @@ class Redis
|
|
89
103
|
end
|
90
104
|
end
|
91
105
|
|
106
|
+
def timeouts
|
107
|
+
if empty?
|
108
|
+
[]
|
109
|
+
else
|
110
|
+
[nil, *super, nil]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
92
114
|
def commands
|
93
115
|
if empty?
|
94
116
|
[]
|
@@ -108,9 +130,12 @@ class Redis
|
|
108
130
|
class Future < BasicObject
|
109
131
|
FutureNotReady = ::Redis::FutureNotReady.new
|
110
132
|
|
111
|
-
|
133
|
+
attr_reader :timeout
|
134
|
+
|
135
|
+
def initialize(command, transformation, timeout)
|
112
136
|
@command = command
|
113
137
|
@transformation = transformation
|
138
|
+
@timeout = timeout
|
114
139
|
@object = FutureNotReady
|
115
140
|
end
|
116
141
|
|
data/lib/redis/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1.
|
4
|
+
version: 4.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -16,22 +16,8 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date:
|
19
|
+
date: 2019-05-30 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
22
|
-
name: test-unit
|
23
|
-
requirement: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - ">="
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: 3.1.5
|
28
|
-
type: :development
|
29
|
-
prerelease: false
|
30
|
-
version_requirements: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: 3.1.5
|
35
21
|
- !ruby/object:Gem::Dependency
|
36
22
|
name: mocha
|
37
23
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,15 +108,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
108
|
requirements:
|
123
109
|
- - ">="
|
124
110
|
- !ruby/object:Gem::Version
|
125
|
-
version: 2.
|
111
|
+
version: 2.3.0
|
126
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
113
|
requirements:
|
128
114
|
- - ">="
|
129
115
|
- !ruby/object:Gem::Version
|
130
116
|
version: '0'
|
131
117
|
requirements: []
|
132
|
-
|
133
|
-
rubygems_version: 2.5.1
|
118
|
+
rubygems_version: 3.0.3
|
134
119
|
signing_key:
|
135
120
|
specification_version: 4
|
136
121
|
summary: A Ruby client library for Redis
|