dalli 0.9.10 → 0.10.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,18 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 0.10.0
5
+ ======
6
+
7
+ Warning: this release changed how Rails marshals data with Dalli. Unfortunately previous versions double marshalled values. It is possible that data stored with previous versions of Dalli will not work with this version.
8
+
9
+ IT IS HIGHLY RECOMMENDED YOU FLUSH YOUR CACHE BEFORE UPGRADING.
10
+
11
+ - Rework how the Rails cache store does value marshalling.
12
+ - Rework old server version detection to avoid a socket read hang.
13
+ - Refactor the Rails 2.3 :dalli\_store to be closer to :mem\_cache\_store.
14
+ - Better documentation for session store config (plukevdh)
15
+
4
16
  0.9.10
5
17
  ----
6
18
 
data/README.md CHANGED
@@ -61,10 +61,10 @@ A more comprehensive example (note that we are setting a reasonable default for
61
61
  config.cache_store = :dalli_store, 'cache-1.example.com', 'cache-2.example.com',
62
62
  :namespace => NAME_OF_RAILS_APP, :expires_in => 1.day, :compress => true, :compress_threshold => 64.kilobytes
63
63
 
64
- In `config/initializers/session_store.rb`:
64
+ To use Dalli for Rails session storage, in `config/initializers/session_store.rb`:
65
65
 
66
66
  require 'action_dispatch/middleware/session/dalli_store'
67
- Rails.application.config.session_store :dalli_store, :key => ...
67
+ Rails.application.config.session_store :dalli_store, :memcache_server => ['host1', 'host2'], :namespace => 'sessions', :key => '_foundation_session', :expire_after => 30.minutes
68
68
 
69
69
 
70
70
  Usage with Rails 2.3.x
@@ -2,10 +2,7 @@ require 'active_support/cache'
2
2
  require 'action_dispatch/middleware/session/abstract_store'
3
3
  require 'dalli'
4
4
 
5
- # Dalli-based session store for Rails 3.0. Use like so:
6
- #
7
- # require 'action_dispatch/middleware/session/dalli_store'
8
- # config.session_store ActionDispatch::Session::DalliStore, ['cache-1', 'cache-2'], :expire_after => 2.weeks
5
+ # Dalli-based session store for Rails 3.0.
9
6
  module ActionDispatch
10
7
  module Session
11
8
  class DalliStore < AbstractStore
@@ -40,7 +37,7 @@ module ActionDispatch
40
37
  begin
41
38
  session = @pool.get(sid) || {}
42
39
  rescue Dalli::DalliError
43
- Rails.logger.warn("Session::DalliStore: #{$!.message}")
40
+ Rails.logger.warn("Session::DalliStore#get: #{$!.message}")
44
41
  session = {}
45
42
  end
46
43
  [sid, session]
@@ -52,7 +49,7 @@ module ActionDispatch
52
49
  @pool.set(sid, session_data, expiry)
53
50
  sid
54
51
  rescue Dalli::DalliError
55
- Rails.logger.warn("Session::DalliStore: #{$!.message}")
52
+ Rails.logger.warn("Session::DalliStore#set: #{$!.message}")
56
53
  false
57
54
  end
58
55
 
@@ -61,7 +58,7 @@ module ActionDispatch
61
58
  @pool.delete(sid)
62
59
  end
63
60
  rescue Dalli::DalliError
64
- Rails.logger.warn("Session::DalliStore: #{$!.message}")
61
+ Rails.logger.warn("Session::DalliStore#delete: #{$!.message}")
65
62
  false
66
63
  end
67
64
 
@@ -17,6 +17,7 @@ module ActiveSupport
17
17
  class DalliStore < Store
18
18
 
19
19
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/
20
+ RAW = { :raw => true }
20
21
 
21
22
  def self.build_mem_cache(*addresses)
22
23
  addresses = addresses.flatten
@@ -53,7 +54,7 @@ module ActiveSupport
53
54
  options = names.extract_options!
54
55
  options = merged_options(options)
55
56
  keys_to_names = names.inject({}){|map, name| map[escape_key(namespaced_key(name, options))] = name; map}
56
- raw_values = @data.get_multi(keys_to_names.keys, options)
57
+ raw_values = @data.get_multi(keys_to_names.keys, RAW)
57
58
  values = {}
58
59
  raw_values.each do |key, value|
59
60
  entry = deserialize_entry(value)
@@ -75,7 +76,7 @@ module ActiveSupport
75
76
  @data.incr(escape_key(namespaced_key(name, options)), amount, expires_in, initial)
76
77
  end
77
78
  rescue Dalli::DalliError => e
78
- logger.error("DalliError (#{e}): #{e.message}") if logger
79
+ logger.error("DalliError: #{e.message}") if logger
79
80
  nil
80
81
  end
81
82
 
@@ -92,7 +93,7 @@ module ActiveSupport
92
93
  @data.decr(escape_key(namespaced_key(name, options)), amount, expires_in, initial)
93
94
  end
94
95
  rescue Dalli::DalliError => e
95
- logger.error("DalliError (#{e}): #{e.message}") if logger
96
+ logger.error("DalliError: #{e.message}") if logger
96
97
  nil
97
98
  end
98
99
 
@@ -112,17 +113,22 @@ module ActiveSupport
112
113
  end
113
114
 
114
115
  protected
116
+
117
+ # This CacheStore impl controls value marshalling so we take special
118
+ # care to always pass :raw => true to the Dalli API so it does not
119
+ # double marshal.
120
+
115
121
  # Read an entry from the cache.
116
122
  def read_entry(key, options) # :nodoc:
117
- deserialize_entry(@data.get(escape_key(key), :raw => true))
123
+ deserialize_entry(@data.get(escape_key(key), RAW))
118
124
  rescue Dalli::DalliError => e
119
- logger.error("DalliError (#{e}): #{e.message}") if logger
125
+ logger.error("DalliError: #{e.message}") if logger
120
126
  nil
121
127
  end
122
128
 
123
129
  # Write an entry to the cache.
124
130
  def write_entry(key, entry, options) # :nodoc:
125
- method = options && options[:unless_exist] ? :add : :set
131
+ method = options[:unless_exist] ? :add : :set
126
132
  value = options[:raw] ? entry.value.to_s : entry
127
133
  expires_in = options[:expires_in].to_i
128
134
  if expires_in > 0 && !options[:raw]
@@ -131,7 +137,7 @@ module ActiveSupport
131
137
  end
132
138
  @data.send(method, escape_key(key), value, expires_in, options)
133
139
  rescue Dalli::DalliError => e
134
- logger.error("DalliError (#{e}): #{e.message}") if logger
140
+ logger.error("DalliError: #{e.message}") if logger
135
141
  false
136
142
  end
137
143
 
@@ -139,7 +145,7 @@ module ActiveSupport
139
145
  def delete_entry(key, options) # :nodoc:
140
146
  @data.delete(escape_key(key))
141
147
  rescue Dalli::DalliError => e
142
- logger.error("DalliError (#{e}): #{e.message}") if logger
148
+ logger.error("DalliError: #{e.message}") if logger
143
149
  false
144
150
  end
145
151
 
@@ -152,6 +158,8 @@ module ActiveSupport
152
158
 
153
159
  def deserialize_entry(raw_value)
154
160
  if raw_value
161
+ # FIXME: This is a terrible implementation for performance reasons:
162
+ # throwing an exception is much slower than some if logic.
155
163
  entry = Marshal.load(raw_value) rescue raw_value
156
164
  entry.is_a?(Entry) ? entry : Entry.new(entry)
157
165
  else
@@ -45,6 +45,8 @@ module ActiveSupport
45
45
  # Reads multiple keys from the cache using a single call to the
46
46
  # servers for all keys. Options can be passed in the last argument.
47
47
  def read_multi(*names)
48
+ options = nil
49
+ options = names.pop if names.last.is_a?(Hash)
48
50
  keys_to_names = names.inject({}){|map, name| map[escape_key(name)] = name; map}
49
51
  cache_keys = {}
50
52
  # map keys to servers
@@ -53,62 +55,24 @@ module ActiveSupport
53
55
  cache_keys[cache_key] = key
54
56
  end
55
57
 
56
- values = @data.get_multi keys_to_names.keys
58
+ values = @data.get_multi(keys_to_names.keys, options)
57
59
  results = {}
58
60
  values.each do |key, value|
59
- results[cache_keys[key]] = Marshal.load value
61
+ results[cache_keys[key]] = value
60
62
  end
61
63
  results
62
64
  end
63
65
 
64
- # Increment a cached value. This method uses the memcached incr atomic
65
- # operator and can only be used on values written with the :raw option.
66
- # Calling it on a value not stored with :raw will initialize that value
67
- # to zero.
68
- def increment(key, amount = 1) # :nodoc:
69
- log("incrementing", key, amount)
70
- @data.incr(escape_key(key), amount)
71
- rescue Dalli::DalliError => e
72
- logger.error("DalliError (#{e}): #{e.message}") if logger
73
- nil
74
- end
75
-
76
- # Decrement a cached value. This method uses the memcached decr atomic
77
- # operator and can only be used on values written with the :raw option.
78
- # Calling it on a value not stored with :raw will initialize that value
79
- # to zero.
80
- def decrement(key, amount = 1) # :nodoc:
81
- log("decrement", key, amount)
82
- @data.decr(escape_key(key), amount)
83
- rescue Dalli::DalliError => e
84
- logger.error("DalliError (#{e}): #{e.message}") if logger
85
- nil
86
- end
87
-
88
66
  def reset
89
67
  @data.reset
90
68
  end
91
69
 
92
- # Clear the entire cache on all memcached servers. This method should
93
- # be used with care when using a shared cache.
94
- def clear
95
- @data.flush_all
96
- end
97
-
98
- # Get the statistics from the memcached servers.
99
- def stats
100
- @data.stats
101
- end
102
-
103
70
  # Read an entry from the cache.
104
71
  def read(key, options = nil) # :nodoc:
105
72
  super
106
- value = @data.get(escape_key(key), options)
107
- return nil if value.nil?
108
- value = options && options[:raw] ? value : Marshal.load(value)
109
- value
73
+ @data.get(escape_key(key), options)
110
74
  rescue Dalli::DalliError => e
111
- logger.error("DalliError (#{e}): #{e.message}")
75
+ logger.error("DalliError: #{e.message}")
112
76
  nil
113
77
  end
114
78
 
@@ -121,11 +85,11 @@ module ActiveSupport
121
85
  # the cache. See ActiveSupport::Cache::Store#write for an example.
122
86
  def write(key, value, options = nil)
123
87
  super
88
+ value = value.to_s if options && options[:raw]
124
89
  method = options && options[:unless_exist] ? :add : :set
125
- value = options && options[:raw] ? value.to_s : Marshal.dump(value)
126
90
  @data.send(method, escape_key(key), value, expires_in(options), options)
127
91
  rescue Dalli::DalliError => e
128
- logger.error("DalliError (#{e}): #{e.message}")
92
+ logger.error("DalliError: #{e.message}")
129
93
  false
130
94
  end
131
95
 
@@ -133,7 +97,7 @@ module ActiveSupport
133
97
  super
134
98
  @data.delete(escape_key(key))
135
99
  rescue Dalli::DalliError => e
136
- logger.error("DalliError (#{e}): #{e.message}")
100
+ logger.error("DalliError: #{e.message}")
137
101
  false
138
102
  end
139
103
 
@@ -144,6 +108,30 @@ module ActiveSupport
144
108
  !read(key, options).nil?
145
109
  end
146
110
 
111
+ # Increment a cached value. This method uses the memcached incr atomic
112
+ # operator and can only be used on values written with the :raw option.
113
+ # Calling it on a value not stored with :raw will initialize that value
114
+ # to zero.
115
+ def increment(key, amount = 1) # :nodoc:
116
+ log("incrementing", key, amount)
117
+ @data.incr(escape_key(key), amount)
118
+ rescue Dalli::DalliError => e
119
+ logger.error("DalliError: #{e.message}") if logger
120
+ nil
121
+ end
122
+
123
+ # Decrement a cached value. This method uses the memcached decr atomic
124
+ # operator and can only be used on values written with the :raw option.
125
+ # Calling it on a value not stored with :raw will initialize that value
126
+ # to zero.
127
+ def decrement(key, amount = 1) # :nodoc:
128
+ log("decrement", key, amount)
129
+ @data.decr(escape_key(key), amount)
130
+ rescue Dalli::DalliError => e
131
+ logger.error("DalliError: #{e.message}") if logger
132
+ nil
133
+ end
134
+
147
135
  def delete_matched(matcher, options = nil) # :nodoc:
148
136
  # don't do any local caching at present, just pass
149
137
  # through and let the error happen
@@ -151,6 +139,17 @@ module ActiveSupport
151
139
  raise "Not supported by Memcache"
152
140
  end
153
141
 
142
+ # Clear the entire cache on all memcached servers. This method should
143
+ # be used with care when using a shared cache.
144
+ def clear
145
+ @data.flush_all
146
+ end
147
+
148
+ # Get the statistics from the memcached servers.
149
+ def stats
150
+ @data.stats
151
+ end
152
+
154
153
  private
155
154
 
156
155
  # Exists in 2.3.8 but not in 2.3.2 so roll our own version
data/lib/dalli/client.rb CHANGED
@@ -44,7 +44,7 @@ module Dalli
44
44
  def get_multi(*keys)
45
45
  return {} if keys.empty?
46
46
  options = nil
47
- options = keys.pop if keys.last.is_a?(Hash)
47
+ options = keys.pop if keys.last.is_a?(Hash) || keys.last.nil?
48
48
  ring.lock do
49
49
  keys.flatten.each do |key|
50
50
  perform(:getkq, key)
data/lib/dalli/server.rb CHANGED
@@ -14,9 +14,7 @@ module Dalli
14
14
  @weight ||= 1
15
15
  @weight = Integer(@weight)
16
16
  @down_at = nil
17
- connection
18
17
  @version = detect_memcached_version
19
- raise NotImplementedError, "Dalli does not support memcached versions < 1.4.0, found #{@version} at #{@hostname}:#{@port}" if @version < '1.4.0'
20
18
  Dalli.logger.debug { "#{@hostname}:#{@port} running memcached v#{@version}" }
21
19
  end
22
20
 
@@ -64,15 +62,26 @@ module Dalli
64
62
  private
65
63
 
66
64
  def detect_memcached_version
65
+ return "(unknown)" if ENV['SKIP_MEMCACHE_VERSION_CHECK']
66
+
67
67
  # HACK, the server does not appear to have a way to negotiate the protocol.
68
68
  # If you ask for the version in text, the socket is immediately locked to the text
69
- # protocol. All we can do is use binary and handle the failure if the server is old.
70
- # Alternative suggestions welcome.
71
- begin
69
+ # protocol. But if we use binary, an old remote server will not respond. If
70
+ # the server is using SASL, it will not respond to the text protocol. Sigh.
71
+ if username
72
+ # using SASL, assume the binary protocol will work.
72
73
  binary_version
73
- rescue Dalli::NetworkError
74
- sleep 1
75
- text_version
74
+ else
75
+ # Use text to determine the remote version, close the socket and open it back up.
76
+ # Alternative suggestions welcome.
77
+ version = text_version
78
+ if version < '1.4.0'
79
+ Dalli.logger.error "Dalli does not support memcached versions < 1.4.0, found #{version} at #{@hostname}:#{@port}"
80
+ raise NotImplementedError, "Dalli does not support memcached versions < 1.4.0, found #{version} at #{@hostname}:#{@port}"
81
+ end
82
+ close
83
+ connection
84
+ version
76
85
  end
77
86
  end
78
87
 
@@ -188,18 +197,18 @@ module Dalli
188
197
  cas_response
189
198
  end
190
199
 
191
- def binary_version
192
- req = [REQUEST, OPCODES[:version], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
193
- write(req)
194
- generic_response
195
- end
196
-
197
200
  def text_version
198
201
  write("version\r\n")
199
202
  connection.gets =~ /VERSION (.*)\r\n/
200
203
  $1
201
204
  end
202
205
 
206
+ def binary_version
207
+ req = [REQUEST, OPCODES[:version], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
208
+ write(req)
209
+ generic_response
210
+ end
211
+
203
212
  def cas_response
204
213
  header = read(24)
205
214
  raise Dalli::NetworkError, 'No response' if !header
@@ -450,9 +459,10 @@ module Dalli
450
459
  content = read(count, socket)
451
460
  return Dalli.logger.info("Dalli/SASL: #{content}") if status == 0
452
461
 
453
- raise Dalli::NetworkError, "Error authenticating: #{status}" unless status == 0x21
454
- (step, msg) = sasl.receive('challenge', content)
455
- raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
462
+ raise Dalli::DalliError, "Error authenticating: #{status}" unless status == 0x21
463
+ raise NotImplementedError, "No two-step authentication mechanisms supported"
464
+ # (step, msg) = sasl.receive('challenge', content)
465
+ # raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
456
466
  end
457
467
  end
458
468
  end
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '0.9.10'
2
+ VERSION = '0.10.0'
3
3
  end
@@ -78,7 +78,7 @@ module MemcachedMock
78
78
  rescue Errno::ECHILD
79
79
  end
80
80
  end
81
- sleep 0.2
81
+ sleep 0.1
82
82
  pid
83
83
  end
84
84
 
@@ -7,19 +7,20 @@ class TestActiveSupport < Test::Unit::TestCase
7
7
  with_activesupport do
8
8
  memcached do
9
9
  connect
10
- dvalue = @mc.fetch('somekeywithoutspaces', :expires_in => 1.second) { 123 }
11
- mvalue = @dalli.fetch('someotherkeywithoutspaces', :expires_in => 1.second) { 123 }
10
+ mvalue = @mc.fetch('somekeywithoutspaces', :expires_in => 1.second) { 123 }
11
+ dvalue = @dalli.fetch('someotherkeywithoutspaces', :expires_in => 1.second) { 123 }
12
+ assert_equal 123, dvalue
12
13
  assert_equal mvalue, dvalue
13
14
 
14
15
  o = Object.new
15
16
  o.instance_variable_set :@foo, 'bar'
16
- dvalue = @mc.fetch(rand_key, :raw => true) { o }
17
- mvalue = @dalli.fetch(rand_key, :raw => true) { o }
17
+ mvalue = @mc.fetch(rand_key, :raw => true) { o }
18
+ dvalue = @dalli.fetch(rand_key, :raw => true) { o }
18
19
  assert_equal mvalue, dvalue
19
- assert_equal o, dvalue
20
+ assert_equal o, mvalue
20
21
 
21
- dvalue = @mc.fetch(rand_key) { o }
22
- mvalue = @dalli.fetch(rand_key) { o }
22
+ mvalue = @mc.fetch(rand_key) { o }
23
+ dvalue = @dalli.fetch(rand_key) { o }
23
24
  assert_equal mvalue, dvalue
24
25
  assert_equal o, dvalue
25
26
  end
@@ -65,6 +66,33 @@ class TestActiveSupport < Test::Unit::TestCase
65
66
  end
66
67
  end
67
68
 
69
+ should 'support raw read_multi' do
70
+ with_activesupport do
71
+ memcached do
72
+ connect
73
+ @mc.write("abc", 5, :raw => true)
74
+ @mc.write("cba", 5, :raw => true)
75
+ if RAILS_VERSION < '3.0.0'
76
+ assert_raise ArgumentError do
77
+ @mc.read_multi("abc", "cba")
78
+ end
79
+ else
80
+ assert_equal({'abc' => '5', 'cba' => '5' }, @mc.read_multi("abc", "cba"))
81
+ end
82
+
83
+ @dalli.write("abc", 5, :raw => true)
84
+ @dalli.write("cba", 5, :raw => true)
85
+ if RAILS_VERSION < '3.0.0'
86
+ assert_raise ArgumentError do
87
+ @dalli.read_multi("abc", "cba")
88
+ end
89
+ else
90
+ assert_equal({'abc' => '5', 'cba' => '5' }, @dalli.read_multi("abc", "cba"))
91
+ end
92
+ end
93
+ end
94
+ end
95
+
68
96
  should 'support read, write and delete' do
69
97
  with_activesupport do
70
98
  memcached do
data/test/test_dalli.rb CHANGED
@@ -2,7 +2,7 @@ require 'helper'
2
2
  require 'memcached_mock'
3
3
 
4
4
  class TestDalli < Test::Unit::TestCase
5
-
5
+
6
6
  should "default to localhost:11211" do
7
7
  dc = Dalli::Client.new
8
8
  ring = dc.send(:ring)
@@ -323,13 +323,6 @@ class TestDalli < Test::Unit::TestCase
323
323
  end
324
324
  end
325
325
 
326
- should 'gracefully handle authentication failures' do
327
- memcached(19124, '-S') do |dc|
328
- assert_raise Dalli::DalliError, /32/ do
329
- dc.set('abc', 123)
330
- end
331
- end
332
- end
333
326
 
334
327
  should "handle namespaced keys" do
335
328
  memcached do |dc|
@@ -342,6 +335,26 @@ class TestDalli < Test::Unit::TestCase
342
335
  end
343
336
  end
344
337
 
338
+ context 'without authentication credentials' do
339
+ setup do
340
+ ENV['MEMCACHE_USERNAME'] = 'testuser'
341
+ ENV['MEMCACHE_PASSWORD'] = 'wrongpwd'
342
+ end
343
+
344
+ teardown do
345
+ ENV['MEMCACHE_USERNAME'] = nil
346
+ ENV['MEMCACHE_PASSWORD'] = nil
347
+ end
348
+
349
+ should 'gracefully handle authentication failures' do
350
+ memcached(19124, '-S') do |dc|
351
+ assert_raise Dalli::DalliError, /32/ do
352
+ dc.set('abc', 123)
353
+ end
354
+ end
355
+ end
356
+ end
357
+
345
358
  # OSX: Create a SASL user for the memcached application like so:
346
359
  #
347
360
  # saslpasswd2 -a memcached -c testuser
@@ -360,7 +373,7 @@ class TestDalli < Test::Unit::TestCase
360
373
 
361
374
  should 'support SASL authentication' do
362
375
  memcached(19124, '-S') do |dc|
363
- # I get "Dalli::NetworkError: Error authenticating: 32" in OSX
376
+ # I get "Dalli::DalliError: Error authenticating: 32" in OSX
364
377
  # but SASL works on Heroku servers. YMMV.
365
378
  assert_equal true, dc.set('abc', 123)
366
379
  assert_equal 123, dc.get('abc')
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 9
8
7
  - 10
9
- version: 0.9.10
8
+ - 0
9
+ version: 0.10.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-13 00:00:00 -07:00
17
+ date: 2010-10-16 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency