dalli 2.1.0 → 2.2.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,13 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 2.2.0
5
+ =======
6
+
7
+ - Add Rack session with\_lock helper, for Rails 4.0 support [#264]
8
+ - Accept connection string in the form of a URL (e.g., memcached://user:pass@hostname:port) [glenngillen]
9
+ - Add touch operation [#228, uzzz]
10
+
4
11
  2.1.0
5
12
  =======
6
13
 
data/README.md CHANGED
@@ -49,6 +49,9 @@ Remember, Dalli **requires** memcached 1.4+. You can check the version with `mem
49
49
 
50
50
  brew install memcached
51
51
 
52
+ On Ubuntu you can install it by running:
53
+
54
+ apt-get install memcached
52
55
 
53
56
  You can verify your installation using this piece of code:
54
57
 
data/Rakefile CHANGED
@@ -2,6 +2,8 @@ require 'rake/testtask'
2
2
  Rake::TestTask.new(:test) do |test|
3
3
  test.libs << 'test'
4
4
  test.pattern = 'test/**/test_*.rb'
5
+ test.warning = true
6
+ test.verbose = true
5
7
  end
6
8
 
7
9
  Rake::TestTask.new(:bench) do |test|
@@ -94,6 +94,7 @@ module ActiveSupport
94
94
  options ||= {}
95
95
  name = expanded_key name
96
96
 
97
+ log(:exist, name, options)
97
98
  !read_entry(name, options).nil?
98
99
  end
99
100
 
@@ -101,18 +102,19 @@ module ActiveSupport
101
102
  options ||= {}
102
103
  name = expanded_key name
103
104
 
105
+ log(:delete, name, options)
104
106
  delete_entry(name, options)
105
107
  end
106
108
 
107
109
  # Reads multiple keys from the cache using a single call to the
108
110
  # servers for all keys. Keys must be Strings.
109
111
  def read_multi(*names)
110
- options = names.extract_options!
112
+ names.extract_options!
111
113
  names = names.flatten
112
114
  mapping = names.inject({}) { |memo, name| memo[escape(expanded_key(name))] = name; memo }
113
115
  instrument(:read_multi, names) do
114
116
  results = @data.get_multi(mapping.keys)
115
- results.inject({}) do |memo, (inner, value)|
117
+ results.inject({}) do |memo, (inner, _)|
116
118
  entry = results[inner]
117
119
  # NB Backwards data compatibility, to be removed at some point
118
120
  memo[mapping[inner]] = (entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry)
@@ -162,7 +164,9 @@ module ActiveSupport
162
164
  # Clear the entire cache on all memcached servers. This method should
163
165
  # be used with care when using a shared cache.
164
166
  def clear(options=nil)
165
- @data.flush_all
167
+ instrument(:clear, 'flushing all keys') do
168
+ @data.flush_all
169
+ end
166
170
  rescue Dalli::DalliError => e
167
171
  logger.error("DalliError: #{e.message}") if logger
168
172
  raise if @raise_errors
@@ -234,7 +238,7 @@ module ActiveSupport
234
238
 
235
239
  def escape(key)
236
240
  key = key.to_s.dup
237
- key = key.force_encoding("BINARY") if key.encoding_aware?
241
+ key = key.force_encoding("BINARY") if key.respond_to?(:encode)
238
242
  key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
239
243
  key
240
244
  end
data/lib/dalli.rb CHANGED
@@ -4,7 +4,7 @@ require 'dalli/server'
4
4
  require 'dalli/socket'
5
5
  require 'dalli/version'
6
6
  require 'dalli/options'
7
- require 'dalli/railtie' if defined?(Rails)
7
+ require 'dalli/railtie' if defined?(::Rails::Railtie)
8
8
 
9
9
  module Dalli
10
10
  # generic error
data/lib/dalli/client.rb CHANGED
@@ -12,9 +12,8 @@ module Dalli
12
12
  # :threadsafe => true, :failover => true, :expires_in => 300)
13
13
  #
14
14
  # servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
15
- # Both weight and port are optional. If you pass in nil, Dalli will default to 'localhost:11211'.
16
- # Note that the <tt>MEMCACHE_SERVERS</tt> environment variable will override the servers parameter for use
17
- # in managed environments like Heroku.
15
+ # Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
16
+ # environment variable or default to 'localhost:11211' if it is not present.
18
17
  #
19
18
  # Options:
20
19
  # - :namespace - prepend each key with this value to provide simple namespacing.
@@ -25,7 +24,7 @@ module Dalli
25
24
  # sending them to memcached.
26
25
  #
27
26
  def initialize(servers=nil, options={})
28
- @servers = env_servers || servers || '127.0.0.1:11211'
27
+ @servers = servers || env_servers || '127.0.0.1:11211'
29
28
  @options = normalize_options(options)
30
29
  @ring = nil
31
30
  end
@@ -198,6 +197,16 @@ module Dalli
198
197
  perform(:decr, key, amt.to_i, ttl, default)
199
198
  end
200
199
 
200
+ ##
201
+ # Touch updates expiration time for a given key.
202
+ #
203
+ # Returns true if key exists, otherwise nil.
204
+ def touch(key, ttl=nil)
205
+ ttl ||= @options[:expires_in].to_i
206
+ resp = perform(:touch, key, ttl)
207
+ resp.nil? ? nil : true
208
+ end
209
+
201
210
  ##
202
211
  # Collect the stats for each server.
203
212
  # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
@@ -233,7 +242,14 @@ module Dalli
233
242
  def ring
234
243
  @ring ||= Dalli::Ring.new(
235
244
  Array(@servers).map do |s|
236
- Dalli::Server.new(s, @options)
245
+ server_options = {}
246
+ if s =~ %r{\Amemcached://}
247
+ uri = URI.parse(s)
248
+ server_options[:username] = uri.user
249
+ server_options[:password] = uri.password
250
+ s = "#{uri.host}:#{uri.port}"
251
+ end
252
+ Dalli::Server.new(s, @options.merge(server_options))
237
253
  end, @options
238
254
  )
239
255
  end
data/lib/dalli/server.rb CHANGED
@@ -38,6 +38,7 @@ module Dalli
38
38
  @sock = nil
39
39
  @msg = nil
40
40
  @pid = nil
41
+ @inprogress = nil
41
42
  end
42
43
 
43
44
  # Chokepoint method for instrumentation
@@ -261,6 +262,12 @@ module Dalli
261
262
  generic_response
262
263
  end
263
264
 
265
+ def touch(key, ttl)
266
+ req = [REQUEST, OPCODES[:touch], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:touch])
267
+ write(req)
268
+ generic_response
269
+ end
270
+
264
271
  COMPRESSION_MIN_SIZE = 1024
265
272
 
266
273
  # http://www.hjp.at/zettel/m/memcached_flags.rxml
@@ -310,7 +317,7 @@ module Dalli
310
317
  def cas_response
311
318
  header = read(24)
312
319
  raise Dalli::NetworkError, 'No response' if !header
313
- (extras, type, status, count, _, cas) = header.unpack(CAS_HEADER)
320
+ (extras, _, status, count, _, cas) = header.unpack(CAS_HEADER)
314
321
  data = read(count) if count > 0
315
322
  if status == 1
316
323
  nil
@@ -331,7 +338,7 @@ module Dalli
331
338
  def generic_response(unpack=false)
332
339
  header = read(24)
333
340
  raise Dalli::NetworkError, 'No response' if !header
334
- (extras, type, status, count) = header.unpack(NORMAL_HEADER)
341
+ (extras, _, status, count) = header.unpack(NORMAL_HEADER)
335
342
  data = read(count) if count > 0
336
343
  if status == 1
337
344
  nil
@@ -353,7 +360,7 @@ module Dalli
353
360
  loop do
354
361
  header = read(24)
355
362
  raise Dalli::NetworkError, 'No response' if !header
356
- (key_length, status, body_length) = header.unpack(KV_HEADER)
363
+ (key_length, _, body_length) = header.unpack(KV_HEADER)
357
364
  return hash if key_length == 0
358
365
  key = read(key_length)
359
366
  value = read(body_length - key_length) if body_length - key_length > 0
@@ -366,7 +373,7 @@ module Dalli
366
373
  loop do
367
374
  header = read(24)
368
375
  raise Dalli::NetworkError, 'No response' if !header
369
- (key_length, status, body_length) = header.unpack(KV_HEADER)
376
+ (key_length, _, body_length) = header.unpack(KV_HEADER)
370
377
  return hash if key_length == 0
371
378
  flags = read(4).unpack('N')[0]
372
379
  key = read(key_length)
@@ -462,6 +469,7 @@ module Dalli
462
469
  :auth_negotiation => 0x20,
463
470
  :auth_request => 0x21,
464
471
  :auth_continue => 0x22,
472
+ :touch => 0x1C,
465
473
  }
466
474
 
467
475
  HEADER = "CCnCCnNNQ"
@@ -482,6 +490,7 @@ module Dalli
482
490
  :prepend => 'a*a*',
483
491
  :auth_request => 'a*a*',
484
492
  :auth_continue => 'a*a*',
493
+ :touch => 'Na*',
485
494
  }
486
495
  FORMAT = OP_FORMAT.inject({}) { |memo, (k, v)| memo[k] = HEADER + v; memo }
487
496
 
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '2.1.0'
2
+ VERSION = '2.2.0'
3
3
  end
@@ -4,7 +4,7 @@ require 'dalli'
4
4
  module Rack
5
5
  module Session
6
6
  class Dalli < Abstract::ID
7
- attr_reader :pool
7
+ attr_reader :pool, :mutex
8
8
 
9
9
  DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
10
10
  :namespace => 'rack:session',
@@ -12,6 +12,7 @@ module Rack
12
12
 
13
13
  def initialize(app, options={})
14
14
  super
15
+ @mutex = Mutex.new
15
16
  mserv = @default_options[:memcache_server]
16
17
  mopts = @default_options.reject{|k,v| !DEFAULT_OPTIONS.include? k }
17
18
  @pool = options[:cache] || ::Dalli::Client.new(mserv, mopts)
@@ -25,26 +26,45 @@ module Rack
25
26
  end
26
27
 
27
28
  def get_session(env, sid)
28
- unless sid and session = @pool.get(sid)
29
- sid, session = generate_sid, {}
30
- unless @pool.add(sid, session)
31
- raise "Session collision on '#{sid.inspect}'"
29
+ with_lock(env, [nil, {}]) do
30
+ unless sid and session = @pool.get(sid)
31
+ sid, session = generate_sid, {}
32
+ unless @pool.add(sid, session)
33
+ raise "Session collision on '#{sid.inspect}'"
34
+ end
32
35
  end
36
+ [sid, session]
33
37
  end
34
- [sid, session]
35
38
  end
36
39
 
37
40
  def set_session(env, session_id, new_session, options)
38
41
  expiry = options[:expire_after]
39
42
  expiry = expiry.nil? ? 0 : expiry + 1
40
43
 
41
- @pool.set session_id, new_session, expiry
42
- session_id
44
+ with_lock(env, false) do
45
+ @pool.set session_id, new_session, expiry
46
+ session_id
47
+ end
43
48
  end
44
49
 
45
50
  def destroy_session(env, session_id, options)
46
- @pool.delete(session_id)
47
- generate_sid unless options[:drop]
51
+ with_lock(env) do
52
+ @pool.delete(session_id)
53
+ generate_sid unless options[:drop]
54
+ end
55
+ end
56
+
57
+ def with_lock(env, default=nil)
58
+ @mutex.lock if env['rack.multithread']
59
+ yield
60
+ rescue Dalli::DalliError, Errno::ECONNREFUSED
61
+ if $VERBOSE
62
+ warn "#{self} is unable to find memcached server."
63
+ warn $!.inspect
64
+ end
65
+ default
66
+ ensure
67
+ @mutex.unlock if @mutex.locked?
48
68
  end
49
69
 
50
70
  end
@@ -78,7 +78,6 @@ module MemcachedMock
78
78
  begin
79
79
  Process.kill("TERM", pid)
80
80
  Process.wait(pid)
81
- File.delete(MemcachedMock.tmp_socket_path) if options[:unix]
82
81
  rescue Errno::ECHILD, Errno::ESRCH
83
82
  end
84
83
  end
@@ -118,7 +118,6 @@ describe 'ActiveSupport' do
118
118
  with_activesupport do
119
119
  memcached do
120
120
  connect
121
- x = rand_key
122
121
  y = rand_key
123
122
  assert_nil @dalli.read(y)
124
123
  dres = @dalli.write(y, 123)
data/test/test_dalli.rb CHANGED
@@ -14,12 +14,12 @@ describe 'Dalli' do
14
14
  # Rails.logger.expects :warn
15
15
  assert dc.instance_variable_get(:@options)[:compress]
16
16
  end
17
-
17
+
18
18
  should 'raises error with invalid expires_in' do
19
19
  bad_data = [{:bad => 'expires in data'}, Hash, [1,2,3]]
20
20
  bad_data.each do |bad|
21
21
  assert_raises ArgumentError do
22
- dc = Dalli::Client.new('foo', {:expires_in => bad})
22
+ Dalli::Client.new('foo', {:expires_in => bad})
23
23
  end
24
24
  end
25
25
  end
@@ -282,7 +282,7 @@ describe 'Dalli' do
282
282
 
283
283
  should 'support the append and prepend operations' do
284
284
  memcached do |dc|
285
- resp = dc.flush
285
+ dc.flush
286
286
  assert_equal true, dc.set('456', 'xyz', 0, :raw => true)
287
287
  assert_equal true, dc.prepend('456', '0')
288
288
  assert_equal true, dc.append('456', '9')
@@ -294,6 +294,22 @@ describe 'Dalli' do
294
294
  end
295
295
  end
296
296
 
297
+ should 'support touch operation' do
298
+ memcached do |dc|
299
+ begin
300
+ dc.flush
301
+ dc.set 'key', 'value'
302
+ assert_equal true, dc.touch('key', 10)
303
+ assert_equal true, dc.touch('key')
304
+ assert_equal 'value', dc.get('key')
305
+ assert_nil dc.touch('notexist')
306
+ rescue Dalli::DalliError => e
307
+ # This will happen when memcached is in lesser version than 1.4.8
308
+ assert_equal 'Response error 129: Unknown command', e.message
309
+ end
310
+ end
311
+ end
312
+
297
313
  should 'allow TCP connections to be configured for keepalive' do
298
314
  memcached(19122, '', :keepalive => true) do |dc|
299
315
  dc.set(:a, 1)
@@ -394,7 +410,7 @@ describe 'Dalli' do
394
410
  assert_equal({ 'a' => 9, 'b' => 11 }, cache.get_multi(['a', 'b']))
395
411
  inc = cache.incr('cat', 10)
396
412
  assert_equal 0, inc % 5
397
- dec = cache.decr('cat', 5)
413
+ cache.decr('cat', 5)
398
414
  assert_equal 11, cache.get('b')
399
415
  end
400
416
  end
@@ -415,7 +431,7 @@ describe 'Dalli' do
415
431
  assert_equal 2, dc2.get('namespaced')
416
432
  end
417
433
  end
418
-
434
+
419
435
  should 'truncate cache keys that are too long' do
420
436
  memcached do
421
437
  @dalli = Dalli::Client.new('localhost:19122', :namespace => 'some:namspace')
@@ -13,7 +13,7 @@ describe 'failover' do
13
13
  end
14
14
  flunk("Did not timeout")
15
15
  end
16
- rescue Timeout::Error => e
16
+ rescue Timeout::Error
17
17
  end
18
18
 
19
19
  assert_equal({:test => '123'}, dc.get("test_123"))
@@ -0,0 +1,314 @@
1
+ require 'helper'
2
+
3
+ require 'rack/session/dalli'
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+ require 'thread'
7
+
8
+ describe Rack::Session::Dalli do
9
+ session_key = Rack::Session::Dalli::DEFAULT_OPTIONS[:key]
10
+ session_match = /#{session_key}=([0-9a-fA-F]+);/
11
+ incrementor = lambda do |env|
12
+ env["rack.session"]["counter"] ||= 0
13
+ env["rack.session"]["counter"] += 1
14
+ Rack::Response.new(env["rack.session"].inspect).to_a
15
+ end
16
+ drop_session = Rack::Lint.new(proc do |env|
17
+ env['rack.session.options'][:drop] = true
18
+ incrementor.call(env)
19
+ end)
20
+ renew_session = Rack::Lint.new(proc do |env|
21
+ env['rack.session.options'][:renew] = true
22
+ incrementor.call(env)
23
+ end)
24
+ defer_session = Rack::Lint.new(proc do |env|
25
+ env['rack.session.options'][:defer] = true
26
+ incrementor.call(env)
27
+ end)
28
+ skip_session = Rack::Lint.new(proc do |env|
29
+ env['rack.session.options'][:skip] = true
30
+ incrementor.call(env)
31
+ end)
32
+ incrementor = Rack::Lint.new(incrementor)
33
+
34
+ # test memcache connection
35
+ Rack::Session::Dalli.new(incrementor)
36
+
37
+ it "faults on no connection" do
38
+ assert_raises Dalli::RingError do
39
+ rsd = Rack::Session::Dalli.new(incrementor, :memcache_server => 'nosuchserver')
40
+ rsd.pool.set('ping', '')
41
+ end
42
+ end
43
+
44
+ it "connects to existing server" do
45
+ assert_silent do
46
+ rsd = Rack::Session::Dalli.new(incrementor, :namespace => 'test:rack:session')
47
+ rsd.pool.set('ping', '')
48
+ end
49
+ end
50
+
51
+ it "passes options to MemCache" do
52
+ rsd = Rack::Session::Dalli.new(incrementor, :namespace => 'test:rack:session')
53
+ assert_equal('test:rack:session', rsd.pool.instance_eval { @options[:namespace] })
54
+ end
55
+
56
+ it "creates a new cookie" do
57
+ rsd = Rack::Session::Dalli.new(incrementor)
58
+ res = Rack::MockRequest.new(rsd).get("/")
59
+ assert res["Set-Cookie"].include?("#{session_key}=")
60
+ assert_equal '{"counter"=>1}', res.body
61
+ end
62
+
63
+ it "determines session from a cookie" do
64
+ rsd = Rack::Session::Dalli.new(incrementor)
65
+ req = Rack::MockRequest.new(rsd)
66
+ res = req.get("/")
67
+ cookie = res["Set-Cookie"]
68
+ assert_equal '{"counter"=>2}', req.get("/", "HTTP_COOKIE" => cookie).body
69
+ assert_equal '{"counter"=>3}', req.get("/", "HTTP_COOKIE" => cookie).body
70
+ end
71
+
72
+ it "determines session only from a cookie by default" do
73
+ rsd = Rack::Session::Dalli.new(incrementor)
74
+ req = Rack::MockRequest.new(rsd)
75
+ res = req.get("/")
76
+ sid = res["Set-Cookie"][session_match, 1]
77
+ assert_equal '{"counter"=>1}', req.get("/?rack.session=#{sid}").body
78
+ assert_equal '{"counter"=>1}', req.get("/?rack.session=#{sid}").body
79
+ end
80
+
81
+ it "determines session from params" do
82
+ rsd = Rack::Session::Dalli.new(incrementor, :cookie_only => false)
83
+ req = Rack::MockRequest.new(rsd)
84
+ res = req.get("/")
85
+ sid = res["Set-Cookie"][session_match, 1]
86
+ assert_equal '{"counter"=>2}', req.get("/?rack.session=#{sid}").body
87
+ assert_equal '{"counter"=>3}', req.get("/?rack.session=#{sid}").body
88
+ end
89
+
90
+ it "survives nonexistant cookies" do
91
+ bad_cookie = "rack.session=blarghfasel"
92
+ rsd = Rack::Session::Dalli.new(incrementor)
93
+ res = Rack::MockRequest.new(rsd).
94
+ get("/", "HTTP_COOKIE" => bad_cookie)
95
+ assert_equal '{"counter"=>1}', res.body
96
+ cookie = res["Set-Cookie"][session_match]
97
+ refute_match(/#{bad_cookie}/, cookie)
98
+ end
99
+
100
+ it "maintains freshness" do
101
+ rsd = Rack::Session::Dalli.new(incrementor, :expire_after => 3)
102
+ res = Rack::MockRequest.new(rsd).get('/')
103
+ assert res.body.include?('"counter"=>1')
104
+ cookie = res["Set-Cookie"]
105
+ res = Rack::MockRequest.new(rsd).get('/', "HTTP_COOKIE" => cookie)
106
+ assert_equal cookie, res["Set-Cookie"]
107
+ assert res.body.include?('"counter"=>2')
108
+ puts 'Sleeping to expire session' if $DEBUG
109
+ sleep 4
110
+ res = Rack::MockRequest.new(rsd).get('/', "HTTP_COOKIE" => cookie)
111
+ refute_equal cookie, res["Set-Cookie"]
112
+ assert res.body.include?('"counter"=>1')
113
+ end
114
+
115
+ it "does not send the same session id if it did not change" do
116
+ rsd = Rack::Session::Dalli.new(incrementor)
117
+ req = Rack::MockRequest.new(rsd)
118
+
119
+ res0 = req.get("/")
120
+ cookie = res0["Set-Cookie"][session_match]
121
+ assert_equal '{"counter"=>1}', res0.body
122
+
123
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
124
+ assert_nil res1["Set-Cookie"]
125
+ assert_equal '{"counter"=>2}', res1.body
126
+
127
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
128
+ assert_nil res2["Set-Cookie"]
129
+ assert_equal '{"counter"=>3}', res2.body
130
+ end
131
+
132
+ it "deletes cookies with :drop option" do
133
+ rsd = Rack::Session::Dalli.new(incrementor)
134
+ req = Rack::MockRequest.new(rsd)
135
+ drop = Rack::Utils::Context.new(rsd, drop_session)
136
+ dreq = Rack::MockRequest.new(drop)
137
+
138
+ res1 = req.get("/")
139
+ session = (cookie = res1["Set-Cookie"])[session_match]
140
+ assert_equal '{"counter"=>1}', res1.body
141
+
142
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
143
+ assert_nil res2["Set-Cookie"]
144
+ assert_equal '{"counter"=>2}', res2.body
145
+
146
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
147
+ refute_equal session, res3["Set-Cookie"][session_match]
148
+ assert_equal '{"counter"=>1}', res3.body
149
+ end
150
+
151
+ it "provides new session id with :renew option" do
152
+ rsd = Rack::Session::Dalli.new(incrementor)
153
+ req = Rack::MockRequest.new(rsd)
154
+ renew = Rack::Utils::Context.new(rsd, renew_session)
155
+ rreq = Rack::MockRequest.new(renew)
156
+
157
+ res1 = req.get("/")
158
+ session = (cookie = res1["Set-Cookie"])[session_match]
159
+ assert_equal '{"counter"=>1}', res1.body
160
+
161
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
162
+ new_cookie = res2["Set-Cookie"]
163
+ new_session = new_cookie[session_match]
164
+ refute_equal session, new_session
165
+ assert_equal '{"counter"=>2}', res2.body
166
+
167
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
168
+ assert_equal '{"counter"=>3}', res3.body
169
+
170
+ # Old cookie was deleted
171
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
172
+ assert_equal '{"counter"=>1}', res4.body
173
+ end
174
+
175
+ it "omits cookie with :defer option but still updates the state" do
176
+ rsd = Rack::Session::Dalli.new(incrementor)
177
+ count = Rack::Utils::Context.new(rsd, incrementor)
178
+ defer = Rack::Utils::Context.new(rsd, defer_session)
179
+ dreq = Rack::MockRequest.new(defer)
180
+ creq = Rack::MockRequest.new(count)
181
+
182
+ res0 = dreq.get("/")
183
+ assert_nil res0["Set-Cookie"]
184
+ assert_equal '{"counter"=>1}', res0.body
185
+
186
+ res0 = creq.get("/")
187
+ res1 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
188
+ assert_equal '{"counter"=>2}', res1.body
189
+ res2 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
190
+ assert_equal '{"counter"=>3}', res2.body
191
+ end
192
+
193
+ it "omits cookie and state update with :skip option" do
194
+ rsd = Rack::Session::Dalli.new(incrementor)
195
+ count = Rack::Utils::Context.new(rsd, incrementor)
196
+ skip = Rack::Utils::Context.new(rsd, skip_session)
197
+ sreq = Rack::MockRequest.new(skip)
198
+ creq = Rack::MockRequest.new(count)
199
+
200
+ res0 = sreq.get("/")
201
+ assert_nil res0["Set-Cookie"]
202
+ assert_equal '{"counter"=>1}', res0.body
203
+
204
+ res0 = creq.get("/")
205
+ res1 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
206
+ assert_equal '{"counter"=>2}', res1.body
207
+ res2 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
208
+ assert_equal '{"counter"=>2}', res2.body
209
+ end
210
+
211
+ it "updates deep hashes correctly" do
212
+ hash_check = proc do |env|
213
+ session = env['rack.session']
214
+ unless session.include? 'test'
215
+ session.update :a => :b, :c => { :d => :e },
216
+ :f => { :g => { :h => :i} }, 'test' => true
217
+ else
218
+ session[:f][:g][:h] = :j
219
+ end
220
+ [200, {}, [session.inspect]]
221
+ end
222
+ rsd = Rack::Session::Dalli.new(hash_check)
223
+ req = Rack::MockRequest.new(rsd)
224
+
225
+ res0 = req.get("/")
226
+ session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
227
+ ses0 = rsd.pool.get(session_id, true)
228
+
229
+ req.get("/", "HTTP_COOKIE" => cookie)
230
+ ses1 = rsd.pool.get(session_id, true)
231
+
232
+ refute_equal ses0, ses1
233
+ end
234
+
235
+ # anyone know how to do this better?
236
+ it "cleanly merges sessions when multithreaded" do
237
+ unless $DEBUG
238
+ assert_equal 1, 1 # fake assertion to appease the mighty bacon
239
+ next
240
+ end
241
+ warn 'Running multithread test for Session::Dalli'
242
+ rsd = Rack::Session::Dalli.new(incrementor)
243
+ req = Rack::MockRequest.new(rsd)
244
+
245
+ res = req.get('/')
246
+ assert_equal '{"counter"=>1}', res.body
247
+ cookie = res["Set-Cookie"]
248
+ session_id = cookie[session_match, 1]
249
+
250
+ delta_incrementor = lambda do |env|
251
+ # emulate disconjoinment of threading
252
+ env['rack.session'] = env['rack.session'].dup
253
+ Thread.stop
254
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
255
+ incrementor.call(env)
256
+ end
257
+ tses = Rack::Utils::Context.new rsd, delta_incrementor
258
+ treq = Rack::MockRequest.new(tses)
259
+ tnum = rand(7).to_i+5
260
+ r = Array.new(tnum) do
261
+ Thread.new(treq) do |run|
262
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
263
+ end
264
+ end.reverse.map{|t| t.run.join.value }
265
+ r.each do |request|
266
+ assert_equal cookie, request['Set-Cookie']
267
+ assert request.body.include?('"counter"=>2')
268
+ end
269
+
270
+ session = rsd.pool.get(session_id)
271
+ assert_equal tnum+1, session.size # counter
272
+ assert_equal 2, session['counter'] # meeeh
273
+
274
+ tnum = rand(7).to_i+5
275
+ r = Array.new(tnum) do |i|
276
+ app = Rack::Utils::Context.new rsd, time_delta
277
+ req = Rack::MockRequest.new app
278
+ Thread.new(req) do |run|
279
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
280
+ end
281
+ end.reverse.map{|t| t.run.join.value }
282
+ r.each do |request|
283
+ assert_equal cookie, request['Set-Cookie']
284
+ assert request.body.include?('"counter"=>3')
285
+ end
286
+
287
+ session = rsd.pool.get(session_id)
288
+ assert_equal tnum+1, session.size
289
+ assert_equal 3, session['counter']
290
+
291
+ drop_counter = proc do |env|
292
+ env['rack.session'].delete 'counter'
293
+ env['rack.session']['foo'] = 'bar'
294
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
295
+ end
296
+ tses = Rack::Utils::Context.new rsd, drop_counter
297
+ treq = Rack::MockRequest.new(tses)
298
+ tnum = rand(7).to_i+5
299
+ r = Array.new(tnum) do
300
+ Thread.new(treq) do |run|
301
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
302
+ end
303
+ end.reverse.map{|t| t.run.join.value }
304
+ r.each do |request|
305
+ assert_equal cookie, request['Set-Cookie']
306
+ assert request.body.include?('"foo"=>"bar"')
307
+ end
308
+
309
+ session = rsd.pool.get(session_id)
310
+ assert_equal r.size+1, session.size
311
+ assert_nil session['counter']
312
+ assert_equal 'bar', session['foo']
313
+ end
314
+ end
data/test/test_sasl.rb CHANGED
@@ -3,6 +3,13 @@ require 'helper'
3
3
  describe 'Sasl' do
4
4
 
5
5
  context 'a server requiring authentication' do
6
+ before do
7
+ @server = mock()
8
+ @server.stubs(:request).returns(true)
9
+ @server.stubs(:weight).returns(1)
10
+ @server.stubs(:hostname).returns("localhost")
11
+ @server.stubs(:port).returns("19124")
12
+ end
6
13
 
7
14
  context 'without authentication credentials' do
8
15
  before do
@@ -79,5 +86,21 @@ describe 'Sasl' do
79
86
  end
80
87
  end
81
88
 
89
+ should 'pass SASL as URI' do
90
+ Dalli::Server.expects(:new).with("localhost:19124",
91
+ :username => "testuser", :password => "testtest").returns(@server)
92
+ dc = Dalli::Client.new('memcached://testuser:testtest@localhost:19124')
93
+ dc.flush_all
94
+ end
95
+
96
+ should 'pass SASL as ring of URIs' do
97
+ Dalli::Server.expects(:new).with("localhost:19124",
98
+ :username => "testuser", :password => "testtest").returns(@server)
99
+ Dalli::Server.expects(:new).with("otherhost:19125",
100
+ :username => "testuser2", :password => "testtest2").returns(@server)
101
+ dc = Dalli::Client.new(['memcached://testuser:testtest@localhost:19124',
102
+ 'memcached://testuser2:testtest2@otherhost:19125'])
103
+ dc.flush_all
104
+ end
82
105
  end
83
106
  end
@@ -183,7 +183,7 @@ class TestSessionStore < ActionController::IntegrationTest
183
183
  def test_expire_after
184
184
  with_test_route_set(:expire_after => 1) do
185
185
  get '/set_session_value'
186
- assert_match /expires/, @response.headers['Set-Cookie']
186
+ assert_match(/expires/, @response.headers['Set-Cookie'])
187
187
 
188
188
  sleep(1)
189
189
 
@@ -195,7 +195,7 @@ class TestSessionStore < ActionController::IntegrationTest
195
195
  def test_expires_in
196
196
  with_test_route_set(:expires_in => 1) do
197
197
  get '/set_session_value'
198
- assert_no_match /expires/, @response.headers['Set-Cookie']
198
+ assert_no_match(/expires/, @response.headers['Set-Cookie'])
199
199
 
200
200
  sleep(1)
201
201
 
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.1.0
4
+ version: 2.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-23 00:00:00.000000000 Z
12
+ date: 2012-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mini_shoulda
16
- requirement: &70203098892260 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70203098892260
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: mocha
27
- requirement: &70203098891120 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70203098891120
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rails
38
- requirement: &70203098890540 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
@@ -43,7 +53,12 @@ dependencies:
43
53
  version: '3'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70203098890540
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
47
62
  description: High performance memcached client for Ruby
48
63
  email: mperham@gmail.com
49
64
  executables: []
@@ -77,6 +92,7 @@ files:
77
92
  - test/test_encoding.rb
78
93
  - test/test_failover.rb
79
94
  - test/test_network.rb
95
+ - test/test_rack_session.rb
80
96
  - test/test_ring.rb
81
97
  - test/test_sasl.rb
82
98
  - test/test_session_store.rb
@@ -101,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
117
  version: '0'
102
118
  requirements: []
103
119
  rubyforge_project:
104
- rubygems_version: 1.8.15
120
+ rubygems_version: 1.8.24
105
121
  signing_key:
106
122
  specification_version: 3
107
123
  summary: High performance memcached client for Ruby
@@ -115,6 +131,7 @@ test_files:
115
131
  - test/test_encoding.rb
116
132
  - test/test_failover.rb
117
133
  - test/test_network.rb
134
+ - test/test_rack_session.rb
118
135
  - test/test_ring.rb
119
136
  - test/test_sasl.rb
120
137
  - test/test_session_store.rb