dalli 2.6.4 → 2.7.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.
- checksums.yaml +7 -0
- data/Gemfile +1 -0
- data/History.md +26 -2
- data/LICENSE +1 -1
- data/README.md +21 -9
- data/dalli.gemspec +1 -1
- data/lib/active_support/cache/dalli_store.rb +79 -19
- data/lib/dalli/cas/client.rb +58 -0
- data/lib/dalli/client.rb +148 -108
- data/lib/dalli/server.rb +45 -28
- data/lib/dalli/version.rb +1 -1
- data/test/benchmark_test.rb +1 -1
- data/test/helper.rb +15 -0
- data/test/memcached_mock.rb +11 -1
- data/test/sasldb +1 -0
- data/test/test_active_support.rb +47 -9
- data/test/test_cas_client.rb +107 -0
- data/test/test_dalli.rb +70 -22
- data/test/test_sasl.rb +4 -4
- metadata +18 -21
data/lib/dalli/server.rb
CHANGED
@@ -55,7 +55,7 @@ module Dalli
|
|
55
55
|
# Chokepoint method for instrumentation
|
56
56
|
def request(op, *args)
|
57
57
|
verify_state
|
58
|
-
raise Dalli::NetworkError, "#{hostname}:#{port} is down: #{@error} #{@msg}" unless alive?
|
58
|
+
raise Dalli::NetworkError, "#{hostname}:#{port} is down: #{@error} #{@msg}. If you are sure it is running, ensure memcached version is > 1.4." unless alive?
|
59
59
|
begin
|
60
60
|
send(op, *args)
|
61
61
|
rescue Dalli::NetworkError
|
@@ -146,7 +146,7 @@ module Dalli
|
|
146
146
|
|
147
147
|
while buf.bytesize - pos >= 24
|
148
148
|
header = buf.slice(pos, 24)
|
149
|
-
(key_length, _, body_length) = header.unpack(KV_HEADER)
|
149
|
+
(key_length, _, body_length, cas) = header.unpack(KV_HEADER)
|
150
150
|
|
151
151
|
if key_length == 0
|
152
152
|
# all done!
|
@@ -163,7 +163,7 @@ module Dalli
|
|
163
163
|
pos = pos + 24 + body_length
|
164
164
|
|
165
165
|
begin
|
166
|
-
values[key] = deserialize(value, flags)
|
166
|
+
values[key] = [deserialize(value, flags), cas]
|
167
167
|
rescue DalliError
|
168
168
|
end
|
169
169
|
|
@@ -175,8 +175,8 @@ module Dalli
|
|
175
175
|
@position = pos
|
176
176
|
|
177
177
|
values
|
178
|
-
rescue SystemCallError, Timeout::Error, EOFError
|
179
|
-
failure!
|
178
|
+
rescue SystemCallError, Timeout::Error, EOFError => e
|
179
|
+
failure!(e)
|
180
180
|
end
|
181
181
|
|
182
182
|
# Abort an earlier #multi_response_start. Used to signal an external
|
@@ -188,7 +188,7 @@ module Dalli
|
|
188
188
|
@multi_buffer = nil
|
189
189
|
@position = nil
|
190
190
|
@inprogress = false
|
191
|
-
failure!
|
191
|
+
failure!(RuntimeError.new('External timeout'))
|
192
192
|
rescue NetworkError
|
193
193
|
true
|
194
194
|
end
|
@@ -198,12 +198,13 @@ module Dalli
|
|
198
198
|
private
|
199
199
|
|
200
200
|
def verify_state
|
201
|
-
failure! if @inprogress
|
202
|
-
failure! if @pid && @pid != Process.pid
|
201
|
+
failure!(RuntimeError.new('Already writing to socket')) if @inprogress
|
202
|
+
failure!(RuntimeError.new('Cannot share client between multiple processes')) if @pid && @pid != Process.pid
|
203
203
|
end
|
204
204
|
|
205
|
-
def failure!
|
206
|
-
|
205
|
+
def failure!(exception)
|
206
|
+
message = "#{hostname}:#{port} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
|
207
|
+
Dalli.logger.info { message }
|
207
208
|
|
208
209
|
@fail_count += 1
|
209
210
|
if @fail_count >= options[:socket_max_failures]
|
@@ -271,7 +272,7 @@ module Dalli
|
|
271
272
|
guard_max_value(key, value) do
|
272
273
|
req = [REQUEST, OPCODES[multi? ? :setq : :set], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, flags, ttl, key, value].pack(FORMAT[:set])
|
273
274
|
write(req)
|
274
|
-
|
275
|
+
cas_response unless multi?
|
275
276
|
end
|
276
277
|
end
|
277
278
|
|
@@ -281,22 +282,22 @@ module Dalli
|
|
281
282
|
guard_max_value(key, value) do
|
282
283
|
req = [REQUEST, OPCODES[multi? ? :addq : :add], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, flags, ttl, key, value].pack(FORMAT[:add])
|
283
284
|
write(req)
|
284
|
-
|
285
|
+
cas_response unless multi?
|
285
286
|
end
|
286
287
|
end
|
287
288
|
|
288
|
-
def replace(key, value, ttl, options)
|
289
|
+
def replace(key, value, ttl, cas, options)
|
289
290
|
(value, flags) = serialize(key, value, options)
|
290
291
|
|
291
292
|
guard_max_value(key, value) do
|
292
|
-
req = [REQUEST, OPCODES[multi? ? :replaceq : :replace], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0,
|
293
|
+
req = [REQUEST, OPCODES[multi? ? :replaceq : :replace], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, flags, ttl, key, value].pack(FORMAT[:replace])
|
293
294
|
write(req)
|
294
|
-
|
295
|
+
cas_response unless multi?
|
295
296
|
end
|
296
297
|
end
|
297
298
|
|
298
|
-
def delete(key)
|
299
|
-
req = [REQUEST, OPCODES[multi? ? :deleteq : :delete], key.bytesize, 0, 0, 0, key.bytesize, 0,
|
299
|
+
def delete(key, cas)
|
300
|
+
req = [REQUEST, OPCODES[multi? ? :deleteq : :delete], key.bytesize, 0, 0, 0, key.bytesize, 0, cas, key].pack(FORMAT[:delete])
|
300
301
|
write(req)
|
301
302
|
generic_response unless multi?
|
302
303
|
end
|
@@ -368,7 +369,7 @@ module Dalli
|
|
368
369
|
def cas(key)
|
369
370
|
req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
|
370
371
|
write(req)
|
371
|
-
|
372
|
+
data_cas_response
|
372
373
|
end
|
373
374
|
|
374
375
|
def version
|
@@ -432,7 +433,7 @@ module Dalli
|
|
432
433
|
raise UnmarshalError, "Unable to uncompress value: #{$!.message}"
|
433
434
|
end
|
434
435
|
|
435
|
-
def
|
436
|
+
def data_cas_response
|
436
437
|
header = read(24)
|
437
438
|
raise Dalli::NetworkError, 'No response' if !header
|
438
439
|
(extras, _, status, count, _, cas) = header.unpack(CAS_HEADER)
|
@@ -451,7 +452,7 @@ module Dalli
|
|
451
452
|
|
452
453
|
CAS_HEADER = '@4CCnNNQ'
|
453
454
|
NORMAL_HEADER = '@4CCnN'
|
454
|
-
KV_HEADER = '@2n@6nN'
|
455
|
+
KV_HEADER = '@2n@6nN@16Q'
|
455
456
|
|
456
457
|
def guard_max_value(key, value)
|
457
458
|
if value.bytesize <= @options[:value_max_bytes]
|
@@ -482,12 +483,28 @@ module Dalli
|
|
482
483
|
end
|
483
484
|
end
|
484
485
|
|
486
|
+
def cas_response
|
487
|
+
header = read(24)
|
488
|
+
raise Dalli::NetworkError, 'No response' if !header
|
489
|
+
(_, _, status, count, _, cas) = header.unpack(CAS_HEADER)
|
490
|
+
read(count) if count > 0 # this is potential data that we don't care about
|
491
|
+
if status == 1
|
492
|
+
nil
|
493
|
+
elsif status == 2 || status == 5
|
494
|
+
false # Not stored, normal status for add operation
|
495
|
+
elsif status != 0
|
496
|
+
raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
|
497
|
+
else
|
498
|
+
cas
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
485
502
|
def keyvalue_response
|
486
503
|
hash = {}
|
487
504
|
loop do
|
488
505
|
header = read(24)
|
489
506
|
raise Dalli::NetworkError, 'No response' if !header
|
490
|
-
(key_length, _, body_length) = header.unpack(KV_HEADER)
|
507
|
+
(key_length, _, body_length, _) = header.unpack(KV_HEADER)
|
491
508
|
return hash if key_length == 0
|
492
509
|
key = read(key_length)
|
493
510
|
value = read(body_length - key_length) if body_length - key_length > 0
|
@@ -500,7 +517,7 @@ module Dalli
|
|
500
517
|
loop do
|
501
518
|
header = read(24)
|
502
519
|
raise Dalli::NetworkError, 'No response' if !header
|
503
|
-
(key_length, _, body_length) = header.unpack(KV_HEADER)
|
520
|
+
(key_length, _, body_length, _) = header.unpack(KV_HEADER)
|
504
521
|
return hash if key_length == 0
|
505
522
|
flags = read(4).unpack('N')[0]
|
506
523
|
key = read(key_length)
|
@@ -515,8 +532,8 @@ module Dalli
|
|
515
532
|
result = @sock.write(bytes)
|
516
533
|
@inprogress = false
|
517
534
|
result
|
518
|
-
rescue SystemCallError, Timeout::Error
|
519
|
-
failure!
|
535
|
+
rescue SystemCallError, Timeout::Error => e
|
536
|
+
failure!(e)
|
520
537
|
end
|
521
538
|
end
|
522
539
|
|
@@ -526,8 +543,8 @@ module Dalli
|
|
526
543
|
data = @sock.readfull(count)
|
527
544
|
@inprogress = false
|
528
545
|
data
|
529
|
-
rescue SystemCallError, Timeout::Error, EOFError
|
530
|
-
failure!
|
546
|
+
rescue SystemCallError, Timeout::Error, EOFError => e
|
547
|
+
failure!(e)
|
531
548
|
end
|
532
549
|
end
|
533
550
|
|
@@ -542,9 +559,9 @@ module Dalli
|
|
542
559
|
up!
|
543
560
|
rescue Dalli::DalliError # SASL auth failure
|
544
561
|
raise
|
545
|
-
rescue SystemCallError, Timeout::Error, EOFError, SocketError
|
562
|
+
rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
|
546
563
|
# SocketError = DNS resolution failure
|
547
|
-
failure!
|
564
|
+
failure!(e)
|
548
565
|
end
|
549
566
|
end
|
550
567
|
|
data/lib/dalli/version.rb
CHANGED
data/test/benchmark_test.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -7,6 +7,8 @@ require 'minitest/autorun'
|
|
7
7
|
require 'mocha/setup'
|
8
8
|
require 'memcached_mock'
|
9
9
|
|
10
|
+
ENV['MEMCACHED_SASL_PWDB'] = "#{File.dirname(__FILE__)}/sasldb"
|
11
|
+
|
10
12
|
WANT_RAILS_VERSION = ENV['RAILS_VERSION'] || '>= 3.0.0'
|
11
13
|
gem 'rails', WANT_RAILS_VERSION
|
12
14
|
require 'rails'
|
@@ -26,6 +28,19 @@ class MiniTest::Spec
|
|
26
28
|
assert_match(regexp, ex.message, "#{ex.class.name}: #{ex.message}\n#{ex.backtrace.join("\n\t")}")
|
27
29
|
end
|
28
30
|
|
31
|
+
def op_cas_succeeds(rsp)
|
32
|
+
rsp.is_a?(Integer) && rsp > 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def op_replace_succeeds(rsp)
|
36
|
+
rsp.is_a?(Integer) && rsp > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
# add and set must have the same return value because of DalliStore#write_entry
|
40
|
+
def op_addset_succeeds(rsp)
|
41
|
+
rsp.is_a?(Integer) && rsp > 0
|
42
|
+
end
|
43
|
+
|
29
44
|
def with_activesupport
|
30
45
|
require 'active_support/all'
|
31
46
|
require 'active_support/cache/dalli_store'
|
data/test/memcached_mock.rb
CHANGED
@@ -66,6 +66,17 @@ module MemcachedMock
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def memcached(port=19122, args='', options={})
|
69
|
+
memcached_server(port, args)
|
70
|
+
yield Dalli::Client.new(["localhost:#{port}", "127.0.0.1:#{port}"], options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def memcached_cas(port=19122, args='', options={})
|
74
|
+
memcached_server(port, args)
|
75
|
+
require 'dalli/cas/client'
|
76
|
+
yield Dalli::Client.new(["localhost:#{port}", "127.0.0.1:#{port}"], options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def memcached_server(port=19122, args='')
|
69
80
|
Memcached.path ||= find_memcached
|
70
81
|
|
71
82
|
cmd = "#{Memcached.path}memcached #{args} -p #{port}"
|
@@ -83,7 +94,6 @@ module MemcachedMock
|
|
83
94
|
sleep 0.1
|
84
95
|
pid
|
85
96
|
end
|
86
|
-
yield Dalli::Client.new(["localhost:#{port}", "127.0.0.1:#{port}"], options)
|
87
97
|
end
|
88
98
|
|
89
99
|
def supports_fork?
|
data/test/sasldb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
testuser:testtest:::::::
|
data/test/test_active_support.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'helper'
|
3
|
+
require 'connection_pool'
|
3
4
|
|
4
5
|
class MockUser
|
5
6
|
def cache_key
|
@@ -18,7 +19,7 @@ describe 'ActiveSupport' do
|
|
18
19
|
it 'allow mute and silence' do
|
19
20
|
@dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:19122')
|
20
21
|
@dalli.mute do
|
21
|
-
|
22
|
+
assert op_addset_succeeds(@dalli.write('foo', 'bar', nil))
|
22
23
|
assert_equal 'bar', @dalli.read('foo', nil)
|
23
24
|
end
|
24
25
|
refute @dalli.silence?
|
@@ -28,7 +29,7 @@ describe 'ActiveSupport' do
|
|
28
29
|
|
29
30
|
it 'handle nil options' do
|
30
31
|
@dalli = ActiveSupport::Cache.lookup_store(:dalli_store, 'localhost:19122')
|
31
|
-
|
32
|
+
assert op_addset_succeeds(@dalli.write('foo', 'bar', nil))
|
32
33
|
assert_equal 'bar', @dalli.read('foo', nil)
|
33
34
|
assert_equal 18, @dalli.fetch('lkjsadlfk', nil) { 18 }
|
34
35
|
assert_equal 18, @dalli.fetch('lkjsadlfk', nil) { 18 }
|
@@ -147,6 +148,26 @@ describe 'ActiveSupport' do
|
|
147
148
|
end
|
148
149
|
end
|
149
150
|
|
151
|
+
it 'supports fetch_multi' do
|
152
|
+
with_activesupport do
|
153
|
+
memcached do
|
154
|
+
connect
|
155
|
+
|
156
|
+
x = rand_key.to_s
|
157
|
+
y = rand_key
|
158
|
+
hash = { x => 'ABC', y => 'DEF' }
|
159
|
+
|
160
|
+
@dalli.write(y, '123')
|
161
|
+
|
162
|
+
results = @dalli.fetch_multi(x, y) { |key| hash[key] }
|
163
|
+
|
164
|
+
assert_equal({ x => 'ABC', y => '123' }, results)
|
165
|
+
assert_equal('ABC', @dalli.read(x))
|
166
|
+
assert_equal('123', @dalli.read(y))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
150
171
|
it 'support read, write and delete' do
|
151
172
|
with_activesupport do
|
152
173
|
memcached do
|
@@ -154,7 +175,7 @@ describe 'ActiveSupport' do
|
|
154
175
|
y = rand_key
|
155
176
|
assert_nil @dalli.read(y)
|
156
177
|
dres = @dalli.write(y, 123)
|
157
|
-
|
178
|
+
assert op_addset_succeeds(dres)
|
158
179
|
|
159
180
|
dres = @dalli.read(y)
|
160
181
|
assert_equal 123, dres
|
@@ -164,7 +185,7 @@ describe 'ActiveSupport' do
|
|
164
185
|
|
165
186
|
user = MockUser.new
|
166
187
|
dres = @dalli.write(user.cache_key, "foo")
|
167
|
-
|
188
|
+
assert op_addset_succeeds(dres)
|
168
189
|
|
169
190
|
dres = @dalli.read(user)
|
170
191
|
assert_equal "foo", dres
|
@@ -236,7 +257,7 @@ describe 'ActiveSupport' do
|
|
236
257
|
with_activesupport do
|
237
258
|
memcached do
|
238
259
|
connect
|
239
|
-
|
260
|
+
assert op_addset_succeeds(@dalli.write('counter', 0, :raw => true))
|
240
261
|
assert_equal 1, @dalli.increment('counter')
|
241
262
|
assert_equal 2, @dalli.increment('counter')
|
242
263
|
assert_equal 1, @dalli.decrement('counter')
|
@@ -261,7 +282,7 @@ describe 'ActiveSupport' do
|
|
261
282
|
assert_equal nil, @dalli.read('counterZ2')
|
262
283
|
|
263
284
|
user = MockUser.new
|
264
|
-
|
285
|
+
assert op_addset_succeeds(@dalli.write(user, 0, :raw => true))
|
265
286
|
assert_equal 1, @dalli.increment(user)
|
266
287
|
assert_equal 2, @dalli.increment(user)
|
267
288
|
assert_equal 1, @dalli.decrement(user)
|
@@ -338,7 +359,7 @@ describe 'ActiveSupport' do
|
|
338
359
|
connect
|
339
360
|
key = "fooƒ"
|
340
361
|
value = 'bafƒ'
|
341
|
-
|
362
|
+
assert op_addset_succeeds(@dalli.write(key, value))
|
342
363
|
assert_equal value, @dalli.read(key)
|
343
364
|
end
|
344
365
|
end
|
@@ -354,13 +375,30 @@ describe 'ActiveSupport' do
|
|
354
375
|
end
|
355
376
|
end
|
356
377
|
|
378
|
+
it 'supports connection pooling' do
|
379
|
+
with_activesupport do
|
380
|
+
memcached do
|
381
|
+
@dalli = ActiveSupport::Cache::DalliStore.new('localhost:19122', :expires_in => 1, :namespace => 'foo', :compress => true, :pool_size => 3)
|
382
|
+
assert_equal nil, @dalli.read('foo')
|
383
|
+
assert @dalli.write('foo', 1)
|
384
|
+
assert_equal 1, @dalli.fetch('foo') { raise 'boom' }
|
385
|
+
assert_equal true, @dalli.dalli.is_a?(ConnectionPool)
|
386
|
+
assert_equal 1, @dalli.increment('bar')
|
387
|
+
assert_equal 0, @dalli.decrement('bar')
|
388
|
+
assert_equal true, @dalli.delete('bar')
|
389
|
+
assert_equal [true], @dalli.clear
|
390
|
+
assert_equal 1, @dalli.stats.size
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
357
395
|
it 'allow keys to be frozen' do
|
358
396
|
with_activesupport do
|
359
397
|
memcached do
|
360
398
|
connect
|
361
399
|
key = "foo"
|
362
400
|
key.freeze
|
363
|
-
|
401
|
+
assert op_addset_succeeds(@dalli.write(key, "value"))
|
364
402
|
end
|
365
403
|
end
|
366
404
|
end
|
@@ -371,7 +409,7 @@ describe 'ActiveSupport' do
|
|
371
409
|
connect
|
372
410
|
map = { "one" => "one", "two" => "two" }
|
373
411
|
map.each_pair do |k, v|
|
374
|
-
|
412
|
+
assert op_addset_succeeds(@dalli.write(k, v))
|
375
413
|
end
|
376
414
|
assert_equal map, @dalli.read_multi(*(map.keys))
|
377
415
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'memcached_mock'
|
3
|
+
|
4
|
+
describe 'Dalli::Cas::Client' do
|
5
|
+
describe 'using a live server' do
|
6
|
+
it 'supports get with CAS' do
|
7
|
+
memcached_cas do |dc|
|
8
|
+
dc.flush
|
9
|
+
|
10
|
+
expected = { 'blah' => 'blerg!' }
|
11
|
+
get_block_called = false
|
12
|
+
stored_value = stored_cas = nil
|
13
|
+
# Validate call-with-block
|
14
|
+
dc.get_cas('gets_key') do |v, cas|
|
15
|
+
get_block_called = true
|
16
|
+
stored_value = v
|
17
|
+
stored_cas = cas
|
18
|
+
end
|
19
|
+
assert get_block_called
|
20
|
+
assert_nil stored_value
|
21
|
+
|
22
|
+
dc.set('gets_key', expected)
|
23
|
+
|
24
|
+
# Validate call-with-return-value
|
25
|
+
stored_value, stored_cas = dc.get_cas('gets_key')
|
26
|
+
assert_equal stored_value, expected
|
27
|
+
assert(stored_cas != 0)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'supports multi-get with CAS' do
|
32
|
+
memcached_cas do |dc|
|
33
|
+
dc.close
|
34
|
+
dc.flush
|
35
|
+
|
36
|
+
expected_hash = {'a' => 'foo', 'b' => 123}
|
37
|
+
expected_hash.each_pair do |k, v|
|
38
|
+
dc.set(k, v)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Invocation without block
|
42
|
+
resp = dc.get_multi_cas(%w(a b c d e f))
|
43
|
+
resp.each_pair do |k, data|
|
44
|
+
value, cas = [data.first, data.second]
|
45
|
+
assert_equal expected_hash[k], value
|
46
|
+
assert(cas && cas != 0)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Invocation with block
|
50
|
+
dc.get_multi_cas(%w(a b c d e f)) do |k, data|
|
51
|
+
value, cas = [data.first, data.second]
|
52
|
+
assert_equal expected_hash[k], value
|
53
|
+
assert(cas && cas != 0)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'supports replace-with-CAS operation' do
|
59
|
+
memcached_cas do |dc|
|
60
|
+
dc.flush
|
61
|
+
cas = dc.set('key', 'value')
|
62
|
+
|
63
|
+
# Accepts CAS, replaces, and returns new CAS
|
64
|
+
cas = dc.replace_cas('key', 'value2', cas)
|
65
|
+
assert cas.is_a?(Integer)
|
66
|
+
|
67
|
+
assert_equal 'value2', dc.get('key')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'supports delete with CAS' do
|
72
|
+
memcached_cas do |dc|
|
73
|
+
cas = dc.set('some_key', 'some_value')
|
74
|
+
dc.delete_cas('some_key', cas)
|
75
|
+
assert_nil dc.get('some_key')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'handles CAS round-trip operations' do
|
80
|
+
memcached_cas do |dc|
|
81
|
+
dc.flush
|
82
|
+
|
83
|
+
expected = {'blah' => 'blerg!'}
|
84
|
+
dc.set('some_key', expected)
|
85
|
+
|
86
|
+
value, cas = dc.get_cas('some_key')
|
87
|
+
assert_equal value, expected
|
88
|
+
assert(!cas.nil? && cas != 0)
|
89
|
+
|
90
|
+
# Set operation, first with wrong then with correct CAS
|
91
|
+
expected = {'blah' => 'set succeeded'}
|
92
|
+
assert(dc.set_cas('some_key', expected, cas+1) == false)
|
93
|
+
assert op_addset_succeeds(cas = dc.set_cas('some_key', expected, cas))
|
94
|
+
|
95
|
+
# Replace operation, first with wrong then with correct CAS
|
96
|
+
expected = {'blah' => 'replace succeeded'}
|
97
|
+
assert(dc.replace_cas('some_key', expected, cas+1) == false)
|
98
|
+
assert op_addset_succeeds(cas = dc.replace_cas('some_key', expected, cas))
|
99
|
+
|
100
|
+
# Delete operation, first with wrong then with correct CAS
|
101
|
+
assert(dc.delete_cas('some_key', cas+1) == false)
|
102
|
+
assert dc.delete_cas('some_key', cas)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|