dalli 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of dalli might be problematic. Click here for more details.

data/History.md CHANGED
@@ -1,6 +1,15 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 2.6.0
5
+ =======
6
+
7
+ - read_multi optimization, now checks local_cache [chendo, #306]
8
+ - Re-implement get_multi to be non-blocking [tmm1, #295]
9
+ - Add `dalli` accessor to dalli_store to access the underlying
10
+ Dalli::Client, for things like `get_multi`.
11
+ - Add `Dalli::GzipCompressor`, primarily for compatibility with nginx's HttpMemcachedModule using `memcached_gzip_flag`
12
+
4
13
  2.5.0
5
14
  =======
6
15
 
@@ -9,12 +18,14 @@ Dalli Changelog
9
18
  - Removed lots of old session_store test code, tests now all run without a default memcached server [#275]
10
19
  - Changed Dalli ActiveSupport adapter to always attempt instrumentation [brianmario, #284]
11
20
  - Change write operations (add/set/replace) to return false when value is too large to store [brianmario, #283]
21
+ - Allowing different compressors per client [naseem]
12
22
 
13
23
  2.4.0
14
24
  =======
15
25
  - Added the ability to swap out the compressed used to [de]compress cache data [brianmario, #276]
16
26
  - Fix get\_multi performance issues with lots of memcached servers [tmm1]
17
27
  - Throw more specific exceptions [tmm1]
28
+ - Allowing different types of serialization per client [naseem]
18
29
 
19
30
  2.3.0
20
31
  =======
data/README.md CHANGED
@@ -112,6 +112,9 @@ Dalli::Client accepts the following options. All times are in seconds.
112
112
 
113
113
  **compression_max_size**: Maximum value byte size for which to attempt compression. Default is unlimited.
114
114
 
115
+ **serializer**: The serializer to use for objects being stored (ex. JSON).
116
+ Default is Marshal.
117
+
115
118
  **socket_timeout**: Timeout for all socket operations (connect, read, write). Default is 0.5.
116
119
 
117
120
  **socket_max_failures**: When a socket operation fails after socket_timeout, the same operation is retried. This is to not immediately mark a server down when there's a very slight network problem. Default is 2.
@@ -128,6 +131,10 @@ Dalli::Client accepts the following options. All times are in seconds.
128
131
 
129
132
  **keepalive**: Boolean, if true Dalli will enable keep-alives on the socket so inactivity
130
133
 
134
+ **compressor**: The compressor to use for objects being stored.
135
+ Default is zlib, implemented under `Dalli::Compressor`.
136
+ If serving compressed data using nginx's HttpMemcachedModule, set `memcached_gzip_flag 2` and use `Dalli::GzipCompressor`
137
+
131
138
  Features and Changes
132
139
  ------------------------
133
140
 
data/dalli.gemspec CHANGED
@@ -3,6 +3,7 @@ require './lib/dalli/version'
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{dalli}
5
5
  s.version = Dalli::VERSION
6
+ s.license = "MIT"
6
7
 
7
8
  s.authors = ["Mike Perham"]
8
9
  s.description = %q{High performance memcached client for Ruby}
@@ -49,6 +49,13 @@ module ActiveSupport
49
49
  extend Strategy::LocalCache
50
50
  end
51
51
 
52
+ ##
53
+ # Access the underlying Dalli::Client instance for
54
+ # access to get_multi, etc.
55
+ def dalli
56
+ @data
57
+ end
58
+
52
59
  def fetch(name, options=nil)
53
60
  options ||= {}
54
61
  name = expanded_key name
@@ -118,13 +125,24 @@ module ActiveSupport
118
125
  def read_multi(*names)
119
126
  names.extract_options!
120
127
  names = names.flatten
121
- mapping = names.inject({}) { |memo, name| memo[escape(expanded_key(name))] = name; memo }
128
+ mapping = names.inject({}) { |memo, name| memo[expanded_key(name)] = name; memo }
122
129
  instrument(:read_multi, names) do
123
- results = @data.get_multi(mapping.keys)
130
+ results = {}
131
+ if local_cache
132
+ mapping.keys.each do |key|
133
+ if value = local_cache.read_entry(key, options)
134
+ results[key] = value
135
+ end
136
+ end
137
+ end
138
+
139
+ results.merge!(@data.get_multi(mapping.keys - results.keys))
124
140
  results.inject({}) do |memo, (inner, _)|
125
141
  entry = results[inner]
126
142
  # NB Backwards data compatibility, to be removed at some point
127
- memo[mapping[inner]] = (entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry)
143
+ value = (entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry)
144
+ memo[mapping[inner]] = value
145
+ local_cache.write_entry(inner, value, options) if local_cache
128
146
  memo
129
147
  end
130
148
  end
@@ -201,7 +219,7 @@ module ActiveSupport
201
219
 
202
220
  # Read an entry from the cache.
203
221
  def read_entry(key, options) # :nodoc:
204
- entry = @data.get(escape(key), options)
222
+ entry = @data.get(key, options)
205
223
  # NB Backwards data compatibility, to be removed at some point
206
224
  entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry
207
225
  rescue Dalli::DalliError => e
@@ -214,7 +232,7 @@ module ActiveSupport
214
232
  def write_entry(key, value, options) # :nodoc:
215
233
  method = options[:unless_exist] ? :add : :set
216
234
  expires_in = options[:expires_in]
217
- @data.send(method, escape(key), value, expires_in, options)
235
+ @data.send(method, key, value, expires_in, options)
218
236
  rescue Dalli::DalliError => e
219
237
  logger.error("DalliError: #{e.message}") if logger
220
238
  raise if @raise_errors
@@ -223,7 +241,7 @@ module ActiveSupport
223
241
 
224
242
  # Delete an entry from the cache.
225
243
  def delete_entry(key, options) # :nodoc:
226
- @data.delete(escape(key))
244
+ @data.delete(key)
227
245
  rescue Dalli::DalliError => e
228
246
  logger.error("DalliError: #{e.message}") if logger
229
247
  raise if @raise_errors
@@ -248,12 +266,8 @@ module ActiveSupport
248
266
  key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"}
249
267
  end
250
268
 
251
- key.to_param
252
- end
253
-
254
- def escape(key)
255
- key = key.to_s.dup
256
- key = key.force_encoding("BINARY") if key.respond_to?(:encode)
269
+ key = key.to_param
270
+ key.force_encoding('binary') if key.respond_to? :force_encoding
257
271
  key
258
272
  end
259
273
 
data/lib/dalli.rb CHANGED
@@ -1,10 +1,10 @@
1
+ require 'dalli/compressor'
1
2
  require 'dalli/client'
2
3
  require 'dalli/ring'
3
4
  require 'dalli/server'
4
5
  require 'dalli/socket'
5
6
  require 'dalli/version'
6
7
  require 'dalli/options'
7
- require 'dalli/compressor'
8
8
  require 'dalli/railtie' if defined?(::Rails::Railtie)
9
9
 
10
10
  module Dalli
@@ -39,27 +39,6 @@ module Dalli
39
39
  @logger = logger
40
40
  end
41
41
 
42
- # Default serialization to Marshal
43
- @serializer = Marshal
44
-
45
- def self.serializer
46
- @serializer
47
- end
48
-
49
- def self.serializer=(serializer)
50
- @serializer = serializer
51
- end
52
-
53
- # Default serialization to Dalli::Compressor
54
- @compressor = Compressor
55
-
56
- def self.compressor
57
- @compressor
58
- end
59
-
60
- def self.compressor=(compressor)
61
- @compressor = compressor
62
- end
63
42
  end
64
43
 
65
44
  if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
data/lib/dalli/client.rb CHANGED
@@ -22,13 +22,14 @@ module Dalli
22
22
  # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
23
23
  # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever
24
24
  # - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before
25
+ # - :serializer - defaults to Marshal
25
26
  # sending them to memcached.
27
+ # - :compressor - defaults to zlib
26
28
  #
27
29
  def initialize(servers=nil, options={})
28
30
  @servers = servers || env_servers || '127.0.0.1:11211'
29
31
  @options = normalize_options(options)
30
32
  @ring = nil
31
- @servers_in_use = nil
32
33
  end
33
34
 
34
35
  #
@@ -60,33 +61,80 @@ module Dalli
60
61
  options = nil
61
62
  options = keys.pop if keys.last.is_a?(Hash) || keys.last.nil?
62
63
  ring.lock do
63
- self.servers_in_use = Set.new
64
-
65
- keys.flatten.each do |key|
66
- begin
67
- perform(:getkq, key)
68
- rescue DalliError, NetworkError => e
69
- Dalli.logger.debug { e.inspect }
70
- Dalli.logger.debug { "unable to get key #{key}" }
64
+ begin
65
+ servers = self.servers_in_use = Set.new
66
+
67
+ keys.flatten.each do |key|
68
+ begin
69
+ perform(:getkq, key)
70
+ rescue DalliError, NetworkError => e
71
+ Dalli.logger.debug { e.inspect }
72
+ Dalli.logger.debug { "unable to get key #{key}" }
73
+ end
71
74
  end
72
- end
73
75
 
74
- values = {}
75
- servers_in_use.each do |server|
76
- next unless server.alive?
77
- begin
78
- server.request(:noop).each_pair do |key, value|
79
- values[key_without_namespace(key)] = value
76
+ values = {}
77
+ return values if servers.empty?
78
+
79
+ servers.each do |server|
80
+ next unless server.alive?
81
+ begin
82
+ server.multi_response_start
83
+ rescue DalliError, NetworkError => e
84
+ Dalli.logger.debug { e.inspect }
85
+ Dalli.logger.debug { "results from this server will be missing" }
86
+ servers.delete(server)
80
87
  end
81
- rescue DalliError, NetworkError => e
82
- Dalli.logger.debug { e.inspect }
83
- Dalli.logger.debug { "results from this server will be missing" }
84
88
  end
89
+
90
+ start = Time.now
91
+ loop do
92
+ # remove any dead servers
93
+ servers.delete_if { |s| s.sock.nil? }
94
+ break if servers.empty?
95
+
96
+ # calculate remaining timeout
97
+ elapsed = Time.now - start
98
+ timeout = servers.first.options[:socket_timeout]
99
+ if elapsed > timeout
100
+ readable = nil
101
+ else
102
+ sockets = servers.map(&:sock)
103
+ readable, _ = IO.select(sockets, nil, nil, timeout - elapsed)
104
+ end
105
+
106
+ if readable.nil?
107
+ # no response within timeout; abort pending connections
108
+ servers.each do |server|
109
+ puts "Abort!"
110
+ server.multi_response_abort
111
+ end
112
+ break
113
+
114
+ else
115
+ readable.each do |sock|
116
+ server = sock.server
117
+
118
+ begin
119
+ server.multi_response_nonblock.each do |key, value|
120
+ values[key_without_namespace(key)] = value
121
+ end
122
+
123
+ if server.multi_response_completed?
124
+ servers.delete(server)
125
+ end
126
+ rescue NetworkError
127
+ servers.delete(server)
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ values
134
+ ensure
135
+ self.servers_in_use = nil
85
136
  end
86
- values
87
137
  end
88
- ensure
89
- self.servers_in_use = nil
90
138
  end
91
139
 
92
140
  def fetch(key, ttl=nil, options=nil)
@@ -1,4 +1,5 @@
1
1
  require 'zlib'
2
+ require 'stringio'
2
3
 
3
4
  module Dalli
4
5
  class Compressor
@@ -10,4 +11,19 @@ module Dalli
10
11
  Zlib::Inflate.inflate(data)
11
12
  end
12
13
  end
14
+
15
+ class GzipCompressor
16
+ def self.compress(data)
17
+ io = StringIO.new("w")
18
+ gz = Zlib::GzipWriter.new(io)
19
+ gz.write(data)
20
+ gz.close
21
+ io.string
22
+ end
23
+
24
+ def self.decompress(data)
25
+ io = StringIO.new(data, "rb")
26
+ Zlib::GzipReader.new(io).read
27
+ end
28
+ end
13
29
  end
data/lib/dalli/options.rb CHANGED
@@ -31,6 +31,24 @@ module Dalli
31
31
  end
32
32
  end
33
33
 
34
+ def multi_response_start
35
+ @lock.synchronize do
36
+ super
37
+ end
38
+ end
39
+
40
+ def multi_response_nonblock
41
+ @lock.synchronize do
42
+ super
43
+ end
44
+ end
45
+
46
+ def multi_response_abort
47
+ @lock.synchronize do
48
+ super
49
+ end
50
+ end
51
+
34
52
  def lock!
35
53
  @lock.mon_enter
36
54
  end
data/lib/dalli/server.rb CHANGED
@@ -7,6 +7,7 @@ module Dalli
7
7
  attr_accessor :port
8
8
  attr_accessor :weight
9
9
  attr_accessor :options
10
+ attr_reader :sock
10
11
 
11
12
  DEFAULTS = {
12
13
  # seconds between trying to contact a remote server
@@ -19,10 +20,12 @@ module Dalli
19
20
  :socket_failure_delay => 0.01,
20
21
  # max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
21
22
  :value_max_bytes => 1024 * 1024,
23
+ :compressor => Compressor,
22
24
  # min byte size to attempt compression
23
25
  :compression_min_size => 1024,
24
26
  # max byte size for compression
25
27
  :compression_max_size => false,
28
+ :serializer => Marshal,
26
29
  :username => nil,
27
30
  :password => nil,
28
31
  :keepalive => true
@@ -96,6 +99,89 @@ module Dalli
96
99
  def unlock!
97
100
  end
98
101
 
102
+ def serializer
103
+ @options[:serializer]
104
+ end
105
+
106
+ def compressor
107
+ @options[:compressor]
108
+ end
109
+
110
+ # Start reading key/value pairs from this connection. This is usually called
111
+ # after a series of GETKQ commands. A NOOP is sent, and the server begins
112
+ # flushing responses for kv pairs that were found.
113
+ #
114
+ # Returns nothing.
115
+ def multi_response_start
116
+ verify_state
117
+ write_noop
118
+ @multi_buffer = ''
119
+ @inprogress = true
120
+ end
121
+
122
+ # Did the last call to #multi_response_start complete successfully?
123
+ def multi_response_completed?
124
+ @multi_buffer.nil?
125
+ end
126
+
127
+ # Attempt to receive and parse as many key/value pairs as possible
128
+ # from this server. After #multi_response_start, this should be invoked
129
+ # repeatedly whenever this server's socket is readable until
130
+ # #multi_response_completed?.
131
+ #
132
+ # Returns a Hash of kv pairs received.
133
+ def multi_response_nonblock
134
+ raise 'multi_response has completed' if @multi_buffer.nil?
135
+
136
+ @multi_buffer << @sock.read_available
137
+ buf = @multi_buffer
138
+ values = {}
139
+
140
+ while buf.bytesize >= 24
141
+ header = buf.slice(0, 24)
142
+ (key_length, _, body_length) = header.unpack(KV_HEADER)
143
+
144
+ if key_length == 0
145
+ # all done!
146
+ @multi_buffer = nil
147
+ @inprogress = false
148
+ break
149
+
150
+ elsif buf.bytesize >= (24 + body_length)
151
+ buf.slice!(0, 24)
152
+ flags = buf.slice!(0, 4).unpack('N')[0]
153
+ key = buf.slice!(0, key_length)
154
+ value = buf.slice!(0, body_length - key_length - 4) if body_length - key_length - 4 > 0
155
+
156
+ begin
157
+ values[key] = deserialize(value, flags)
158
+ rescue DalliError
159
+ end
160
+
161
+ else
162
+ # not enough data yet, wait for more
163
+ break
164
+ end
165
+ end
166
+
167
+ values
168
+ rescue SystemCallError, Timeout::Error, EOFError
169
+ failure!
170
+ end
171
+
172
+ # Abort an earlier #multi_response_start. Used to signal an external
173
+ # timeout. The underlying socket is disconnected, and the exception is
174
+ # swallowed.
175
+ #
176
+ # Returns nothing.
177
+ def multi_response_abort
178
+ @multi_buffer = nil
179
+ @inprogress = false
180
+ failure!
181
+ rescue NetworkError
182
+ true
183
+ end
184
+
99
185
  # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
100
186
 
101
187
  private
@@ -234,11 +320,15 @@ module Dalli
234
320
  body ? longlong(*body.unpack('NN')) : body
235
321
  end
236
322
 
323
+ def write_noop
324
+ req = [REQUEST, OPCODES[:noop], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
325
+ write(req)
326
+ end
327
+
237
328
  # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
238
329
  # We need to read all the responses at once.
239
330
  def noop
240
- req = [REQUEST, OPCODES[:noop], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
241
- write(req)
331
+ write_noop
242
332
  multi_response
243
333
  end
244
334
 
@@ -295,7 +385,7 @@ module Dalli
295
385
  value = unless options && options[:raw]
296
386
  marshalled = true
297
387
  begin
298
- Dalli.serializer.dump(value)
388
+ self.serializer.dump(value)
299
389
  rescue => ex
300
390
  # Marshalling can throw several different types of generic Ruby exceptions.
301
391
  # Convert to a specific exception so we can special case it higher up the stack.
@@ -309,7 +399,7 @@ module Dalli
309
399
  compressed = false
310
400
  if @options[:compress] && value.bytesize >= @options[:compression_min_size] &&
311
401
  (!@options[:compression_max_size] || value.bytesize <= @options[:compression_max_size])
312
- value = Dalli.compressor.compress(value)
402
+ value = self.compressor.compress(value)
313
403
  compressed = true
314
404
  end
315
405
 
@@ -320,8 +410,8 @@ module Dalli
320
410
  end
321
411
 
322
412
  def deserialize(value, flags)
323
- value = Dalli.compressor.decompress(value) if (flags & FLAG_COMPRESSED) != 0
324
- value = Dalli.serializer.load(value) if (flags & FLAG_SERIALIZED) != 0
413
+ value = self.compressor.decompress(value) if (flags & FLAG_COMPRESSED) != 0
414
+ value = self.serializer.load(value) if (flags & FLAG_SERIALIZED) != 0
325
415
  value
326
416
  rescue TypeError
327
417
  raise if $!.message !~ /needs to have method `_load'|exception class\/object expected|instance of IO needed|incompatible marshal file format/
@@ -432,7 +522,7 @@ module Dalli
432
522
 
433
523
  begin
434
524
  @pid = Process.pid
435
- @sock = KSocket.open(hostname, port, options)
525
+ @sock = KSocket.open(hostname, port, self, options)
436
526
  @version = version # trigger actual connect
437
527
  sasl_authentication if need_auth?
438
528
  up!
data/lib/dalli/socket.rb CHANGED
@@ -3,7 +3,7 @@ begin
3
3
  puts "Using kgio socket IO" if defined?($TESTING) && $TESTING
4
4
 
5
5
  class Dalli::Server::KSocket < Kgio::Socket
6
- attr_accessor :options
6
+ attr_accessor :options, :server
7
7
 
8
8
  def kgio_wait_readable
9
9
  IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
@@ -13,12 +13,13 @@ begin
13
13
  IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
14
14
  end
15
15
 
16
- def self.open(host, port, options = {})
16
+ def self.open(host, port, server, options = {})
17
17
  addr = Socket.pack_sockaddr_in(port, host)
18
18
  sock = start(addr)
19
19
  sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
20
20
  sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
21
21
  sock.options = options
22
+ sock.server = server
22
23
  sock.kgio_wait_writable
23
24
  sock
24
25
  end
@@ -34,6 +35,22 @@ begin
34
35
  value
35
36
  end
36
37
 
38
+ def read_available
39
+ value = ''
40
+ loop do
41
+ ret = kgio_tryread(8196)
42
+ case ret
43
+ when nil
44
+ raise EOFError, 'end of stream'
45
+ when :wait_readable
46
+ break
47
+ else
48
+ value << ret
49
+ end
50
+ end
51
+ value
52
+ end
53
+
37
54
  end
38
55
 
39
56
  if ::Kgio.respond_to?(:wait_readable=)
@@ -45,14 +62,15 @@ rescue LoadError
45
62
 
46
63
  puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
47
64
  class Dalli::Server::KSocket < TCPSocket
48
- attr_accessor :options
65
+ attr_accessor :options, :server
49
66
 
50
- def self.open(host, port, options = {})
67
+ def self.open(host, port, server, options = {})
51
68
  Timeout.timeout(options[:socket_timeout]) do
52
69
  sock = new(host, port)
53
70
  sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
54
71
  sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
55
72
  sock.options = { :host => host, :port => port }.merge(options)
73
+ sock.server = server
56
74
  sock
57
75
  end
58
76
  end
@@ -74,5 +92,17 @@ rescue LoadError
74
92
  value
75
93
  end
76
94
 
95
+ def read_available
96
+ value = ''
97
+ loop do
98
+ begin
99
+ value << read_nonblock(8196)
100
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
101
+ break
102
+ end
103
+ end
104
+ value
105
+ end
106
+
77
107
  end
78
108
  end
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '2.5.0'
2
+ VERSION = '2.6.0'
3
3
  end
@@ -24,7 +24,7 @@ describe 'performance' do
24
24
  should 'run benchmarks' do
25
25
  memcached do
26
26
 
27
- Benchmark.bm(31) do |x|
27
+ Benchmark.bm(37) do |x|
28
28
 
29
29
  n = 2500
30
30
 
@@ -44,6 +44,61 @@ describe 'performance' do
44
44
  end
45
45
  end
46
46
 
47
+ x.report("mixed:rails-localcache:dalli") do
48
+ n.times do
49
+ @ds.with_local_cache do
50
+ @ds.read @key1
51
+ @ds.write @key2, @value
52
+ @ds.fetch(@key3) { @value }
53
+ @ds.fetch(@key2) { @value }
54
+ @ds.fetch(@key1) { @value }
55
+ @ds.write @key2, @value, :unless_exists => true
56
+ @ds.delete @key2
57
+ @ds.increment @counter, 1, :initial => 100
58
+ @ds.increment @counter, 1, :expires_in => 12
59
+ @ds.decrement @counter, 1
60
+ end
61
+ end
62
+ end
63
+
64
+ @ds.clear
65
+ sizeable_data = "<marquee>some view partial data</marquee>" * 50
66
+ [@key1, @key2, @key3, @key4, @key5, @key6].each do |key|
67
+ @ds.write(key, sizeable_data)
68
+ end
69
+
70
+ x.report("read_multi_big:rails:dalli") do
71
+ n.times do
72
+ @ds.read_multi @key1, @key2, @key3, @key4
73
+ @ds.read @key1
74
+ @ds.read @key2
75
+ @ds.read @key3
76
+ @ds.read @key4
77
+ @ds.read @key1
78
+ @ds.read @key2
79
+ @ds.read @key3
80
+ @ds.read_multi @key1, @key2, @key3
81
+ end
82
+ end
83
+
84
+ x.report("read_multi_big:rails-localcache:dalli") do
85
+ n.times do
86
+ @ds.with_local_cache do
87
+ @ds.read_multi @key1, @key2, @key3, @key4
88
+ @ds.read @key1
89
+ @ds.read @key2
90
+ @ds.read @key3
91
+ @ds.read @key4
92
+ end
93
+ @ds.with_local_cache do
94
+ @ds.read @key1
95
+ @ds.read @key2
96
+ @ds.read @key3
97
+ @ds.read_multi @key1, @key2, @key3
98
+ end
99
+ end
100
+ end
101
+
47
102
  @m = Dalli::Client.new(@servers)
48
103
  x.report("set:plain:dalli") do
49
104
  n.times do
@@ -114,6 +114,37 @@ describe 'ActiveSupport' do
114
114
  end
115
115
  end
116
116
 
117
+ should 'support read_multi with LocalCache' do
118
+ with_activesupport do
119
+ memcached do
120
+ connect
121
+ x = rand_key
122
+ y = rand_key
123
+ assert_equal({}, @dalli.read_multi(x, y))
124
+ @dalli.write(x, '123')
125
+ @dalli.write(y, 456)
126
+
127
+ @dalli.with_local_cache do
128
+ assert_equal({ x => '123', y => 456 }, @dalli.read_multi(x, y))
129
+ Dalli::Client.any_instance.expects(:get).with(any_parameters).never
130
+
131
+ dres = @dalli.read(x)
132
+ assert_equal dres, '123'
133
+ end
134
+
135
+ Dalli::Client.any_instance.unstub(:get)
136
+
137
+ # Fresh LocalStore
138
+ @dalli.with_local_cache do
139
+ @dalli.read(x)
140
+ Dalli::Client.any_instance.expects(:get_multi).with([y.to_s]).returns(y.to_s => 456)
141
+
142
+ assert_equal({ x => '123', y => 456}, @dalli.read_multi(x, y))
143
+ end
144
+ end
145
+ end
146
+ end
147
+
117
148
  should 'support read, write and delete' do
118
149
  with_activesupport do
119
150
  memcached do
@@ -147,6 +178,34 @@ describe 'ActiveSupport' do
147
178
  end
148
179
  end
149
180
 
181
+ should 'support read, write and delete with LocalCache' do
182
+ with_activesupport do
183
+ memcached do
184
+ connect
185
+ y = rand_key.to_s
186
+ @dalli.with_local_cache do
187
+ Dalli::Client.any_instance.expects(:get).with(y, {}).once.returns(123)
188
+ dres = @dalli.read(y)
189
+ assert_equal 123, dres
190
+
191
+ Dalli::Client.any_instance.expects(:get).with(y, {}).never
192
+
193
+ dres = @dalli.read(y)
194
+ assert_equal 123, dres
195
+
196
+ @dalli.write(y, 456)
197
+ dres = @dalli.read(y)
198
+ assert_equal 456, dres
199
+
200
+ @dalli.delete(y)
201
+ Dalli::Client.any_instance.expects(:get).with(y, {}).once.returns(nil)
202
+ dres = @dalli.read(y)
203
+ assert_equal nil, dres
204
+ end
205
+ end
206
+ end
207
+ end
208
+
150
209
  should 'support increment/decrement commands' do
151
210
  with_activesupport do
152
211
  memcached do
@@ -16,21 +16,34 @@ end
16
16
  describe 'Compressor' do
17
17
 
18
18
  should 'default to Dalli::Compressor' do
19
- assert_equal Dalli::Compressor, Dalli.compressor
19
+ memcache = Dalli::Client.new('127.0.0.1:11211')
20
+ memcache.set 1,2
21
+ assert_equal Dalli::Compressor, memcache.instance_variable_get('@ring').servers.first.compressor
20
22
  end
21
23
 
22
24
  should 'support a custom compressor' do
23
- original_compressor = Dalli.compressor
25
+ memcache = Dalli::Client.new('127.0.0.1:11211', :compressor => NoopCompressor)
26
+ memcache.set 1,2
24
27
  begin
25
- Dalli.compressor = NoopCompressor
26
- assert_equal NoopCompressor, Dalli.compressor
28
+ assert_equal NoopCompressor, memcache.instance_variable_get('@ring').servers.first.compressor
27
29
 
28
30
  memcached(19127) do |dc|
29
31
  assert dc.set("string-test", "a test string")
30
32
  assert_equal("a test string", dc.get("string-test"))
31
33
  end
32
- ensure
33
- Dalli.compressor = original_compressor
34
34
  end
35
35
  end
36
36
  end
37
+
38
+ describe 'GzipCompressor' do
39
+
40
+ should 'compress and uncompress data using Zlib::GzipWriter/Reader' do
41
+ memcached(19127,nil,{:compress=>true,:compressor=>Dalli::GzipCompressor}) do |dc|
42
+ data = (0...1025).map{65.+(rand(26)).chr}.join
43
+ assert dc.set("test", data)
44
+ assert_equal Dalli::GzipCompressor, dc.instance_variable_get('@ring').servers.first.compressor
45
+ assert_equal(data, dc.get("test"))
46
+ end
47
+ end
48
+
49
+ end
data/test/test_dalli.rb CHANGED
@@ -184,18 +184,18 @@ describe 'Dalli' do
184
184
  resp = dc.get_multi(%w(a b c d e f))
185
185
  assert_equal({ 'a' => 'foo', 'b' => 123, 'c' => %w(a b c) }, resp)
186
186
 
187
- # Perform a huge multi-get with 10,000 elements.
187
+ # Perform a big multi-get with 1000 elements.
188
188
  arr = []
189
189
  dc.multi do
190
- 10_000.times do |idx|
190
+ 1000.times do |idx|
191
191
  dc.set idx, idx
192
192
  arr << idx
193
193
  end
194
194
  end
195
195
 
196
196
  result = dc.get_multi(arr)
197
- assert_equal(10_000, result.size)
198
- assert_equal(1000, result['1000'])
197
+ assert_equal(1000, result.size)
198
+ assert_equal(50, result['50'])
199
199
  end
200
200
  end
201
201
 
@@ -410,6 +410,9 @@ describe 'Dalli' do
410
410
  assert_equal 0, inc % 5
411
411
  cache.decr('cat', 5)
412
412
  assert_equal 11, cache.get('b')
413
+
414
+ assert_equal %w(a b), cache.get_multi('a', 'b', 'c').keys.sort
415
+
413
416
  end
414
417
  end
415
418
  end
@@ -6,21 +6,21 @@ require 'memcached_mock'
6
6
  describe 'Serializer' do
7
7
 
8
8
  should 'default to Marshal' do
9
- assert_equal Marshal, Dalli.serializer
9
+ memcache = Dalli::Client.new('127.0.0.1:11211')
10
+ memcache.set 1,2
11
+ assert_equal Marshal, memcache.instance_variable_get('@ring').servers.first.serializer
10
12
  end
11
13
 
12
14
  should 'support a custom serializer' do
13
- original_serializer = Dalli.serializer
15
+ memcache = Dalli::Client.new('127.0.0.1:11211', :serializer => JSON)
16
+ memcache.set 1,2
14
17
  begin
15
- Dalli.serializer = JSON
16
- assert_equal JSON, Dalli.serializer
18
+ assert_equal JSON, memcache.instance_variable_get('@ring').servers.first.serializer
17
19
 
18
20
  memcached(19128) do |dc|
19
21
  assert dc.set("json_test", {"foo" => "bar"})
20
22
  assert_equal({"foo" => "bar"}, dc.get("json_test"))
21
23
  end
22
- ensure
23
- Dalli.serializer = original_serializer
24
24
  end
25
25
  end
26
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-13 00:00:00.000000000 Z
12
+ date: 2012-12-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mini_shoulda
@@ -98,7 +98,8 @@ files:
98
98
  - test/test_sasl.rb
99
99
  - test/test_serializer.rb
100
100
  homepage: http://github.com/mperham/dalli
101
- licenses: []
101
+ licenses:
102
+ - MIT
102
103
  post_install_message:
103
104
  rdoc_options:
104
105
  - --charset=UTF-8