dalli 0.10.1 → 0.11.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 +14 -0
- data/README.md +0 -2
- data/dalli.gemspec +1 -1
- data/lib/dalli.rb +1 -1
- data/lib/dalli/client.rb +40 -26
- data/lib/dalli/server.rb +106 -54
- data/lib/dalli/version.rb +1 -1
- data/test/test_dalli.rb +54 -5
- data/test/test_encoding.rb +1 -1
- data/test/test_network.rb +4 -4
- metadata +6 -6
data/History.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
Dalli Changelog
|
2
2
|
=====================
|
3
3
|
|
4
|
+
0.11.0
|
5
|
+
======
|
6
|
+
|
7
|
+
Warning: this release changes how Dalli marshals data. I do not guarantee compatibility until 1.0 but I will increment the minor version every time a release breaks compatibility until 1.0.
|
8
|
+
|
9
|
+
IT IS HIGHLY RECOMMENDED YOU FLUSH YOUR CACHE BEFORE UPGRADING.
|
10
|
+
|
11
|
+
- multi() now works reentrantly.
|
12
|
+
- Added new Dalli::Client option for default TTLs, :expires_in, defaults to 0 (aka forever).
|
13
|
+
- Added new Dalli::Client option, :compression, to enable auto-compression of values.
|
14
|
+
- Refactor how Dalli stores data on the server. Values are now tagged
|
15
|
+
as "marshalled" or "compressed" so they can be automatically deserialized
|
16
|
+
without the client having to know how they were stored.
|
17
|
+
|
4
18
|
0.10.1
|
5
19
|
======
|
6
20
|
|
data/README.md
CHANGED
@@ -113,8 +113,6 @@ Features and Changes
|
|
113
113
|
|
114
114
|
Dalli is **NOT** 100% API compatible with memcache-client. If you have code which uses the MemCache API directly, it will likely need small tweaks. Method parameters and return values changed slightly. See Upgrade.md for more detail.
|
115
115
|
|
116
|
-
I've removed support for key namespaces and automatic pruning of keys longer than 250 characters. ActiveSupport::Cache implements these features so there is little need for Dalli to reinvent them.
|
117
|
-
|
118
116
|
By default, Dalli is thread-safe. Disable thread-safety at your own peril.
|
119
117
|
|
120
118
|
Note that Dalli does not require ActiveSupport or Rails. You can safely use it in your own Ruby projects.
|
data/dalli.gemspec
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.test_files = Dir.glob("test/**/*")
|
29
29
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
30
30
|
s.add_development_dependency(%q<mocha>, [">= 0"])
|
31
|
-
s.add_development_dependency(%q<rails>, [">= 3.0.
|
31
|
+
s.add_development_dependency(%q<rails>, [">= 3.0.1"])
|
32
32
|
s.add_development_dependency(%q<memcache-client>, [">= 1.8.5"])
|
33
33
|
end
|
34
34
|
|
data/lib/dalli.rb
CHANGED
data/lib/dalli/client.rb
CHANGED
@@ -4,20 +4,25 @@ module Dalli
|
|
4
4
|
##
|
5
5
|
# Dalli::Client is the main class which developers will use to interact with
|
6
6
|
# the memcached server. Usage:
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# :threadsafe => true, :failover => true)
|
10
|
-
#
|
7
|
+
#
|
8
|
+
# Dalli::Client.new(['localhost:11211:10', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5'],
|
9
|
+
# :threadsafe => true, :failover => true, :expires_in => 300)
|
10
|
+
#
|
11
11
|
# servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
|
12
|
-
# Both weight and port are optional.
|
12
|
+
# Both weight and port are optional. If you pass in nil, Dalli will default to 'localhost:11211'.
|
13
|
+
# Note that the <tt>MEMCACHE_SERVERS</tt> environment variable will override the servers parameter for use
|
14
|
+
# in managed environments like Heroku.
|
13
15
|
#
|
14
16
|
# Options:
|
15
|
-
#
|
16
|
-
#
|
17
|
+
# - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
|
18
|
+
# - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
|
19
|
+
# - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever
|
20
|
+
# - :compression - defaults to false, if true Dalli will compress values larger than 100 bytes before
|
21
|
+
# sending them to memcached.
|
17
22
|
#
|
18
23
|
def initialize(servers=nil, options={})
|
19
24
|
@servers = env_servers || servers || 'localhost:11211'
|
20
|
-
@options = options
|
25
|
+
@options = { :expires_in => 0 }.merge(options)
|
21
26
|
end
|
22
27
|
|
23
28
|
#
|
@@ -30,10 +35,10 @@ module Dalli
|
|
30
35
|
# pipelined as Dalli will use 'quiet' operations where possible.
|
31
36
|
# Currently supports the set, add, replace and delete operations.
|
32
37
|
def multi
|
33
|
-
Thread.current[:
|
38
|
+
old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
|
34
39
|
yield
|
35
40
|
ensure
|
36
|
-
Thread.current[:
|
41
|
+
Thread.current[:dalli_multi] = old
|
37
42
|
end
|
38
43
|
|
39
44
|
def get(key, options=nil)
|
@@ -54,7 +59,8 @@ module Dalli
|
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
|
-
def fetch(key, ttl=
|
62
|
+
def fetch(key, ttl=nil, options=nil)
|
63
|
+
ttl ||= @options[:expires_in]
|
58
64
|
val = get(key, options)
|
59
65
|
if val.nil? && block_given?
|
60
66
|
val = yield
|
@@ -63,25 +69,29 @@ module Dalli
|
|
63
69
|
val
|
64
70
|
end
|
65
71
|
|
66
|
-
def cas(key, ttl=
|
72
|
+
def cas(key, ttl=nil, options=nil, &block)
|
73
|
+
ttl ||= @options[:expires_in]
|
67
74
|
(value, cas) = perform(:cas, key)
|
68
75
|
value = (!value || value == 'Not found') ? nil : deserialize(value, options)
|
69
76
|
if value
|
70
77
|
newvalue = block.call(value)
|
71
|
-
perform(:add, key, serialize(newvalue, options), ttl, cas)
|
78
|
+
perform(:add, key, serialize(newvalue, options), ttl, cas, options)
|
72
79
|
end
|
73
80
|
end
|
74
81
|
|
75
|
-
def set(key, value, ttl=
|
76
|
-
|
82
|
+
def set(key, value, ttl=nil, options=nil)
|
83
|
+
ttl ||= @options[:expires_in]
|
84
|
+
perform(:set, key, serialize(value, options), ttl, options)
|
77
85
|
end
|
78
86
|
|
79
|
-
def add(key, value, ttl=
|
80
|
-
|
87
|
+
def add(key, value, ttl=nil, options=nil)
|
88
|
+
ttl ||= @options[:expires_in]
|
89
|
+
perform(:add, key, serialize(value, options), ttl, 0, options)
|
81
90
|
end
|
82
91
|
|
83
|
-
def replace(key, value, ttl=
|
84
|
-
|
92
|
+
def replace(key, value, ttl=nil, options=nil)
|
93
|
+
ttl ||= @options[:expires_in]
|
94
|
+
perform(:replace, key, serialize(value, options), ttl, options)
|
85
95
|
end
|
86
96
|
|
87
97
|
def delete(key)
|
@@ -116,8 +126,9 @@ module Dalli
|
|
116
126
|
# If default is nil, the counter must already exist or the operation
|
117
127
|
# will fail and will return nil. Otherwise this method will return
|
118
128
|
# the new value for the counter.
|
119
|
-
def incr(key, amt=1, ttl=
|
129
|
+
def incr(key, amt=1, ttl=nil, default=nil)
|
120
130
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
131
|
+
ttl ||= @options[:expires_in]
|
121
132
|
perform(:incr, key, amt, ttl, default)
|
122
133
|
end
|
123
134
|
|
@@ -131,8 +142,9 @@ module Dalli
|
|
131
142
|
# If default is nil, the counter must already exist or the operation
|
132
143
|
# will fail and will return nil. Otherwise this method will return
|
133
144
|
# the new value for the counter.
|
134
|
-
def decr(key, amt=1, ttl=
|
145
|
+
def decr(key, amt=1, ttl=nil, default=nil)
|
135
146
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
147
|
+
ttl ||= @options[:expires_in]
|
136
148
|
perform(:decr, key, amt, ttl, default)
|
137
149
|
end
|
138
150
|
|
@@ -153,19 +165,21 @@ module Dalli
|
|
153
165
|
def ring
|
154
166
|
@ring ||= Dalli::Ring.new(
|
155
167
|
Array(@servers).map do |s|
|
156
|
-
Dalli::Server.new(s)
|
168
|
+
Dalli::Server.new(s, @options)
|
157
169
|
end, @options
|
158
170
|
)
|
159
171
|
end
|
160
172
|
|
161
173
|
def serialize(value, options)
|
162
|
-
|
174
|
+
value
|
175
|
+
# options && options[:raw] ? value.to_s : ::Marshal.dump(value)
|
163
176
|
end
|
164
177
|
|
165
178
|
def deserialize(value, options)
|
166
|
-
|
167
|
-
|
168
|
-
|
179
|
+
value
|
180
|
+
# options && options[:raw] ? value : ::Marshal.load(value)
|
181
|
+
# rescue TypeError
|
182
|
+
# raise Dalli::DalliError, "Invalid marshalled data in memcached: #{value}"
|
169
183
|
end
|
170
184
|
|
171
185
|
def env_servers
|
data/lib/dalli/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'timeout'
|
3
|
+
require 'zlib'
|
3
4
|
|
4
5
|
module Dalli
|
5
6
|
class Server
|
@@ -7,7 +8,7 @@ module Dalli
|
|
7
8
|
attr_accessor :port
|
8
9
|
attr_accessor :weight
|
9
10
|
|
10
|
-
def initialize(attribs)
|
11
|
+
def initialize(attribs, options=nil)
|
11
12
|
(@hostname, @port, @weight) = attribs.split(':')
|
12
13
|
@port ||= 11211
|
13
14
|
@port = Integer(@port)
|
@@ -15,6 +16,7 @@ module Dalli
|
|
15
16
|
@weight = Integer(@weight)
|
16
17
|
@down_at = nil
|
17
18
|
@version = detect_memcached_version
|
19
|
+
@options = options
|
18
20
|
Dalli.logger.debug { "#{@hostname}:#{@port} running memcached v#{@version}" }
|
19
21
|
end
|
20
22
|
|
@@ -28,8 +30,9 @@ module Dalli
|
|
28
30
|
raise
|
29
31
|
rescue Exception => ex
|
30
32
|
Dalli.logger.error "Unexpected exception in Dalli: #{ex.class.name}: #{ex.message}"
|
33
|
+
Dalli.logger.error "This is a bug in Dalli, please enter an issue in Github if it does not already exist."
|
31
34
|
Dalli.logger.error ex.backtrace.join("\n\t")
|
32
|
-
down!
|
35
|
+
down!(true)
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
@@ -42,8 +45,8 @@ module Dalli
|
|
42
45
|
connection
|
43
46
|
true
|
44
47
|
rescue Dalli::NetworkError => dne
|
45
|
-
Dalli.logger.info(
|
46
|
-
|
48
|
+
Dalli.logger.info(dne.message)
|
49
|
+
false
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
@@ -85,15 +88,21 @@ module Dalli
|
|
85
88
|
end
|
86
89
|
end
|
87
90
|
|
88
|
-
def down!
|
91
|
+
def down!(toss_exception=false)
|
89
92
|
close
|
90
93
|
@down_at = Time.now.to_i
|
91
|
-
@msg = $!.message
|
94
|
+
@msg = @msg || ($! && $!.message) || ''
|
95
|
+
raise Dalli::NetworkError, "#{self.hostname}:#{self.port} is currently down: #{@msg}" if toss_exception
|
92
96
|
nil
|
93
97
|
end
|
94
98
|
|
99
|
+
def up!
|
100
|
+
@down_at = nil
|
101
|
+
@msg = nil
|
102
|
+
end
|
103
|
+
|
95
104
|
def multi?
|
96
|
-
Thread.current[:
|
105
|
+
Thread.current[:dalli_multi]
|
97
106
|
end
|
98
107
|
|
99
108
|
ONE_MB = 1024 * 1024
|
@@ -101,7 +110,7 @@ module Dalli
|
|
101
110
|
def get(key)
|
102
111
|
req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
|
103
112
|
write(req)
|
104
|
-
generic_response
|
113
|
+
generic_response(true)
|
105
114
|
end
|
106
115
|
|
107
116
|
def getkq(key)
|
@@ -109,40 +118,41 @@ module Dalli
|
|
109
118
|
write(req)
|
110
119
|
end
|
111
120
|
|
112
|
-
def set(key, value, ttl)
|
113
|
-
|
121
|
+
def set(key, value, ttl, options)
|
122
|
+
(value, flags) = serialize(value, options)
|
114
123
|
|
115
|
-
req = [REQUEST, OPCODES[multi? ? :setq : :set], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0,
|
124
|
+
req = [REQUEST, OPCODES[multi? ? :setq : :set], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, flags, ttl, key, value].pack(FORMAT[:set])
|
116
125
|
write(req)
|
117
126
|
generic_response unless multi?
|
118
127
|
end
|
119
128
|
|
120
|
-
def
|
121
|
-
|
122
|
-
write(req)
|
123
|
-
generic_response
|
124
|
-
end
|
129
|
+
def add(key, value, ttl, cas, options)
|
130
|
+
(value, flags) = serialize(value, options)
|
125
131
|
|
126
|
-
|
127
|
-
raise Dalli::DalliError, "Value too large, memcached can only store 1MB of data per key" if value.bytesize > ONE_MB
|
128
|
-
|
129
|
-
req = [REQUEST, OPCODES[multi? ? :addq : :add], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, 0, ttl, key, value].pack(FORMAT[:add])
|
132
|
+
req = [REQUEST, OPCODES[multi? ? :addq : :add], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, flags, ttl, key, value].pack(FORMAT[:add])
|
130
133
|
write(req)
|
131
134
|
generic_response unless multi?
|
132
135
|
end
|
133
136
|
|
134
|
-
def
|
135
|
-
|
137
|
+
def replace(key, value, ttl, options)
|
138
|
+
(value, flags) = serialize(value, options)
|
139
|
+
req = [REQUEST, OPCODES[multi? ? :replaceq : :replace], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, flags, ttl, key, value].pack(FORMAT[:replace])
|
136
140
|
write(req)
|
137
|
-
generic_response
|
141
|
+
generic_response unless multi?
|
138
142
|
end
|
139
|
-
|
143
|
+
|
140
144
|
def delete(key)
|
141
145
|
req = [REQUEST, OPCODES[multi? ? :deleteq : :delete], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:delete])
|
142
146
|
write(req)
|
143
147
|
generic_response unless multi?
|
144
148
|
end
|
145
149
|
|
150
|
+
def flush(ttl)
|
151
|
+
req = [REQUEST, OPCODES[:flush], 0, 4, 0, 0, 4, 0, 0, 0].pack(FORMAT[:flush])
|
152
|
+
write(req)
|
153
|
+
generic_response
|
154
|
+
end
|
155
|
+
|
146
156
|
def decr(key, count, ttl, default)
|
147
157
|
expiry = default ? ttl : 0xFFFFFFFF
|
148
158
|
default ||= 0
|
@@ -173,16 +183,16 @@ module Dalli
|
|
173
183
|
multi_response
|
174
184
|
end
|
175
185
|
|
176
|
-
def
|
177
|
-
req = [REQUEST, OPCODES[:
|
186
|
+
def append(key, value)
|
187
|
+
req = [REQUEST, OPCODES[:append], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[:append])
|
178
188
|
write(req)
|
179
189
|
generic_response
|
180
190
|
end
|
181
191
|
|
182
|
-
def
|
183
|
-
req = [REQUEST, OPCODES[
|
192
|
+
def prepend(key, value)
|
193
|
+
req = [REQUEST, OPCODES[:prepend], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[:prepend])
|
184
194
|
write(req)
|
185
|
-
generic_response
|
195
|
+
generic_response
|
186
196
|
end
|
187
197
|
|
188
198
|
def stats(info='')
|
@@ -209,6 +219,44 @@ module Dalli
|
|
209
219
|
generic_response
|
210
220
|
end
|
211
221
|
|
222
|
+
COMPRESSION_MIN_SIZE = 100
|
223
|
+
|
224
|
+
# http://www.hjp.at/zettel/m/memcached_flags.rxml
|
225
|
+
# Looks like most clients use bit 0 to indicate native language serialization
|
226
|
+
# and bit 1 to indicate gzip compression.
|
227
|
+
FLAG_MARSHALLED = 0x1
|
228
|
+
FLAG_COMPRESSED = 0x2
|
229
|
+
|
230
|
+
def serialize(value, options=nil)
|
231
|
+
marshalled = false
|
232
|
+
value = unless options && options[:raw]
|
233
|
+
marshalled = true
|
234
|
+
Marshal.dump(value)
|
235
|
+
else
|
236
|
+
value.to_s
|
237
|
+
end
|
238
|
+
compressed = false
|
239
|
+
if @options[:compression] && value.bytesize >= COMPRESSION_MIN_SIZE
|
240
|
+
value = Zlib::Deflate.deflate(value)
|
241
|
+
compressed = true
|
242
|
+
end
|
243
|
+
raise Dalli::DalliError, "Value too large, memcached can only store 1MB of data per key" if value.bytesize > ONE_MB
|
244
|
+
flags = 0
|
245
|
+
flags |= FLAG_COMPRESSED if compressed
|
246
|
+
flags |= FLAG_MARSHALLED if marshalled
|
247
|
+
[value, flags]
|
248
|
+
end
|
249
|
+
|
250
|
+
def deserialize(value, flags)
|
251
|
+
value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
|
252
|
+
value = Marshal.load(value) if (flags & FLAG_MARSHALLED) != 0
|
253
|
+
value
|
254
|
+
rescue TypeError
|
255
|
+
raise DalliError, "Unable to unmarshal value: '#{value}'"
|
256
|
+
rescue Zlib::Error
|
257
|
+
raise DalliError, "Unable to uncompress value: '#{value}'"
|
258
|
+
end
|
259
|
+
|
212
260
|
def cas_response
|
213
261
|
header = read(24)
|
214
262
|
raise Dalli::NetworkError, 'No response' if !header
|
@@ -219,12 +267,18 @@ module Dalli
|
|
219
267
|
elsif status != 0
|
220
268
|
raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
|
221
269
|
elsif data
|
222
|
-
|
270
|
+
flags = data[0...extras].unpack('N')[0]
|
271
|
+
value = data[extras..-1]
|
272
|
+
data = deserialize(value, flags)
|
223
273
|
end
|
224
274
|
[data, cas]
|
225
275
|
end
|
226
276
|
|
227
|
-
|
277
|
+
CAS_HEADER = '@4CCnNNQ'
|
278
|
+
NORMAL_HEADER = '@4CCnN'
|
279
|
+
KV_HEADER = '@2n@6nN'
|
280
|
+
|
281
|
+
def generic_response(unpack=false)
|
228
282
|
header = read(24)
|
229
283
|
raise Dalli::NetworkError, 'No response' if !header
|
230
284
|
(extras, type, status, count) = header.unpack(NORMAL_HEADER)
|
@@ -236,7 +290,9 @@ module Dalli
|
|
236
290
|
elsif status != 0
|
237
291
|
raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
|
238
292
|
elsif data
|
239
|
-
|
293
|
+
flags = data[0...extras].unpack('N')[0]
|
294
|
+
value = data[extras..-1]
|
295
|
+
unpack ? deserialize(value, flags) : value
|
240
296
|
else
|
241
297
|
true
|
242
298
|
end
|
@@ -262,10 +318,10 @@ module Dalli
|
|
262
318
|
raise Dalli::NetworkError, 'No response' if !header
|
263
319
|
(key_length, status, body_length) = header.unpack(KV_HEADER)
|
264
320
|
return hash if key_length == 0
|
265
|
-
read(4)
|
321
|
+
flags = read(4).unpack('N')[0]
|
266
322
|
key = read(key_length)
|
267
323
|
value = read(body_length - key_length - 4) if body_length - key_length - 4 > 0
|
268
|
-
hash[key] = value
|
324
|
+
hash[key] = deserialize(value, flags)
|
269
325
|
end
|
270
326
|
end
|
271
327
|
|
@@ -278,24 +334,26 @@ module Dalli
|
|
278
334
|
end
|
279
335
|
|
280
336
|
# All this ugly code to ensure proper Socket connect timeout
|
281
|
-
addr = Socket.getaddrinfo(self.hostname, nil)
|
282
|
-
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
283
337
|
begin
|
284
|
-
|
285
|
-
|
286
|
-
resp = IO.select(nil, [sock], nil, TIMEOUT)
|
338
|
+
addr = Socket.getaddrinfo(self.hostname, nil)
|
339
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
287
340
|
begin
|
288
341
|
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
289
|
-
rescue Errno::
|
290
|
-
|
291
|
-
|
292
|
-
|
342
|
+
rescue Errno::EINPROGRESS
|
343
|
+
resp = IO.select(nil, [sock], nil, TIMEOUT)
|
344
|
+
begin
|
345
|
+
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
346
|
+
rescue Errno::EISCONN
|
347
|
+
end
|
293
348
|
end
|
349
|
+
rescue
|
350
|
+
down!(true)
|
294
351
|
end
|
295
352
|
# end ugly code
|
296
353
|
|
297
354
|
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
298
355
|
sasl_authentication(sock) if Dalli::Server.need_auth?
|
356
|
+
up!
|
299
357
|
sock
|
300
358
|
end
|
301
359
|
end
|
@@ -303,9 +361,8 @@ module Dalli
|
|
303
361
|
def write(bytes)
|
304
362
|
begin
|
305
363
|
connection.write(bytes)
|
306
|
-
rescue
|
307
|
-
down!
|
308
|
-
raise Dalli::NetworkError, $!.class.name
|
364
|
+
rescue SystemCallError
|
365
|
+
down!(true)
|
309
366
|
end
|
310
367
|
end
|
311
368
|
|
@@ -325,9 +382,8 @@ module Dalli
|
|
325
382
|
end
|
326
383
|
end
|
327
384
|
value
|
328
|
-
rescue
|
329
|
-
down!
|
330
|
-
raise Dalli::NetworkError, "#{$!.class.name}: #{$!.message}"
|
385
|
+
rescue SystemCallError, Timeout::Error, EOFError
|
386
|
+
down!(true)
|
331
387
|
end
|
332
388
|
end
|
333
389
|
|
@@ -339,10 +395,6 @@ module Dalli
|
|
339
395
|
(a << 32) | b
|
340
396
|
end
|
341
397
|
|
342
|
-
CAS_HEADER = '@4CCnNNQ'
|
343
|
-
NORMAL_HEADER = '@4CCnN'
|
344
|
-
KV_HEADER = '@2n@6nN'
|
345
|
-
|
346
398
|
REQUEST = 0x80
|
347
399
|
RESPONSE = 0x81
|
348
400
|
|
@@ -465,4 +517,4 @@ module Dalli
|
|
465
517
|
# raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
|
466
518
|
end
|
467
519
|
end
|
468
|
-
end
|
520
|
+
end
|
data/lib/dalli/version.rb
CHANGED
data/test/test_dalli.rb
CHANGED
@@ -222,10 +222,7 @@ class TestDalli < Test::Unit::TestCase
|
|
222
222
|
assert_equal true, dc.prepend('456', '0')
|
223
223
|
assert_equal true, dc.append('456', '9')
|
224
224
|
assert_equal '0xyz9', dc.get('456', :raw => true)
|
225
|
-
|
226
|
-
assert_raises Dalli::DalliError do
|
227
|
-
assert_equal '0xyz9', dc.get('456')
|
228
|
-
end
|
225
|
+
assert_equal '0xyz9', dc.get('456')
|
229
226
|
|
230
227
|
assert_equal false, dc.append('nonexist', 'abc')
|
231
228
|
assert_equal false, dc.prepend('nonexist', 'abc')
|
@@ -323,7 +320,6 @@ class TestDalli < Test::Unit::TestCase
|
|
323
320
|
end
|
324
321
|
end
|
325
322
|
|
326
|
-
|
327
323
|
should "handle namespaced keys" do
|
328
324
|
memcached do |dc|
|
329
325
|
dc = Dalli::Client.new('localhost:19122', :namespace => 'a')
|
@@ -335,6 +331,20 @@ class TestDalli < Test::Unit::TestCase
|
|
335
331
|
end
|
336
332
|
end
|
337
333
|
|
334
|
+
context 'with compression' do
|
335
|
+
should 'allow large values' do
|
336
|
+
memcached do |dc|
|
337
|
+
dalli = Dalli::Client.new(dc.instance_variable_get(:@servers), :compression => true)
|
338
|
+
|
339
|
+
value = "0"*1024*1024
|
340
|
+
assert_raise Dalli::DalliError, /too large/ do
|
341
|
+
dc.set('verylarge', value)
|
342
|
+
end
|
343
|
+
dalli.set('verylarge', value)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
338
348
|
context 'without authentication credentials' do
|
339
349
|
setup do
|
340
350
|
ENV['MEMCACHE_USERNAME'] = 'testuser'
|
@@ -382,5 +392,44 @@ class TestDalli < Test::Unit::TestCase
|
|
382
392
|
end
|
383
393
|
end
|
384
394
|
|
395
|
+
context 'in low memory conditions' do
|
396
|
+
|
397
|
+
should 'handle error response correctly' do
|
398
|
+
memcached(19125, '-m 1 -M') do |dc|
|
399
|
+
failed = false
|
400
|
+
value = "1234567890"*100
|
401
|
+
1_000.times do |idx|
|
402
|
+
begin
|
403
|
+
assert_equal true, dc.set(idx, value)
|
404
|
+
rescue Dalli::DalliError
|
405
|
+
failed = true
|
406
|
+
assert((800..900).include?(idx), "unexpected failure on iteration #{idx}")
|
407
|
+
break
|
408
|
+
end
|
409
|
+
end
|
410
|
+
assert failed, 'did not fail under low memory conditions'
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
should 'fit more values with compression' do
|
415
|
+
memcached(19126, '-m 1 -M') do |dc|
|
416
|
+
dalli = Dalli::Client.new('localhost:19126', :compression => true)
|
417
|
+
failed = false
|
418
|
+
value = "1234567890"*1000
|
419
|
+
10_000.times do |idx|
|
420
|
+
begin
|
421
|
+
assert_equal true, dalli.set(idx, value)
|
422
|
+
rescue Dalli::DalliError
|
423
|
+
failed = true
|
424
|
+
assert((6000..7000).include?(idx), "unexpected failure on iteration #{idx}")
|
425
|
+
break
|
426
|
+
end
|
427
|
+
end
|
428
|
+
assert failed, 'did not fail under low memory conditions'
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|
433
|
+
|
385
434
|
end
|
386
435
|
end
|
data/test/test_encoding.rb
CHANGED
data/test/test_network.rb
CHANGED
@@ -18,7 +18,7 @@ class TestNetwork < Test::Unit::TestCase
|
|
18
18
|
|
19
19
|
should 'handle connection reset' do
|
20
20
|
memcached_mock(lambda {|sock| sock.close }) do
|
21
|
-
assert_error Dalli::NetworkError, /
|
21
|
+
assert_error Dalli::NetworkError, /Connection reset|end of file/ do
|
22
22
|
dc = Dalli::Client.new('localhost:19123')
|
23
23
|
dc.get('abc')
|
24
24
|
end
|
@@ -27,7 +27,7 @@ class TestNetwork < Test::Unit::TestCase
|
|
27
27
|
|
28
28
|
should 'handle malformed response' do
|
29
29
|
memcached_mock(lambda {|sock| sock.write('123') }) do
|
30
|
-
assert_error Dalli::NetworkError, /
|
30
|
+
assert_error Dalli::NetworkError, /end of file/ do
|
31
31
|
dc = Dalli::Client.new('localhost:19123')
|
32
32
|
dc.get('abc')
|
33
33
|
end
|
@@ -36,7 +36,7 @@ class TestNetwork < Test::Unit::TestCase
|
|
36
36
|
|
37
37
|
should 'handle connect timeouts' do
|
38
38
|
memcached_mock(lambda {|sock| sleep(0.6); sock.close }, :delayed_start) do
|
39
|
-
assert_error Dalli::NetworkError, /
|
39
|
+
assert_error Dalli::NetworkError, /IO timeout/ do
|
40
40
|
dc = Dalli::Client.new('localhost:19123')
|
41
41
|
dc.get('abc')
|
42
42
|
end
|
@@ -45,7 +45,7 @@ class TestNetwork < Test::Unit::TestCase
|
|
45
45
|
|
46
46
|
should 'handle read timeouts' do
|
47
47
|
memcached_mock(lambda {|sock| sleep(0.6); sock.write('giraffe') }) do
|
48
|
-
assert_error Dalli::NetworkError, /
|
48
|
+
assert_error Dalli::NetworkError, /IO timeout/ do
|
49
49
|
dc = Dalli::Client.new('localhost:19123')
|
50
50
|
dc.get('abc')
|
51
51
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 11
|
8
|
+
- 0
|
9
|
+
version: 0.11.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mike Perham
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-11-03 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -54,8 +54,8 @@ dependencies:
|
|
54
54
|
segments:
|
55
55
|
- 3
|
56
56
|
- 0
|
57
|
-
-
|
58
|
-
version: 3.0.
|
57
|
+
- 1
|
58
|
+
version: 3.0.1
|
59
59
|
type: :development
|
60
60
|
version_requirements: *id003
|
61
61
|
- !ruby/object:Gem::Dependency
|