finsync_redis 3.3.5
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 +7 -0
- data/.gitignore +16 -0
- data/.travis/Gemfile +11 -0
- data/.travis.yml +89 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +373 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +410 -0
- data/Rakefile +87 -0
- data/benchmarking/logging.rb +71 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/consistency.rb +114 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +37 -0
- data/examples/sentinel/sentinel.conf +9 -0
- data/examples/sentinel/start +49 -0
- data/examples/sentinel.rb +41 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis/client.rb +590 -0
- data/lib/redis/connection/command_helper.rb +44 -0
- data/lib/redis/connection/hiredis.rb +66 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +429 -0
- data/lib/redis/connection/synchrony.rb +133 -0
- data/lib/redis/connection.rb +9 -0
- data/lib/redis/distributed.rb +873 -0
- data/lib/redis/errors.rb +40 -0
- data/lib/redis/hash_ring.rb +132 -0
- data/lib/redis/pipeline.rb +141 -0
- data/lib/redis/subscribe.rb +91 -0
- data/lib/redis/version.rb +3 -0
- data/lib/redis.rb +2788 -0
- data/redis.gemspec +44 -0
- data/test/bitpos_test.rb +69 -0
- data/test/blocking_commands_test.rb +42 -0
- data/test/client_test.rb +59 -0
- data/test/command_map_test.rb +30 -0
- data/test/commands_on_hashes_test.rb +21 -0
- data/test/commands_on_hyper_log_log_test.rb +21 -0
- data/test/commands_on_lists_test.rb +20 -0
- data/test/commands_on_sets_test.rb +77 -0
- data/test/commands_on_sorted_sets_test.rb +137 -0
- data/test/commands_on_strings_test.rb +101 -0
- data/test/commands_on_value_types_test.rb +133 -0
- data/test/connection_handling_test.rb +277 -0
- data/test/connection_test.rb +57 -0
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +46 -0
- data/test/distributed_commands_on_hashes_test.rb +10 -0
- data/test/distributed_commands_on_hyper_log_log_test.rb +33 -0
- data/test/distributed_commands_on_lists_test.rb +22 -0
- data/test/distributed_commands_on_sets_test.rb +83 -0
- data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
- data/test/distributed_commands_on_strings_test.rb +59 -0
- data/test/distributed_commands_on_value_types_test.rb +95 -0
- data/test/distributed_commands_requiring_clustering_test.rb +164 -0
- data/test/distributed_connection_handling_test.rb +23 -0
- data/test/distributed_internals_test.rb +79 -0
- data/test/distributed_key_tags_test.rb +52 -0
- data/test/distributed_persistence_control_commands_test.rb +26 -0
- data/test/distributed_publish_subscribe_test.rb +92 -0
- data/test/distributed_remote_server_control_commands_test.rb +66 -0
- data/test/distributed_scripting_test.rb +102 -0
- data/test/distributed_sorting_test.rb +20 -0
- data/test/distributed_test.rb +58 -0
- data/test/distributed_transactions_test.rb +32 -0
- data/test/encoding_test.rb +18 -0
- data/test/error_replies_test.rb +59 -0
- data/test/fork_safety_test.rb +65 -0
- data/test/helper.rb +232 -0
- data/test/helper_test.rb +24 -0
- data/test/internals_test.rb +417 -0
- data/test/lint/blocking_commands.rb +150 -0
- data/test/lint/hashes.rb +162 -0
- data/test/lint/hyper_log_log.rb +60 -0
- data/test/lint/lists.rb +143 -0
- data/test/lint/sets.rb +140 -0
- data/test/lint/sorted_sets.rb +316 -0
- data/test/lint/strings.rb +260 -0
- data/test/lint/value_types.rb +122 -0
- data/test/persistence_control_commands_test.rb +26 -0
- data/test/pipelining_commands_test.rb +242 -0
- data/test/publish_subscribe_test.rb +282 -0
- data/test/remote_server_control_commands_test.rb +118 -0
- data/test/scanning_test.rb +413 -0
- data/test/scripting_test.rb +78 -0
- data/test/sentinel_command_test.rb +80 -0
- data/test/sentinel_test.rb +255 -0
- data/test/sorting_test.rb +59 -0
- data/test/ssl_test.rb +73 -0
- data/test/support/connection/hiredis.rb +1 -0
- data/test/support/connection/ruby.rb +1 -0
- data/test/support/connection/synchrony.rb +17 -0
- data/test/support/redis_mock.rb +130 -0
- data/test/support/ssl/gen_certs.sh +31 -0
- data/test/support/ssl/trusted-ca.crt +25 -0
- data/test/support/ssl/trusted-ca.key +27 -0
- data/test/support/ssl/trusted-cert.crt +81 -0
- data/test/support/ssl/trusted-cert.key +28 -0
- data/test/support/ssl/untrusted-ca.crt +26 -0
- data/test/support/ssl/untrusted-ca.key +27 -0
- data/test/support/ssl/untrusted-cert.crt +82 -0
- data/test/support/ssl/untrusted-cert.key +28 -0
- data/test/support/wire/synchrony.rb +24 -0
- data/test/support/wire/thread.rb +5 -0
- data/test/synchrony_driver.rb +88 -0
- data/test/test.conf.erb +9 -0
- data/test/thread_safety_test.rb +62 -0
- data/test/transactions_test.rb +264 -0
- data/test/unknown_commands_test.rb +14 -0
- data/test/url_param_test.rb +138 -0
- metadata +202 -0
data/lib/redis/errors.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Redis
|
2
|
+
# Base error for all redis-rb errors.
|
3
|
+
class BaseError < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Raised by the connection when a protocol error occurs.
|
7
|
+
class ProtocolError < BaseError
|
8
|
+
def initialize(reply_type)
|
9
|
+
super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
|
10
|
+
Got '#{reply_type}' as initial reply byte.
|
11
|
+
If you're in a forking environment, such as Unicorn, you need to
|
12
|
+
connect to Redis after forking.
|
13
|
+
EOS
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised by the client when command execution returns an error reply.
|
18
|
+
class CommandError < BaseError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Base error for connection related errors.
|
22
|
+
class BaseConnectionError < BaseError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Raised when connection to a Redis server cannot be made.
|
26
|
+
class CannotConnectError < BaseConnectionError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Raised when connection to a Redis server is lost.
|
30
|
+
class ConnectionError < BaseConnectionError
|
31
|
+
end
|
32
|
+
|
33
|
+
# Raised when performing I/O times out.
|
34
|
+
class TimeoutError < BaseConnectionError
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raised when the connection was inherited by a child process.
|
38
|
+
class InheritedError < BaseConnectionError
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class HashRing
|
5
|
+
|
6
|
+
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
7
|
+
|
8
|
+
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
9
|
+
|
10
|
+
# nodes is a list of objects that have a proper to_s representation.
|
11
|
+
# replicas indicates how many virtual points should be used pr. node,
|
12
|
+
# replicas are required to improve the distribution.
|
13
|
+
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
@replicas = replicas
|
15
|
+
@ring = {}
|
16
|
+
@nodes = []
|
17
|
+
@sorted_keys = []
|
18
|
+
nodes.each do |node|
|
19
|
+
add_node(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds a `node` to the hash ring (including a number of replicas).
|
24
|
+
def add_node(node)
|
25
|
+
@nodes << node
|
26
|
+
@replicas.times do |i|
|
27
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
28
|
+
raise "Node ID collision" if @ring.has_key?(key)
|
29
|
+
@ring[key] = node
|
30
|
+
@sorted_keys << key
|
31
|
+
end
|
32
|
+
@sorted_keys.sort!
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_node(node)
|
36
|
+
@nodes.reject!{|n| n.id == node.id}
|
37
|
+
@replicas.times do |i|
|
38
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
39
|
+
@ring.delete(key)
|
40
|
+
@sorted_keys.reject! {|k| k == key}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# get the node in the hash ring for this key
|
45
|
+
def get_node(key)
|
46
|
+
get_node_pos(key)[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_node_pos(key)
|
50
|
+
return [nil,nil] if @ring.size == 0
|
51
|
+
crc = Zlib.crc32(key)
|
52
|
+
idx = HashRing.binary_search(@sorted_keys, crc)
|
53
|
+
return [@ring[@sorted_keys[idx]], idx]
|
54
|
+
end
|
55
|
+
|
56
|
+
def iter_nodes(key)
|
57
|
+
return [nil,nil] if @ring.size == 0
|
58
|
+
_, pos = get_node_pos(key)
|
59
|
+
@ring.size.times do |n|
|
60
|
+
yield @ring[@sorted_keys[(pos+n) % @ring.size]]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class << self
|
65
|
+
|
66
|
+
# gem install RubyInline to use this code
|
67
|
+
# Native extension to perform the binary search within the hashring.
|
68
|
+
# There's a pure ruby version below so this is purely optional
|
69
|
+
# for performance. In testing 20k gets and sets, the native
|
70
|
+
# binary search shaved about 12% off the runtime (9sec -> 8sec).
|
71
|
+
begin
|
72
|
+
require 'inline'
|
73
|
+
inline do |builder|
|
74
|
+
builder.c <<-EOM
|
75
|
+
int binary_search(VALUE ary, unsigned int r) {
|
76
|
+
int upper = RARRAY_LEN(ary) - 1;
|
77
|
+
int lower = 0;
|
78
|
+
int idx = 0;
|
79
|
+
|
80
|
+
while (lower <= upper) {
|
81
|
+
idx = (lower + upper) / 2;
|
82
|
+
|
83
|
+
VALUE continuumValue = RARRAY_PTR(ary)[idx];
|
84
|
+
unsigned int l = NUM2UINT(continuumValue);
|
85
|
+
if (l == r) {
|
86
|
+
return idx;
|
87
|
+
}
|
88
|
+
else if (l > r) {
|
89
|
+
upper = idx - 1;
|
90
|
+
}
|
91
|
+
else {
|
92
|
+
lower = idx + 1;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
if (upper < 0) {
|
96
|
+
upper = RARRAY_LEN(ary) - 1;
|
97
|
+
}
|
98
|
+
return upper;
|
99
|
+
}
|
100
|
+
EOM
|
101
|
+
end
|
102
|
+
rescue Exception
|
103
|
+
# Find the closest index in HashRing with value <= the given value
|
104
|
+
def binary_search(ary, value, &block)
|
105
|
+
upper = ary.size - 1
|
106
|
+
lower = 0
|
107
|
+
idx = 0
|
108
|
+
|
109
|
+
while(lower <= upper) do
|
110
|
+
idx = (lower + upper) / 2
|
111
|
+
comp = ary[idx] <=> value
|
112
|
+
|
113
|
+
if comp == 0
|
114
|
+
return idx
|
115
|
+
elsif comp > 0
|
116
|
+
upper = idx - 1
|
117
|
+
else
|
118
|
+
lower = idx + 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
if upper < 0
|
123
|
+
upper = ary.size - 1
|
124
|
+
end
|
125
|
+
return upper
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
class Redis
|
2
|
+
unless defined?(::BasicObject)
|
3
|
+
class BasicObject
|
4
|
+
instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Pipeline
|
9
|
+
attr_accessor :db
|
10
|
+
|
11
|
+
attr :futures
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@with_reconnect = true
|
15
|
+
@shutdown = false
|
16
|
+
@futures = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_reconnect?
|
20
|
+
@with_reconnect
|
21
|
+
end
|
22
|
+
|
23
|
+
def without_reconnect?
|
24
|
+
!@with_reconnect
|
25
|
+
end
|
26
|
+
|
27
|
+
def shutdown?
|
28
|
+
@shutdown
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(command, &block)
|
32
|
+
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
33
|
+
# the connection is gone.
|
34
|
+
@shutdown = true if command.first == :shutdown
|
35
|
+
future = Future.new(command, block)
|
36
|
+
@futures << future
|
37
|
+
future
|
38
|
+
end
|
39
|
+
|
40
|
+
def call_pipeline(pipeline)
|
41
|
+
@shutdown = true if pipeline.shutdown?
|
42
|
+
@futures.concat(pipeline.futures)
|
43
|
+
@db = pipeline.db
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def commands
|
48
|
+
@futures.map { |f| f._command }
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_reconnect(val=true)
|
52
|
+
@with_reconnect = false unless val
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
|
56
|
+
def without_reconnect(&blk)
|
57
|
+
with_reconnect(false, &blk)
|
58
|
+
end
|
59
|
+
|
60
|
+
def finish(replies, &blk)
|
61
|
+
if blk
|
62
|
+
futures.each_with_index.map do |future, i|
|
63
|
+
future._set(blk.call(replies[i]))
|
64
|
+
end
|
65
|
+
else
|
66
|
+
futures.each_with_index.map do |future, i|
|
67
|
+
future._set(replies[i])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Multi < self
|
73
|
+
def finish(replies)
|
74
|
+
exec = replies.last
|
75
|
+
|
76
|
+
return if exec.nil? # The transaction failed because of WATCH.
|
77
|
+
|
78
|
+
# EXEC command failed.
|
79
|
+
raise exec if exec.is_a?(CommandError)
|
80
|
+
|
81
|
+
if exec.size < futures.size
|
82
|
+
# Some command wasn't recognized by Redis.
|
83
|
+
raise replies.detect { |r| r.is_a?(CommandError) }
|
84
|
+
end
|
85
|
+
|
86
|
+
super(exec) do |reply|
|
87
|
+
# Because an EXEC returns nested replies, hiredis won't be able to
|
88
|
+
# convert an error reply to a CommandError instance itself. This is
|
89
|
+
# specific to MULTI/EXEC, so we solve this here.
|
90
|
+
reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def commands
|
95
|
+
[[:multi]] + super + [[:exec]]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class FutureNotReady < RuntimeError
|
101
|
+
def initialize
|
102
|
+
super("Value will be available once the pipeline executes.")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Future < BasicObject
|
107
|
+
FutureNotReady = ::Redis::FutureNotReady.new
|
108
|
+
|
109
|
+
def initialize(command, transformation)
|
110
|
+
@command = command
|
111
|
+
@transformation = transformation
|
112
|
+
@object = FutureNotReady
|
113
|
+
end
|
114
|
+
|
115
|
+
def inspect
|
116
|
+
"<Redis::Future #{@command.inspect}>"
|
117
|
+
end
|
118
|
+
|
119
|
+
def _set(object)
|
120
|
+
@object = @transformation ? @transformation.call(object) : object
|
121
|
+
value
|
122
|
+
end
|
123
|
+
|
124
|
+
def _command
|
125
|
+
@command
|
126
|
+
end
|
127
|
+
|
128
|
+
def value
|
129
|
+
::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
|
130
|
+
@object
|
131
|
+
end
|
132
|
+
|
133
|
+
def is_a?(other)
|
134
|
+
self.class.ancestors.include?(other)
|
135
|
+
end
|
136
|
+
|
137
|
+
def class
|
138
|
+
Future
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class Redis
|
2
|
+
class SubscribedClient
|
3
|
+
def initialize(client)
|
4
|
+
@client = client
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(command)
|
8
|
+
@client.process([command])
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(*channels, &block)
|
12
|
+
subscription("subscribe", "unsubscribe", channels, block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe_with_timeout(timeout, *channels, &block)
|
16
|
+
subscription("subscribe", "unsubscribe", channels, block, timeout)
|
17
|
+
end
|
18
|
+
|
19
|
+
def psubscribe(*channels, &block)
|
20
|
+
subscription("psubscribe", "punsubscribe", channels, block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def psubscribe_with_timeout(timeout, *channels, &block)
|
24
|
+
subscription("psubscribe", "punsubscribe", channels, block, timeout)
|
25
|
+
end
|
26
|
+
|
27
|
+
def unsubscribe(*channels)
|
28
|
+
call([:unsubscribe, *channels])
|
29
|
+
end
|
30
|
+
|
31
|
+
def punsubscribe(*channels)
|
32
|
+
call([:punsubscribe, *channels])
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def subscription(start, stop, channels, block, timeout = 0)
|
38
|
+
sub = Subscription.new(&block)
|
39
|
+
|
40
|
+
unsubscribed = false
|
41
|
+
|
42
|
+
begin
|
43
|
+
@client.call_loop([start, *channels], timeout) do |line|
|
44
|
+
type, *rest = line
|
45
|
+
sub.callbacks[type].call(*rest)
|
46
|
+
unsubscribed = type == stop && rest.last == 0
|
47
|
+
break if unsubscribed
|
48
|
+
end
|
49
|
+
ensure
|
50
|
+
# No need to unsubscribe here. The real client closes the connection
|
51
|
+
# whenever an exception is raised (see #ensure_connected).
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Subscription
|
57
|
+
attr :callbacks
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@callbacks = Hash.new do |hash, key|
|
61
|
+
hash[key] = lambda { |*_| }
|
62
|
+
end
|
63
|
+
|
64
|
+
yield(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def subscribe(&block)
|
68
|
+
@callbacks["subscribe"] = block
|
69
|
+
end
|
70
|
+
|
71
|
+
def unsubscribe(&block)
|
72
|
+
@callbacks["unsubscribe"] = block
|
73
|
+
end
|
74
|
+
|
75
|
+
def message(&block)
|
76
|
+
@callbacks["message"] = block
|
77
|
+
end
|
78
|
+
|
79
|
+
def psubscribe(&block)
|
80
|
+
@callbacks["psubscribe"] = block
|
81
|
+
end
|
82
|
+
|
83
|
+
def punsubscribe(&block)
|
84
|
+
@callbacks["punsubscribe"] = block
|
85
|
+
end
|
86
|
+
|
87
|
+
def pmessage(&block)
|
88
|
+
@callbacks["pmessage"] = block
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|