discourse-redis 3.2.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.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +59 -0
  4. data/.travis/Gemfile +11 -0
  5. data/.yardopts +3 -0
  6. data/CHANGELOG.md +349 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +20 -0
  9. data/README.md +328 -0
  10. data/Rakefile +87 -0
  11. data/benchmarking/logging.rb +71 -0
  12. data/benchmarking/pipeline.rb +51 -0
  13. data/benchmarking/speed.rb +21 -0
  14. data/benchmarking/suite.rb +24 -0
  15. data/benchmarking/worker.rb +71 -0
  16. data/examples/basic.rb +15 -0
  17. data/examples/consistency.rb +114 -0
  18. data/examples/dist_redis.rb +43 -0
  19. data/examples/incr-decr.rb +17 -0
  20. data/examples/list.rb +26 -0
  21. data/examples/pubsub.rb +37 -0
  22. data/examples/sentinel.rb +41 -0
  23. data/examples/sentinel/start +49 -0
  24. data/examples/sets.rb +36 -0
  25. data/examples/unicorn/config.ru +3 -0
  26. data/examples/unicorn/unicorn.rb +20 -0
  27. data/lib/redis.rb +2731 -0
  28. data/lib/redis/client.rb +575 -0
  29. data/lib/redis/connection.rb +9 -0
  30. data/lib/redis/connection/command_helper.rb +44 -0
  31. data/lib/redis/connection/hiredis.rb +64 -0
  32. data/lib/redis/connection/registry.rb +12 -0
  33. data/lib/redis/connection/ruby.rb +322 -0
  34. data/lib/redis/connection/synchrony.rb +124 -0
  35. data/lib/redis/distributed.rb +873 -0
  36. data/lib/redis/errors.rb +40 -0
  37. data/lib/redis/hash_ring.rb +132 -0
  38. data/lib/redis/pipeline.rb +141 -0
  39. data/lib/redis/subscribe.rb +83 -0
  40. data/lib/redis/version.rb +3 -0
  41. data/redis.gemspec +34 -0
  42. data/test/bitpos_test.rb +69 -0
  43. data/test/blocking_commands_test.rb +42 -0
  44. data/test/command_map_test.rb +30 -0
  45. data/test/commands_on_hashes_test.rb +21 -0
  46. data/test/commands_on_hyper_log_log_test.rb +21 -0
  47. data/test/commands_on_lists_test.rb +20 -0
  48. data/test/commands_on_sets_test.rb +77 -0
  49. data/test/commands_on_sorted_sets_test.rb +137 -0
  50. data/test/commands_on_strings_test.rb +101 -0
  51. data/test/commands_on_value_types_test.rb +133 -0
  52. data/test/connection_handling_test.rb +250 -0
  53. data/test/distributed_blocking_commands_test.rb +46 -0
  54. data/test/distributed_commands_on_hashes_test.rb +10 -0
  55. data/test/distributed_commands_on_hyper_log_log_test.rb +33 -0
  56. data/test/distributed_commands_on_lists_test.rb +22 -0
  57. data/test/distributed_commands_on_sets_test.rb +83 -0
  58. data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
  59. data/test/distributed_commands_on_strings_test.rb +59 -0
  60. data/test/distributed_commands_on_value_types_test.rb +95 -0
  61. data/test/distributed_commands_requiring_clustering_test.rb +164 -0
  62. data/test/distributed_connection_handling_test.rb +23 -0
  63. data/test/distributed_internals_test.rb +79 -0
  64. data/test/distributed_key_tags_test.rb +52 -0
  65. data/test/distributed_persistence_control_commands_test.rb +26 -0
  66. data/test/distributed_publish_subscribe_test.rb +92 -0
  67. data/test/distributed_remote_server_control_commands_test.rb +66 -0
  68. data/test/distributed_scripting_test.rb +102 -0
  69. data/test/distributed_sorting_test.rb +20 -0
  70. data/test/distributed_test.rb +58 -0
  71. data/test/distributed_transactions_test.rb +32 -0
  72. data/test/encoding_test.rb +18 -0
  73. data/test/error_replies_test.rb +59 -0
  74. data/test/fork_safety_test.rb +65 -0
  75. data/test/helper.rb +232 -0
  76. data/test/helper_test.rb +24 -0
  77. data/test/internals_test.rb +437 -0
  78. data/test/lint/blocking_commands.rb +150 -0
  79. data/test/lint/hashes.rb +162 -0
  80. data/test/lint/hyper_log_log.rb +60 -0
  81. data/test/lint/lists.rb +143 -0
  82. data/test/lint/sets.rb +125 -0
  83. data/test/lint/sorted_sets.rb +316 -0
  84. data/test/lint/strings.rb +260 -0
  85. data/test/lint/value_types.rb +122 -0
  86. data/test/persistence_control_commands_test.rb +26 -0
  87. data/test/pipelining_commands_test.rb +242 -0
  88. data/test/publish_subscribe_test.rb +254 -0
  89. data/test/remote_server_control_commands_test.rb +118 -0
  90. data/test/scanning_test.rb +413 -0
  91. data/test/scripting_test.rb +78 -0
  92. data/test/sentinel_command_test.rb +80 -0
  93. data/test/sentinel_test.rb +255 -0
  94. data/test/sorting_test.rb +59 -0
  95. data/test/support/connection/hiredis.rb +1 -0
  96. data/test/support/connection/ruby.rb +1 -0
  97. data/test/support/connection/synchrony.rb +17 -0
  98. data/test/support/redis_mock.rb +119 -0
  99. data/test/support/wire/synchrony.rb +24 -0
  100. data/test/support/wire/thread.rb +5 -0
  101. data/test/synchrony_driver.rb +88 -0
  102. data/test/test.conf.erb +9 -0
  103. data/test/thread_safety_test.rb +32 -0
  104. data/test/transactions_test.rb +264 -0
  105. data/test/unknown_commands_test.rb +14 -0
  106. data/test/url_param_test.rb +138 -0
  107. metadata +182 -0
@@ -0,0 +1 @@
1
+ require "support/wire/thread"
@@ -0,0 +1 @@
1
+ require "support/wire/thread"
@@ -0,0 +1,17 @@
1
+ require "support/wire/synchrony"
2
+
3
+ module Helper
4
+ def around
5
+ rv = nil
6
+
7
+ EM.synchrony do
8
+ begin
9
+ rv = yield
10
+ ensure
11
+ EM.stop
12
+ end
13
+ end
14
+
15
+ rv
16
+ end
17
+ end
@@ -0,0 +1,119 @@
1
+ require "socket"
2
+
3
+ module RedisMock
4
+ class Server
5
+ def initialize(options = {}, &block)
6
+ @server = TCPServer.new(options[:host] || "127.0.0.1", 0)
7
+ @server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
8
+ end
9
+
10
+ def port
11
+ @server.addr[1]
12
+ end
13
+
14
+ def start(&block)
15
+ @thread = Thread.new { run(&block) }
16
+ end
17
+
18
+ def shutdown
19
+ @thread.kill
20
+ end
21
+
22
+ def run
23
+ begin
24
+ loop do
25
+ session = @server.accept
26
+
27
+ begin
28
+ return if yield(session) == :exit
29
+ ensure
30
+ session.close
31
+ end
32
+ end
33
+ rescue => ex
34
+ $stderr.puts "Error running mock server: #{ex.message}"
35
+ $stderr.puts ex.backtrace
36
+ retry
37
+ ensure
38
+ @server.close
39
+ end
40
+ end
41
+ end
42
+
43
+ # Starts a mock Redis server in a thread.
44
+ #
45
+ # The server will use the lambda handler passed as argument to handle
46
+ # connections. For example:
47
+ #
48
+ # handler = lambda { |session| session.close }
49
+ # RedisMock.start_with_handler(handler) do
50
+ # # Every connection will be closed immediately
51
+ # end
52
+ #
53
+ def self.start_with_handler(blk, options = {})
54
+ server = Server.new(options)
55
+ port = server.port
56
+
57
+ begin
58
+ server.start(&blk)
59
+ yield(port)
60
+ ensure
61
+ server.shutdown
62
+ end
63
+ end
64
+
65
+ # Starts a mock Redis server in a thread.
66
+ #
67
+ # The server will reply with a `+OK` to all commands, but you can
68
+ # customize it by providing a hash. For example:
69
+ #
70
+ # RedisMock.start(:ping => lambda { "+PONG" }) do |port|
71
+ # assert_equal "PONG", Redis.new(:port => port).ping
72
+ # end
73
+ #
74
+ def self.start(commands, options = {}, &blk)
75
+ handler = lambda do |session|
76
+ while line = session.gets
77
+ argv = Array.new(line[1..-3].to_i) do
78
+ bytes = session.gets[1..-3].to_i
79
+ arg = session.read(bytes)
80
+ session.read(2) # Discard \r\n
81
+ arg
82
+ end
83
+
84
+ command = argv.shift
85
+ blk = commands[command.to_sym]
86
+ blk ||= lambda { |*_| "+OK" }
87
+
88
+ response = blk.call(*argv)
89
+
90
+ # Convert a nil response to :close
91
+ response ||= :close
92
+
93
+ if response == :exit
94
+ break :exit
95
+ elsif response == :close
96
+ break :close
97
+ elsif response.is_a?(Array)
98
+ session.write("*%d\r\n" % response.size)
99
+
100
+ response.each do |resp|
101
+ if resp.is_a?(Array)
102
+ session.write("*%d\r\n" % resp.size)
103
+ resp.each do |r|
104
+ session.write("$%d\r\n%s\r\n" % [r.length, r])
105
+ end
106
+ else
107
+ session.write("$%d\r\n%s\r\n" % [resp.length, resp])
108
+ end
109
+ end
110
+ else
111
+ session.write(response)
112
+ session.write("\r\n") unless response.end_with?("\r\n")
113
+ end
114
+ end
115
+ end
116
+
117
+ start_with_handler(handler, options, &blk)
118
+ end
119
+ end
@@ -0,0 +1,24 @@
1
+ class Wire < Fiber
2
+ # We cannot run this fiber explicitly because EM schedules it. Resuming the
3
+ # current fiber on the next tick to let the reactor do work.
4
+ def self.pass
5
+ f = Fiber.current
6
+ EM.next_tick { f.resume }
7
+ Fiber.yield
8
+ end
9
+
10
+ def self.sleep(sec)
11
+ EM::Synchrony.sleep(sec)
12
+ end
13
+
14
+ def initialize(&blk)
15
+ super
16
+
17
+ # Schedule run in next tick
18
+ EM.next_tick { resume }
19
+ end
20
+
21
+ def join
22
+ self.class.pass while alive?
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ class Wire < Thread
2
+ def self.sleep(sec)
3
+ Kernel.sleep(sec)
4
+ end
5
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'em-synchrony'
4
+ require 'em-synchrony/connection_pool'
5
+
6
+ require 'redis'
7
+ require 'redis/connection/synchrony'
8
+
9
+
10
+ require File.expand_path("./helper", File.dirname(__FILE__))
11
+
12
+ PORT = 6381
13
+ OPTIONS = {:port => PORT, :db => 15}
14
+
15
+ #
16
+ # if running under Eventmachine + Synchrony (Ruby 1.9+), then
17
+ # we can simulate the blocking API while performing the network
18
+ # IO via the EM reactor.
19
+ #
20
+
21
+ EM.synchrony do
22
+ r = Redis.new OPTIONS
23
+ r.flushdb
24
+
25
+ r.rpush "foo", "s1"
26
+ r.rpush "foo", "s2"
27
+
28
+ assert_equal 2, r.llen("foo")
29
+ assert_equal "s2", r.rpop("foo")
30
+
31
+ r.set("foo", "bar")
32
+
33
+ assert_equal "bar", r.getset("foo", "baz")
34
+ assert_equal "baz", r.get("foo")
35
+
36
+ r.set("foo", "a")
37
+
38
+ assert_equal 1, r.getbit("foo", 1)
39
+ assert_equal 1, r.getbit("foo", 2)
40
+ assert_equal 0, r.getbit("foo", 3)
41
+ assert_equal 0, r.getbit("foo", 4)
42
+ assert_equal 0, r.getbit("foo", 5)
43
+ assert_equal 0, r.getbit("foo", 6)
44
+ assert_equal 1, r.getbit("foo", 7)
45
+
46
+ r.flushdb
47
+
48
+ # command pipelining
49
+ r.pipelined do
50
+ r.lpush "foo", "s1"
51
+ r.lpush "foo", "s2"
52
+ end
53
+
54
+ assert_equal 2, r.llen("foo")
55
+ assert_equal "s2", r.lpop("foo")
56
+ assert_equal "s1", r.lpop("foo")
57
+
58
+ assert_equal "OK", r.client.call(:quit)
59
+ assert_equal "PONG", r.ping
60
+
61
+
62
+ rpool = EM::Synchrony::ConnectionPool.new(size: 5) { Redis.new OPTIONS }
63
+
64
+ result = rpool.watch 'foo' do |rd|
65
+ assert_kind_of Redis, rd
66
+
67
+ rd.set "foo", "s1"
68
+ rd.multi do |multi|
69
+ multi.set "foo", "s2"
70
+ end
71
+ end
72
+
73
+ assert_equal nil, result
74
+ assert_equal "s1", rpool.get("foo")
75
+
76
+ result = rpool.watch "foo" do |rd|
77
+ assert_kind_of Redis, rd
78
+
79
+ rd.multi do |multi|
80
+ multi.set "foo", "s3"
81
+ end
82
+ end
83
+
84
+ assert_equal ["OK"], result
85
+ assert_equal "s3", rpool.get("foo")
86
+
87
+ EM.stop
88
+ end
@@ -0,0 +1,9 @@
1
+ dir <%= REDIS_DIR %>
2
+ pidfile <%= REDIS_PID %>
3
+ port 6381
4
+ unixsocket <%= REDIS_SOCKET %>
5
+ timeout 300
6
+ loglevel debug
7
+ logfile <%= REDIS_LOG %>
8
+ databases 16
9
+ daemonize yes
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestThreadSafety < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ driver(:ruby, :hiredis) do
10
+ def test_thread_safety
11
+ redis = Redis.new(OPTIONS)
12
+ redis.set "foo", 1
13
+ redis.set "bar", 2
14
+
15
+ sample = 100
16
+
17
+ t1 = Thread.new do
18
+ $foos = Array.new(sample) { redis.get "foo" }
19
+ end
20
+
21
+ t2 = Thread.new do
22
+ $bars = Array.new(sample) { redis.get "bar" }
23
+ end
24
+
25
+ t1.join
26
+ t2.join
27
+
28
+ assert_equal ["1"], $foos.uniq
29
+ assert_equal ["2"], $bars.uniq
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,264 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestTransactions < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ def test_multi_discard
10
+ r.multi
11
+
12
+ assert_equal "QUEUED", r.set("foo", "1")
13
+ assert_equal "QUEUED", r.get("foo")
14
+
15
+ r.discard
16
+
17
+ assert_equal nil, r.get("foo")
18
+ end
19
+
20
+ def test_multi_exec_with_a_block
21
+ r.multi do |multi|
22
+ multi.set "foo", "s1"
23
+ end
24
+
25
+ assert_equal "s1", r.get("foo")
26
+ end
27
+
28
+ def test_multi_exec_with_a_block_doesn_t_return_replies_for_multi_and_exec
29
+ r1, r2, nothing_else = r.multi do |multi|
30
+ multi.set "foo", "s1"
31
+ multi.get "foo"
32
+ end
33
+
34
+ assert_equal "OK", r1
35
+ assert_equal "s1", r2
36
+ assert_equal nil, nothing_else
37
+ end
38
+
39
+ def test_assignment_inside_multi_exec_block
40
+ r.multi do |m|
41
+ @first = m.sadd("foo", 1)
42
+ @second = m.sadd("foo", 1)
43
+ end
44
+
45
+ assert_equal true, @first.value
46
+ assert_equal false, @second.value
47
+ end
48
+
49
+ # Although we could support accessing the values in these futures,
50
+ # it doesn't make a lot of sense.
51
+ def test_assignment_inside_multi_exec_block_with_delayed_command_errors
52
+ assert_raise(Redis::CommandError) do
53
+ r.multi do |m|
54
+ @first = m.set("foo", "s1")
55
+ @second = m.incr("foo") # not an integer
56
+ @third = m.lpush("foo", "value") # wrong kind of value
57
+ end
58
+ end
59
+
60
+ assert_equal "OK", @first.value
61
+ assert_raise(Redis::CommandError) { @second.value }
62
+ assert_raise(Redis::FutureNotReady) { @third.value }
63
+ end
64
+
65
+ def test_assignment_inside_multi_exec_block_with_immediate_command_errors
66
+ assert_raise(Redis::CommandError) do
67
+ r.multi do |m|
68
+ m.doesnt_exist
69
+ @first = m.sadd("foo", 1)
70
+ @second = m.sadd("foo", 1)
71
+ end
72
+ end
73
+
74
+ assert_raise(Redis::FutureNotReady) { @first.value }
75
+ assert_raise(Redis::FutureNotReady) { @second.value }
76
+ end
77
+
78
+ def test_raise_immediate_errors_in_multi_exec
79
+ assert_raise(RuntimeError) do
80
+ r.multi do |multi|
81
+ multi.set "bar", "s2"
82
+ raise "Some error"
83
+ multi.set "baz", "s3"
84
+ end
85
+ end
86
+
87
+ assert_equal nil, r.get("bar")
88
+ assert_equal nil, r.get("baz")
89
+ end
90
+
91
+ def test_transformed_replies_as_return_values_for_multi_exec_block
92
+ info, _ = r.multi do |m|
93
+ r.info
94
+ end
95
+
96
+ assert info.kind_of?(Hash)
97
+ end
98
+
99
+ def test_transformed_replies_inside_multi_exec_block
100
+ r.multi do |m|
101
+ @info = r.info
102
+ end
103
+
104
+ assert @info.value.kind_of?(Hash)
105
+ end
106
+
107
+ def test_raise_command_errors_in_multi_exec
108
+ assert_raise(Redis::CommandError) do
109
+ r.multi do |m|
110
+ m.set("foo", "s1")
111
+ m.incr("foo") # not an integer
112
+ m.lpush("foo", "value") # wrong kind of value
113
+ end
114
+ end
115
+
116
+ assert_equal "s1", r.get("foo")
117
+ end
118
+
119
+ def test_raise_command_errors_when_accessing_futures_after_multi_exec
120
+ begin
121
+ r.multi do |m|
122
+ m.set("foo", "s1")
123
+ @counter = m.incr("foo") # not an integer
124
+ end
125
+ rescue Exception
126
+ # Not gonna deal with it
127
+ end
128
+
129
+ # We should test for Redis::Error here, but hiredis doesn't yet do
130
+ # custom error classes.
131
+ err = nil
132
+ begin
133
+ @counter.value
134
+ rescue => err
135
+ end
136
+
137
+ assert err.kind_of?(RuntimeError)
138
+ end
139
+
140
+ def test_multi_with_a_block_yielding_the_client
141
+ r.multi do |multi|
142
+ multi.set "foo", "s1"
143
+ end
144
+
145
+ assert_equal "s1", r.get("foo")
146
+ end
147
+
148
+ def test_raise_command_error_when_exec_fails
149
+ redis_mock(:exec => lambda { |*_| "-ERROR" }) do |redis|
150
+ assert_raise(Redis::CommandError) do
151
+ redis.multi do |m|
152
+ m.set "foo", "s1"
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def test_watch
159
+ res = r.watch "foo"
160
+
161
+ assert_equal "OK", res
162
+ end
163
+
164
+ def test_watch_with_an_unmodified_key
165
+ r.watch "foo"
166
+ r.multi do |multi|
167
+ multi.set "foo", "s1"
168
+ end
169
+
170
+ assert_equal "s1", r.get("foo")
171
+ end
172
+
173
+ def test_watch_with_an_unmodified_key_passed_as_array
174
+ r.watch ["foo", "bar"]
175
+ r.multi do |multi|
176
+ multi.set "foo", "s1"
177
+ end
178
+
179
+ assert_equal "s1", r.get("foo")
180
+ end
181
+
182
+ def test_watch_with_a_modified_key
183
+ r.watch "foo"
184
+ r.set "foo", "s1"
185
+ res = r.multi do |multi|
186
+ multi.set "foo", "s2"
187
+ end
188
+
189
+ assert_equal nil, res
190
+ assert_equal "s1", r.get("foo")
191
+ end
192
+
193
+ def test_watch_with_a_modified_key_passed_as_array
194
+ r.watch ["foo", "bar"]
195
+ r.set "foo", "s1"
196
+ res = r.multi do |multi|
197
+ multi.set "foo", "s2"
198
+ end
199
+
200
+ assert_equal nil, res
201
+ assert_equal "s1", r.get("foo")
202
+ end
203
+
204
+ def test_watch_with_a_block_and_an_unmodified_key
205
+ result = r.watch "foo" do |rd|
206
+
207
+ assert_same r, rd
208
+
209
+ rd.multi do |multi|
210
+ multi.set "foo", "s1"
211
+ end
212
+ end
213
+
214
+ assert_equal ["OK"], result
215
+ assert_equal "s1", r.get("foo")
216
+ end
217
+
218
+ def test_watch_with_a_block_and_a_modified_key
219
+ result = r.watch "foo" do |rd|
220
+
221
+ assert_same r, rd
222
+
223
+ rd.set "foo", "s1"
224
+ rd.multi do |multi|
225
+ multi.set "foo", "s2"
226
+ end
227
+ end
228
+
229
+ assert_equal nil, result
230
+ assert_equal "s1", r.get("foo")
231
+ end
232
+
233
+ def test_watch_with_a_block_that_raises_an_exception
234
+ r.set("foo", "s1")
235
+
236
+ begin
237
+ r.watch "foo" do
238
+ raise "test"
239
+ end
240
+ rescue RuntimeError
241
+ end
242
+
243
+ r.set("foo", "s2")
244
+
245
+ # If the watch was still set from within the block above, this multi/exec
246
+ # would fail. This proves that raising an exception above unwatches.
247
+ r.multi do |multi|
248
+ multi.set "foo", "s3"
249
+ end
250
+
251
+ assert_equal "s3", r.get("foo")
252
+ end
253
+
254
+ def test_unwatch_with_a_modified_key
255
+ r.watch "foo"
256
+ r.set "foo", "s1"
257
+ r.unwatch
258
+ r.multi do |multi|
259
+ multi.set "foo", "s2"
260
+ end
261
+
262
+ assert_equal "s2", r.get("foo")
263
+ end
264
+ end