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 +11 -0
- data/README.md +7 -0
- data/dalli.gemspec +1 -0
- data/lib/active_support/cache/dalli_store.rb +26 -12
- data/lib/dalli.rb +1 -22
- data/lib/dalli/client.rb +70 -22
- data/lib/dalli/compressor.rb +16 -0
- data/lib/dalli/options.rb +18 -0
- data/lib/dalli/server.rb +97 -7
- data/lib/dalli/socket.rb +34 -4
- data/lib/dalli/version.rb +1 -1
- data/test/benchmark_test.rb +56 -1
- data/test/test_active_support.rb +59 -0
- data/test/test_compressor.rb +19 -6
- data/test/test_dalli.rb +7 -4
- data/test/test_serializer.rb +6 -6
- metadata +4 -3
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
@@ -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[
|
128
|
+
mapping = names.inject({}) { |memo, name| memo[expanded_key(name)] = name; memo }
|
122
129
|
instrument(:read_multi, names) do
|
123
|
-
results =
|
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
|
-
|
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(
|
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,
|
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(
|
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
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
server.
|
79
|
-
|
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)
|
data/lib/dalli/compressor.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 =
|
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 =
|
324
|
-
value =
|
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
data/test/benchmark_test.rb
CHANGED
@@ -24,7 +24,7 @@ describe 'performance' do
|
|
24
24
|
should 'run benchmarks' do
|
25
25
|
memcached do
|
26
26
|
|
27
|
-
Benchmark.bm(
|
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
|
data/test/test_active_support.rb
CHANGED
@@ -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
|
data/test/test_compressor.rb
CHANGED
@@ -16,21 +16,34 @@ end
|
|
16
16
|
describe 'Compressor' do
|
17
17
|
|
18
18
|
should 'default to Dalli::Compressor' do
|
19
|
-
|
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
|
-
|
25
|
+
memcache = Dalli::Client.new('127.0.0.1:11211', :compressor => NoopCompressor)
|
26
|
+
memcache.set 1,2
|
24
27
|
begin
|
25
|
-
|
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
|
187
|
+
# Perform a big multi-get with 1000 elements.
|
188
188
|
arr = []
|
189
189
|
dc.multi do
|
190
|
-
|
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(
|
198
|
-
assert_equal(
|
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
|
data/test/test_serializer.rb
CHANGED
@@ -6,21 +6,21 @@ require 'memcached_mock'
|
|
6
6
|
describe 'Serializer' do
|
7
7
|
|
8
8
|
should 'default to Marshal' do
|
9
|
-
|
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
|
-
|
15
|
+
memcache = Dalli::Client.new('127.0.0.1:11211', :serializer => JSON)
|
16
|
+
memcache.set 1,2
|
14
17
|
begin
|
15
|
-
|
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.
|
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-
|
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
|