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 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.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
@@ -4,7 +4,7 @@ require 'dalli/server'
4
4
  require 'dalli/version'
5
5
  require 'dalli/options'
6
6
 
7
- unless String.respond_to?(:bytesize)
7
+ unless ''.respond_to?(:bytesize)
8
8
  class String
9
9
  alias_method :bytesize, :size
10
10
  end
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
- # <pre>
8
- # Dalli::Client.new(['localhost:11211:10', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5'],
9
- # :threadsafe => true, :failover => true)
10
- # </pre>
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
- # :failover - if a server is down, store the value on another server. Default: true.
16
- # :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
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[:multi] = true
38
+ old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
34
39
  yield
35
40
  ensure
36
- Thread.current[:multi] = nil
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=0, options=nil)
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=0, options=nil, &block)
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=0, options=nil)
76
- perform(:set, key, serialize(value, options), ttl)
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=0, options=nil)
80
- perform(:add, key, serialize(value, options), ttl, 0)
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=0, options=nil)
84
- perform(:replace, key, serialize(value, options), ttl)
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=0, default=nil)
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=0, default=nil)
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
- options && options[:raw] ? value.to_s : ::Marshal.dump(value)
174
+ value
175
+ # options && options[:raw] ? value.to_s : ::Marshal.dump(value)
163
176
  end
164
177
 
165
178
  def deserialize(value, options)
166
- options && options[:raw] ? value : ::Marshal.load(value)
167
- rescue TypeError
168
- raise Dalli::DalliError, "Invalid marshalled data in memcached: #{value}"
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("Unable to connect to #{hostname}:#{port}: #{dne.message}")
46
- down!
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[:multi]
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
- raise Dalli::DalliError, "Value too large, memcached can only store 1MB of data per key" if value.bytesize > ONE_MB
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, 0, ttl, key, value].pack(FORMAT[:set])
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 flush(ttl)
121
- req = [REQUEST, OPCODES[:flush], 0, 4, 0, 0, 4, 0, 0, 0].pack(FORMAT[:flush])
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
- def add(key, value, ttl, cas)
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 append(key, value)
135
- req = [REQUEST, OPCODES[:append], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[:append])
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 prepend(key, value)
177
- req = [REQUEST, OPCODES[:prepend], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[:prepend])
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 replace(key, value, ttl)
183
- req = [REQUEST, OPCODES[multi? ? :replaceq : :replace], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, 0, ttl, key, value].pack(FORMAT[:replace])
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 unless multi?
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
- data = data[extras..-1] if extras != 0
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
- def generic_response
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
- extras != 0 ? data[extras..-1] : data
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
- sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
285
- rescue Errno::EINPROGRESS
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::EISCONN
290
- ;
291
- rescue
292
- raise Dalli::NetworkError, "#{self.hostname}:#{self.port} is currently down: #{$!.message}"
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 Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF
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 Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL, Timeout::Error, EOFError
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
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '0.10.1'
2
+ VERSION = '0.11.0'
3
3
  end
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
@@ -25,7 +25,7 @@ class TestEncoding < Test::Unit::TestCase
25
25
  key = 'foo'
26
26
  assert dc.set(key, 'bar', 1)
27
27
  assert_equal 'bar', dc.get(key)
28
- sleep 1.1
28
+ sleep 2
29
29
  assert_equal nil, dc.get(key)
30
30
  end
31
31
  end
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, /ECONNRESET|EOFError/ do
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, /EOFError/ do
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, /Timeout::Error/ do
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, /Timeout::Error/ do
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
- - 10
8
- - 1
9
- version: 0.10.1
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-10-22 00:00:00 -07:00
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
- - 0
58
- version: 3.0.0
57
+ - 1
58
+ version: 3.0.1
59
59
  type: :development
60
60
  version_requirements: *id003
61
61
  - !ruby/object:Gem::Dependency