dalli 2.3.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.md CHANGED
@@ -1,6 +1,21 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 2.5.0
5
+ =======
6
+
7
+ - Don't escape non-ASCII keys, memcached binary protocol doesn't care. [#257]
8
+ - :dalli_store now implements LocalCache [#236]
9
+ - Removed lots of old session_store test code, tests now all run without a default memcached server [#275]
10
+ - Changed Dalli ActiveSupport adapter to always attempt instrumentation [brianmario, #284]
11
+ - Change write operations (add/set/replace) to return false when value is too large to store [brianmario, #283]
12
+
13
+ 2.4.0
14
+ =======
15
+ - Added the ability to swap out the compressed used to [de]compress cache data [brianmario, #276]
16
+ - Fix get\_multi performance issues with lots of memcached servers [tmm1]
17
+ - Throw more specific exceptions [tmm1]
18
+
4
19
  2.3.0
5
20
  =======
6
21
  - Added the ability to swap out the serializer used to [de]serialize cache data [brianmario, #274]
data/README.md CHANGED
@@ -108,6 +108,10 @@ Dalli::Client accepts the following options. All times are in seconds.
108
108
 
109
109
  **compress**: Boolean, if true Dalli will gzip-compress values larger than 1K.
110
110
 
111
+ **compression_min_size**: Minimum value byte size for which to attempt compression. Default is 1K.
112
+
113
+ **compression_max_size**: Maximum value byte size for which to attempt compression. Default is unlimited.
114
+
111
115
  **socket_timeout**: Timeout for all socket operations (connect, read, write). Default is 0.5.
112
116
 
113
117
  **socket_max_failures**: When a socket operation fails after socket_timeout, the same operation is retried. This is to not immediately mark a server down when there's a very slight network problem. Default is 2.
@@ -45,6 +45,8 @@ module ActiveSupport
45
45
  addresses
46
46
  end
47
47
  @data = Dalli::Client.new(servers, @options)
48
+
49
+ extend Strategy::LocalCache
48
50
  end
49
51
 
50
52
  def fetch(name, options=nil)
@@ -106,8 +108,9 @@ module ActiveSupport
106
108
  options ||= {}
107
109
  name = expanded_key name
108
110
 
109
- log(:delete, name, options)
110
- delete_entry(name, options)
111
+ instrument(:delete, name, options) do |payload|
112
+ delete_entry(name, options)
113
+ end
111
114
  end
112
115
 
113
116
  # Reads multiple keys from the cache using a single call to the
@@ -251,20 +254,15 @@ module ActiveSupport
251
254
  def escape(key)
252
255
  key = key.to_s.dup
253
256
  key = key.force_encoding("BINARY") if key.respond_to?(:encode)
254
- key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
255
257
  key
256
258
  end
257
259
 
258
260
  def instrument(operation, key, options=nil)
259
261
  log(operation, key, options)
260
262
 
261
- if ActiveSupport::Cache::Store.instrument
262
- payload = { :key => key }
263
- payload.merge!(options) if options.is_a?(Hash)
264
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
265
- else
266
- yield(nil)
267
- end
263
+ payload = { :key => key }
264
+ payload.merge!(options) if options.is_a?(Hash)
265
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
268
266
  end
269
267
 
270
268
  def log(operation, key, options=nil)
@@ -4,6 +4,7 @@ require 'dalli/server'
4
4
  require 'dalli/socket'
5
5
  require 'dalli/version'
6
6
  require 'dalli/options'
7
+ require 'dalli/compressor'
7
8
  require 'dalli/railtie' if defined?(::Rails::Railtie)
8
9
 
9
10
  module Dalli
@@ -13,8 +14,10 @@ module Dalli
13
14
  class NetworkError < DalliError; end
14
15
  # no server available/alive error
15
16
  class RingError < DalliError; end
16
- # application error in marshalling
17
+ # application error in marshalling serialization
17
18
  class MarshalError < DalliError; end
19
+ # application error in marshalling deserialization or decompression
20
+ class UnmarshalError < DalliError; end
18
21
 
19
22
  def self.logger
20
23
  @logger ||= (rails_logger || default_logger)
@@ -46,6 +49,17 @@ module Dalli
46
49
  def self.serializer=(serializer)
47
50
  @serializer = serializer
48
51
  end
52
+
53
+ # Default serialization to Dalli::Compressor
54
+ @compressor = Compressor
55
+
56
+ def self.compressor
57
+ @compressor
58
+ end
59
+
60
+ def self.compressor=(compressor)
61
+ @compressor = compressor
62
+ end
49
63
  end
50
64
 
51
65
  if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
@@ -1,4 +1,5 @@
1
1
  require 'digest/md5'
2
+ require 'set'
2
3
 
3
4
  # encoding: ascii
4
5
  module Dalli
@@ -20,13 +21,14 @@ module Dalli
20
21
  # - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
21
22
  # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
22
23
  # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever
23
- # - :compress - defaults to false, if true Dalli will compress values larger than 100 bytes before
24
+ # - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before
24
25
  # sending them to memcached.
25
26
  #
26
27
  def initialize(servers=nil, options={})
27
28
  @servers = servers || env_servers || '127.0.0.1:11211'
28
29
  @options = normalize_options(options)
29
30
  @ring = nil
31
+ @servers_in_use = nil
30
32
  end
31
33
 
32
34
  #
@@ -58,29 +60,33 @@ module Dalli
58
60
  options = nil
59
61
  options = keys.pop if keys.last.is_a?(Hash) || keys.last.nil?
60
62
  ring.lock do
63
+ self.servers_in_use = Set.new
64
+
61
65
  keys.flatten.each do |key|
62
66
  begin
63
67
  perform(:getkq, key)
64
68
  rescue DalliError, NetworkError => e
65
- Dalli.logger.debug { e.message }
69
+ Dalli.logger.debug { e.inspect }
66
70
  Dalli.logger.debug { "unable to get key #{key}" }
67
71
  end
68
72
  end
69
73
 
70
74
  values = {}
71
- ring.servers.each do |server|
75
+ servers_in_use.each do |server|
72
76
  next unless server.alive?
73
77
  begin
74
78
  server.request(:noop).each_pair do |key, value|
75
79
  values[key_without_namespace(key)] = value
76
80
  end
77
81
  rescue DalliError, NetworkError => e
78
- Dalli.logger.debug { e.message }
82
+ Dalli.logger.debug { e.inspect }
79
83
  Dalli.logger.debug { "results from this server will be missing" }
80
84
  end
81
85
  end
82
86
  values
83
87
  end
88
+ ensure
89
+ self.servers_in_use = nil
84
90
  end
85
91
 
86
92
  def fetch(key, ttl=nil, options=nil)
@@ -264,14 +270,24 @@ module Dalli
264
270
  key = validate_key(key)
265
271
  begin
266
272
  server = ring.server_for_key(key)
267
- server.request(op, key, *args)
273
+ ret = server.request(op, key, *args)
274
+ servers_in_use << server if servers_in_use
275
+ ret
268
276
  rescue NetworkError => e
269
- Dalli.logger.debug { e.message }
277
+ Dalli.logger.debug { e.inspect }
270
278
  Dalli.logger.debug { "retrying request with new server" }
271
279
  retry
272
280
  end
273
281
  end
274
282
 
283
+ def servers_in_use
284
+ Thread.current[:"#{object_id}-servers"]
285
+ end
286
+
287
+ def servers_in_use=(value)
288
+ Thread.current[:"#{object_id}-servers"] = value
289
+ end
290
+
275
291
  def validate_key(key)
276
292
  raise ArgumentError, "key cannot be blank" if !key || key.length == 0
277
293
  key = key_with_namespace(key)
@@ -0,0 +1,13 @@
1
+ require 'zlib'
2
+
3
+ module Dalli
4
+ class Compressor
5
+ def self.compress(data)
6
+ Zlib::Deflate.deflate(data)
7
+ end
8
+
9
+ def self.decompress(data)
10
+ Zlib::Inflate.inflate(data)
11
+ end
12
+ end
13
+ end
@@ -1,6 +1,5 @@
1
1
  require 'socket'
2
2
  require 'timeout'
3
- require 'zlib'
4
3
 
5
4
  module Dalli
6
5
  class Server
@@ -20,6 +19,10 @@ module Dalli
20
19
  :socket_failure_delay => 0.01,
21
20
  # max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
22
21
  :value_max_bytes => 1024 * 1024,
22
+ # min byte size to attempt compression
23
+ :compression_min_size => 1024,
24
+ # max byte size for compression
25
+ :compression_max_size => false,
23
26
  :username => nil,
24
27
  :password => nil,
25
28
  :keepalive => true
@@ -164,24 +167,37 @@ module Dalli
164
167
  def set(key, value, ttl, cas, options)
165
168
  (value, flags) = serialize(key, value, options)
166
169
 
167
- 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])
168
- write(req)
169
- generic_response unless multi?
170
+ if under_max_value_size?(value)
171
+ 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])
172
+ write(req)
173
+ generic_response unless multi?
174
+ else
175
+ false
176
+ end
170
177
  end
171
178
 
172
179
  def add(key, value, ttl, options)
173
180
  (value, flags) = serialize(key, value, options)
174
181
 
175
- 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])
176
- write(req)
177
- generic_response unless multi?
182
+ if under_max_value_size?(value)
183
+ 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])
184
+ write(req)
185
+ generic_response unless multi?
186
+ else
187
+ false
188
+ end
178
189
  end
179
190
 
180
191
  def replace(key, value, ttl, options)
181
192
  (value, flags) = serialize(key, value, options)
182
- 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])
183
- write(req)
184
- generic_response unless multi?
193
+
194
+ if under_max_value_size?(value)
195
+ 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])
196
+ write(req)
197
+ generic_response unless multi?
198
+ else
199
+ false
200
+ end
185
201
  end
186
202
 
187
203
  def delete(key)
@@ -268,8 +284,6 @@ module Dalli
268
284
  generic_response
269
285
  end
270
286
 
271
- COMPRESSION_MIN_SIZE = 1024
272
-
273
287
  # http://www.hjp.at/zettel/m/memcached_flags.rxml
274
288
  # Looks like most clients use bit 0 to indicate native language serialization
275
289
  # and bit 1 to indicate gzip compression.
@@ -293,11 +307,12 @@ module Dalli
293
307
  value.to_s
294
308
  end
295
309
  compressed = false
296
- if @options[:compress] && value.bytesize >= COMPRESSION_MIN_SIZE
297
- value = Zlib::Deflate.deflate(value)
310
+ if @options[:compress] && value.bytesize >= @options[:compression_min_size] &&
311
+ (!@options[:compression_max_size] || value.bytesize <= @options[:compression_max_size])
312
+ value = Dalli.compressor.compress(value)
298
313
  compressed = true
299
314
  end
300
- raise Dalli::DalliError, "Value too large, memcached can only store #{@options[:value_max_bytes]} bytes per key [key: #{key}, size: #{value.bytesize}]" if value.bytesize > @options[:value_max_bytes]
315
+
301
316
  flags = 0
302
317
  flags |= FLAG_COMPRESSED if compressed
303
318
  flags |= FLAG_SERIALIZED if marshalled
@@ -305,13 +320,17 @@ module Dalli
305
320
  end
306
321
 
307
322
  def deserialize(value, flags)
308
- value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
323
+ value = Dalli.compressor.decompress(value) if (flags & FLAG_COMPRESSED) != 0
309
324
  value = Dalli.serializer.load(value) if (flags & FLAG_SERIALIZED) != 0
310
325
  value
311
- rescue TypeError, ArgumentError
312
- raise DalliError, "Unable to unmarshal value: #{$!.message}"
326
+ rescue TypeError
327
+ raise if $!.message !~ /needs to have method `_load'|exception class\/object expected|instance of IO needed|incompatible marshal file format/
328
+ raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
329
+ rescue ArgumentError
330
+ raise if $!.message !~ /undefined class|marshal data too short/
331
+ raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
313
332
  rescue Zlib::Error
314
- raise DalliError, "Unable to uncompress value: #{$!.message}"
333
+ raise UnmarshalError, "Unable to uncompress value: #{$!.message}"
315
334
  end
316
335
 
317
336
  def cas_response
@@ -335,6 +354,10 @@ module Dalli
335
354
  NORMAL_HEADER = '@4CCnN'
336
355
  KV_HEADER = '@2n@6nN'
337
356
 
357
+ def under_max_value_size?(value)
358
+ value.bytesize <= @options[:value_max_bytes]
359
+ end
360
+
338
361
  def generic_response(unpack=false)
339
362
  header = read(24)
340
363
  raise Dalli::NetworkError, 'No response' if !header
@@ -1,3 +1,3 @@
1
1
  module Dalli
2
- VERSION = '2.3.0'
2
+ VERSION = '2.5.0'
3
3
  end
@@ -2,17 +2,17 @@ $TESTING = true
2
2
  require 'rubygems'
3
3
  # require 'simplecov'
4
4
  # SimpleCov.start
5
+ require 'minitest/pride'
6
+ require 'minitest/autorun'
7
+ require 'mocha/setup'
8
+ require 'mini_shoulda'
9
+ require 'memcached_mock'
10
+
5
11
  WANT_RAILS_VERSION = ENV['RAILS_VERSION'] || '>= 3.0.0'
6
12
  gem 'rails', WANT_RAILS_VERSION
7
13
  require 'rails'
8
14
  puts "Testing with Rails #{Rails.version}"
9
15
 
10
- require 'mini_shoulda'
11
- require 'minitest/pride'
12
- require 'minitest/autorun'
13
- require 'memcached_mock'
14
- require 'mocha'
15
-
16
16
  require 'dalli'
17
17
  require 'logger'
18
18
 
@@ -138,6 +138,11 @@ describe 'ActiveSupport' do
138
138
 
139
139
  dres = @dalli.delete(user)
140
140
  assert_equal true, dres
141
+
142
+ bigkey = '123456789012345678901234567890'
143
+ @dalli.write(bigkey, 'double width')
144
+ assert_equal 'double width', @dalli.read(bigkey)
145
+ assert_equal({bigkey => "double width"}, @dalli.read_multi(bigkey))
141
146
  end
142
147
  end
143
148
  end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+ require 'json'
4
+ require 'memcached_mock'
5
+
6
+ class NoopCompressor
7
+ def self.compress(data)
8
+ data
9
+ end
10
+
11
+ def self.decompress(data)
12
+ data
13
+ end
14
+ end
15
+
16
+ describe 'Compressor' do
17
+
18
+ should 'default to Dalli::Compressor' do
19
+ assert_equal Dalli::Compressor, Dalli.compressor
20
+ end
21
+
22
+ should 'support a custom compressor' do
23
+ original_compressor = Dalli.compressor
24
+ begin
25
+ Dalli.compressor = NoopCompressor
26
+ assert_equal NoopCompressor, Dalli.compressor
27
+
28
+ memcached(19127) do |dc|
29
+ assert dc.set("string-test", "a test string")
30
+ assert_equal("a test string", dc.get("string-test"))
31
+ end
32
+ ensure
33
+ Dalli.compressor = original_compressor
34
+ end
35
+ end
36
+ end
@@ -27,18 +27,19 @@ describe 'Dalli' do
27
27
 
28
28
  describe 'key validation' do
29
29
  should 'not allow blanks' do
30
- dc = Dalli::Client.new
31
- dc.set ' ', 1
32
- assert_equal 1, dc.get(' ')
33
- dc.set "\t", 1
34
- assert_equal 1, dc.get("\t")
35
- dc.set "\n", 1
36
- assert_equal 1, dc.get("\n")
37
- assert_raises ArgumentError do
38
- dc.set "", 1
39
- end
40
- assert_raises ArgumentError do
41
- dc.set nil, 1
30
+ memcached do |dc|
31
+ dc.set ' ', 1
32
+ assert_equal 1, dc.get(' ')
33
+ dc.set "\t", 1
34
+ assert_equal 1, dc.get("\t")
35
+ dc.set "\n", 1
36
+ assert_equal 1, dc.get("\n")
37
+ assert_raises ArgumentError do
38
+ dc.set "", 1
39
+ end
40
+ assert_raises ArgumentError do
41
+ dc.set nil, 1
42
+ end
42
43
  end
43
44
  end
44
45
  end
@@ -73,11 +74,7 @@ describe 'Dalli' do
73
74
  dc.flush
74
75
 
75
76
  val1 = "1234567890"*105000
76
- assert_error Dalli::DalliError, /too large/ do
77
- dc.set('a', val1)
78
- val2 = dc.get('a')
79
- assert_equal val1, val2
80
- end
77
+ assert_equal false, dc.set('a', val1)
81
78
 
82
79
  val1 = "1234567890"*100000
83
80
  dc.set('a', val1)
@@ -275,7 +272,7 @@ describe 'Dalli' do
275
272
 
276
273
  # rollover the 64-bit value, we'll get something undefined.
277
274
  resp = dc.incr('big', 1)
278
- 0x10000000000000000.wont_equal resp
275
+ refute_equal 0x10000000000000000, resp
279
276
  dc.reset
280
277
  end
281
278
  end
@@ -327,7 +324,7 @@ describe 'Dalli' do
327
324
  should "pass a simple smoke test" do
328
325
  memcached do |dc|
329
326
  resp = dc.flush
330
- resp.wont_be_nil
327
+ refute_nil resp
331
328
  assert_equal [true, true], resp
332
329
 
333
330
  assert_equal true, dc.set(:foo, 'bar')
@@ -348,7 +345,7 @@ describe 'Dalli' do
348
345
  dc.prepend('123', '0')
349
346
  dc.append('123', '0')
350
347
 
351
- assert_raises Dalli::DalliError do
348
+ assert_raises Dalli::UnmarshalError do
352
349
  resp = dc.get('123')
353
350
  end
354
351
 
@@ -403,9 +400,10 @@ describe 'Dalli' do
403
400
  cache.set('b', 11)
404
401
  inc = cache.incr('cat', 10, 0, 10)
405
402
  cache.set('f', 'zzz')
406
- wont_be_nil(cache.cas('f') do |value|
403
+ res = cache.cas('f') do |value|
407
404
  value << 'z'
408
- end)
405
+ end
406
+ refute_nil res
409
407
  assert_equal false, cache.add('a', 11)
410
408
  assert_equal({ 'a' => 9, 'b' => 11 }, cache.get_multi(['a', 'b']))
411
409
  inc = cache.incr('cat', 10)
@@ -469,9 +467,7 @@ describe 'Dalli' do
469
467
  dalli = Dalli::Client.new(dc.instance_variable_get(:@servers), :compress => true)
470
468
 
471
469
  value = "0"*1024*1024
472
- assert_raises Dalli::DalliError, /too large/ do
473
- dc.set('verylarge', value)
474
- end
470
+ assert_equal false, dc.set('verylarge', value)
475
471
  dalli.set('verylarge', value)
476
472
  end
477
473
  end
@@ -6,6 +6,13 @@ require 'rack/mock'
6
6
  require 'thread'
7
7
 
8
8
  describe Rack::Session::Dalli do
9
+ Rack::Session::Dalli::DEFAULT_OPTIONS[:memcache_server] = 'localhost:19129'
10
+
11
+ before do
12
+ memcached(19129) do
13
+ end
14
+ end
15
+
9
16
  session_key = Rack::Session::Dalli::DEFAULT_OPTIONS[:key]
10
17
  session_match = /#{session_key}=([0-9a-fA-F]+);/
11
18
  incrementor = lambda do |env|
@@ -15,7 +15,7 @@ describe 'Serializer' do
15
15
  Dalli.serializer = JSON
16
16
  assert_equal JSON, Dalli.serializer
17
17
 
18
- memcached(19127) do |dc|
18
+ memcached(19128) do |dc|
19
19
  assert dc.set("json_test", {"foo" => "bar"})
20
20
  assert_equal({"foo" => "bar"}, dc.get("json_test"))
21
21
  end
metadata CHANGED
@@ -1,130 +1,138 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.5.0
4
5
  prerelease:
5
- version: 2.3.0
6
6
  platform: ruby
7
- authors:
8
- - Mike Perham
7
+ authors:
8
+ - Mike Perham
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2012-10-15 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: mini_shoulda
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
19
- none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
24
- type: :development
25
- version_requirements: *id001
26
- - !ruby/object:Gem::Dependency
27
- name: mocha
28
- prerelease: false
29
- requirement: &id002 !ruby/object:Gem::Requirement
30
- none: false
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: "0"
35
- type: :development
36
- version_requirements: *id002
37
- - !ruby/object:Gem::Dependency
38
- name: rails
39
- prerelease: false
40
- requirement: &id003 !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: "3"
46
- type: :development
47
- version_requirements: *id003
12
+ date: 2012-11-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mini_shoulda
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mocha
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
48
62
  description: High performance memcached client for Ruby
49
63
  email: mperham@gmail.com
50
64
  executables: []
51
-
52
65
  extensions: []
53
-
54
66
  extra_rdoc_files: []
55
-
56
- files:
57
- - lib/dalli.rb
58
- - lib/action_dispatch/middleware/session/dalli_store.rb
59
- - lib/active_support/cache/dalli_store.rb
60
- - lib/dalli/client.rb
61
- - lib/dalli/options.rb
62
- - lib/dalli/railtie.rb
63
- - lib/dalli/ring.rb
64
- - lib/dalli/server.rb
65
- - lib/dalli/socket.rb
66
- - lib/dalli/version.rb
67
- - lib/rack/session/dalli.rb
68
- - LICENSE
69
- - README.md
70
- - History.md
71
- - Rakefile
72
- - Gemfile
73
- - dalli.gemspec
74
- - Performance.md
75
- - test/abstract_unit.rb
76
- - test/benchmark_test.rb
77
- - test/helper.rb
78
- - test/memcached_mock.rb
79
- - test/test_active_support.rb
80
- - test/test_dalli.rb
81
- - test/test_encoding.rb
82
- - test/test_failover.rb
83
- - test/test_network.rb
84
- - test/test_rack_session.rb
85
- - test/test_ring.rb
86
- - test/test_sasl.rb
87
- - test/test_serializer.rb
88
- - test/test_session_store.rb
67
+ files:
68
+ - lib/action_dispatch/middleware/session/dalli_store.rb
69
+ - lib/active_support/cache/dalli_store.rb
70
+ - lib/dalli/client.rb
71
+ - lib/dalli/compressor.rb
72
+ - lib/dalli/options.rb
73
+ - lib/dalli/railtie.rb
74
+ - lib/dalli/ring.rb
75
+ - lib/dalli/server.rb
76
+ - lib/dalli/socket.rb
77
+ - lib/dalli/version.rb
78
+ - lib/dalli.rb
79
+ - lib/rack/session/dalli.rb
80
+ - LICENSE
81
+ - README.md
82
+ - History.md
83
+ - Rakefile
84
+ - Gemfile
85
+ - dalli.gemspec
86
+ - Performance.md
87
+ - test/benchmark_test.rb
88
+ - test/helper.rb
89
+ - test/memcached_mock.rb
90
+ - test/test_active_support.rb
91
+ - test/test_compressor.rb
92
+ - test/test_dalli.rb
93
+ - test/test_encoding.rb
94
+ - test/test_failover.rb
95
+ - test/test_network.rb
96
+ - test/test_rack_session.rb
97
+ - test/test_ring.rb
98
+ - test/test_sasl.rb
99
+ - test/test_serializer.rb
89
100
  homepage: http://github.com/mperham/dalli
90
101
  licenses: []
91
-
92
102
  post_install_message:
93
- rdoc_options:
94
- - --charset=UTF-8
95
- require_paths:
96
- - lib
97
- required_ruby_version: !ruby/object:Gem::Requirement
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
98
108
  none: false
99
- requirements:
100
- - - ">="
101
- - !ruby/object:Gem::Version
102
- version: "0"
103
- required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
114
  none: false
105
- requirements:
106
- - - ">="
107
- - !ruby/object:Gem::Version
108
- version: "0"
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
109
119
  requirements: []
110
-
111
120
  rubyforge_project:
112
- rubygems_version: 1.8.15
121
+ rubygems_version: 1.8.24
113
122
  signing_key:
114
123
  specification_version: 3
115
124
  summary: High performance memcached client for Ruby
116
- test_files:
117
- - test/abstract_unit.rb
118
- - test/benchmark_test.rb
119
- - test/helper.rb
120
- - test/memcached_mock.rb
121
- - test/test_active_support.rb
122
- - test/test_dalli.rb
123
- - test/test_encoding.rb
124
- - test/test_failover.rb
125
- - test/test_network.rb
126
- - test/test_rack_session.rb
127
- - test/test_ring.rb
128
- - test/test_sasl.rb
129
- - test/test_serializer.rb
130
- - test/test_session_store.rb
125
+ test_files:
126
+ - test/benchmark_test.rb
127
+ - test/helper.rb
128
+ - test/memcached_mock.rb
129
+ - test/test_active_support.rb
130
+ - test/test_compressor.rb
131
+ - test/test_dalli.rb
132
+ - test/test_encoding.rb
133
+ - test/test_failover.rb
134
+ - test/test_network.rb
135
+ - test/test_rack_session.rb
136
+ - test/test_ring.rb
137
+ - test/test_sasl.rb
138
+ - test/test_serializer.rb
@@ -1,281 +0,0 @@
1
- # Used to test the full Rails stack.
2
- # Stolen from the Rails 3.0 source.
3
- # Needed for the session store tests.
4
- require 'active_support/core_ext/kernel/reporting'
5
- require 'active_support/core_ext/string/encoding'
6
- if "ruby".encoding_aware?
7
- # These are the normal settings that will be set up by Railties
8
- # TODO: Have these tests support other combinations of these values
9
- silence_warnings do
10
- Encoding.default_internal = "UTF-8"
11
- Encoding.default_external = "UTF-8"
12
- end
13
- end
14
-
15
- require 'test/unit'
16
- require 'abstract_controller'
17
- require 'action_controller'
18
- require 'action_view'
19
- require 'action_dispatch'
20
- require 'active_support/dependencies'
21
- require 'action_controller/caching'
22
- require 'action_controller/caching/sweeping'
23
-
24
- require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
25
-
26
- module Rails
27
- def self.logger
28
- @logger ||= begin
29
- l = Logger.new(STDOUT)
30
- l.level = Logger::INFO; l
31
- end
32
- end
33
- end
34
-
35
- # Monkey patch the old routes initialization to be silenced.
36
- class ActionDispatch::Routing::DeprecatedMapper
37
- def initialize_with_silencer(*args)
38
- ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) }
39
- end
40
- alias_method_chain :initialize, :silencer
41
- end
42
-
43
- ActiveSupport::Dependencies.hook!
44
-
45
- # Show backtraces for deprecated behavior for quicker cleanup.
46
- ActiveSupport::Deprecation.debug = true
47
-
48
- ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
49
-
50
- module RackTestUtils
51
- def body_to_string(body)
52
- if body.respond_to?(:each)
53
- str = ""
54
- body.each {|s| str << s }
55
- str
56
- else
57
- body
58
- end
59
- end
60
- extend self
61
- end
62
-
63
- module SetupOnce
64
- extend ActiveSupport::Concern
65
-
66
- included do
67
- cattr_accessor :setup_once_block
68
- self.setup_once_block = nil
69
-
70
- setup :run_setup_once
71
- end
72
-
73
- module ClassMethods
74
- def setup_once(&block)
75
- self.setup_once_block = block
76
- end
77
- end
78
-
79
- private
80
- def run_setup_once
81
- if self.setup_once_block
82
- self.setup_once_block.call
83
- self.setup_once_block = nil
84
- end
85
- end
86
- end
87
-
88
- SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
89
-
90
- module ActiveSupport
91
- class TestCase
92
- include SetupOnce
93
- # Hold off drawing routes until all the possible controller classes
94
- # have been loaded.
95
- setup_once do
96
- SharedTestRoutes.draw do
97
- match ':controller(/:action(/:id))'
98
- end
99
-
100
- ActionController::IntegrationTest.app.routes.draw do
101
- match ':controller(/:action(/:id))'
102
- end
103
- end
104
- end
105
- end
106
-
107
- class RoutedRackApp
108
- attr_reader :routes
109
-
110
- def initialize(routes, &blk)
111
- @routes = routes
112
- @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
113
- end
114
-
115
- def call(env)
116
- @stack.call(env)
117
- end
118
- end
119
-
120
- class BasicController
121
- attr_accessor :request
122
-
123
- def config
124
- @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
125
- # VIEW TODO: View tests should not require a controller
126
- public_dir = File.expand_path("../fixtures/public", __FILE__)
127
- config.assets_dir = public_dir
128
- config.javascripts_dir = "#{public_dir}/javascripts"
129
- config.stylesheets_dir = "#{public_dir}/stylesheets"
130
- config
131
- end
132
- end
133
- end
134
-
135
- class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
136
- setup do
137
- @routes = SharedTestRoutes
138
- end
139
- end
140
-
141
- class ActionController::IntegrationTest < ActiveSupport::TestCase
142
- def self.build_app(routes = nil)
143
- RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
144
- middleware.use "ActionDispatch::Callbacks"
145
- middleware.use "ActionDispatch::ParamsParser"
146
- middleware.use "ActionDispatch::Cookies"
147
- middleware.use "ActionDispatch::Flash"
148
- middleware.use "ActionDispatch::Head"
149
- yield(middleware) if block_given?
150
- end
151
- end
152
-
153
- self.app = build_app
154
-
155
- # Stub Rails dispatcher so it does not get controller references and
156
- # simply return the controller#action as Rack::Body.
157
- class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
158
- protected
159
- def controller_reference(controller_param)
160
- controller_param
161
- end
162
-
163
- def dispatch(controller, action, env)
164
- [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
165
- end
166
- end
167
-
168
- def self.stub_controllers
169
- old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
170
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
171
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
172
- yield ActionDispatch::Routing::RouteSet.new
173
- ensure
174
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
175
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
176
- end
177
-
178
- def with_routing(&block)
179
- temporary_routes = ActionDispatch::Routing::RouteSet.new
180
- old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes)
181
- old_routes = SharedTestRoutes
182
- silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) }
183
-
184
- yield temporary_routes
185
- ensure
186
- self.class.app = old_app
187
- silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
188
- end
189
-
190
- def with_autoload_path(path)
191
- path = File.join(File.dirname(__FILE__), "fixtures", path)
192
- if ActiveSupport::Dependencies.autoload_paths.include?(path)
193
- yield
194
- else
195
- begin
196
- ActiveSupport::Dependencies.autoload_paths << path
197
- yield
198
- ensure
199
- ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
200
- ActiveSupport::Dependencies.clear
201
- end
202
- end
203
- end
204
- end
205
-
206
- # Temporary base class
207
- class Rack::TestCase < ActionController::IntegrationTest
208
- def self.testing(klass = nil)
209
- if klass
210
- @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '')
211
- else
212
- @testing
213
- end
214
- end
215
-
216
- def get(thing, *args)
217
- if thing.is_a?(Symbol)
218
- super("#{self.class.testing}/#{thing}", *args)
219
- else
220
- super
221
- end
222
- end
223
-
224
- def assert_body(body)
225
- assert_equal body, Array.wrap(response.body).join
226
- end
227
-
228
- def assert_status(code)
229
- assert_equal code, response.status
230
- end
231
-
232
- def assert_response(body, status = 200, headers = {})
233
- assert_body body
234
- assert_status status
235
- headers.each do |header, value|
236
- assert_header header, value
237
- end
238
- end
239
-
240
- def assert_content_type(type)
241
- assert_equal type, response.headers["Content-Type"]
242
- end
243
-
244
- def assert_header(name, value)
245
- assert_equal value, response.headers[name]
246
- end
247
- end
248
-
249
- class ActionController::Base
250
- def self.test_routes(&block)
251
- routes = ActionDispatch::Routing::RouteSet.new
252
- routes.draw(&block)
253
- include routes.url_helpers
254
- end
255
- end
256
-
257
- class ::ApplicationController < ActionController::Base
258
- end
259
-
260
- module ActionController
261
- class Base
262
- include ActionController::Testing
263
- end
264
-
265
- Base.view_paths = []
266
-
267
- class TestCase
268
- include ActionDispatch::TestProcess
269
-
270
- setup do
271
- @routes = SharedTestRoutes
272
- end
273
- end
274
- end
275
-
276
- # This stub emulates the Railtie including the URL helpers from a Rails application
277
- module ActionController
278
- class Base
279
- include SharedTestRoutes.url_helpers
280
- end
281
- end
@@ -1,262 +0,0 @@
1
- require 'helper'
2
- require 'abstract_unit'
3
- require 'action_dispatch/middleware/session/dalli_store'
4
-
5
- class Foo
6
- def initialize(bar='baz')
7
- @bar = bar
8
- end
9
- def inspect
10
- "#<#{self.class} bar:#{@bar.inspect}>"
11
- end
12
- end
13
-
14
- class TestSessionStore < ActionController::IntegrationTest
15
- include MemcachedMock::Helper
16
-
17
- class TestController < ActionController::Base
18
- def no_session_access
19
- head :ok
20
- end
21
-
22
- def set_session_value
23
- session[:foo] = "bar"
24
- head :ok
25
- end
26
-
27
- def set_serialized_session_value
28
- session[:foo] = Foo.new
29
- head :ok
30
- end
31
-
32
- def get_session_value
33
- render :text => "foo: #{session[:foo].inspect}"
34
- end
35
-
36
- def get_session_id
37
- render :text => "#{request.session_options[:id]}"
38
- end
39
-
40
- def call_reset_session
41
- session[:bar]
42
- reset_session
43
- session[:bar] = "baz"
44
- head :ok
45
- end
46
-
47
- def rescue_action(e) raise end
48
- end
49
-
50
- begin
51
- require 'dalli'
52
- memcache = Dalli::Client.new('127.0.0.1:11211')
53
- memcache.set('ping', '')
54
-
55
- def test_setting_and_getting_session_value
56
- with_test_route_set do
57
- get '/set_session_value'
58
- assert_response :success
59
- assert cookies['_session_id']
60
-
61
- get '/get_session_value'
62
- assert_response :success
63
- assert_equal 'foo: "bar"', response.body
64
- end
65
- end
66
-
67
- def test_getting_nil_session_value
68
- with_test_route_set do
69
- get '/get_session_value'
70
- assert_response :success
71
- assert_equal 'foo: nil', response.body
72
- end
73
- end
74
-
75
- def test_getting_session_value_after_session_reset
76
- with_test_route_set do
77
- get '/set_session_value'
78
- assert_response :success
79
- assert cookies['_session_id']
80
- session_cookie = cookies.send(:hash_for)['_session_id']
81
-
82
- get '/call_reset_session'
83
- assert_response :success
84
- assert_not_equal [], headers['Set-Cookie']
85
-
86
- cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
87
-
88
- get '/get_session_value'
89
- assert_response :success
90
- assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
91
- end
92
- end
93
-
94
- def test_getting_from_nonexistent_session
95
- with_test_route_set do
96
- get '/get_session_value'
97
- assert_response :success
98
- assert_equal 'foo: nil', response.body
99
- assert_nil cookies['_session_id'], "should only create session on write, not read"
100
- end
101
- end
102
-
103
- def test_setting_session_value_after_session_reset
104
- with_test_route_set do
105
- get '/set_session_value'
106
- assert_response :success
107
- assert cookies['_session_id']
108
- session_id = cookies['_session_id']
109
-
110
- get '/call_reset_session'
111
- assert_response :success
112
- assert_not_equal [], headers['Set-Cookie']
113
-
114
- get '/get_session_value'
115
- assert_response :success
116
- assert_equal 'foo: nil', response.body
117
-
118
- get '/get_session_id'
119
- assert_response :success
120
- assert_not_equal session_id, response.body
121
- end
122
- end
123
-
124
- def test_getting_session_id
125
- with_test_route_set do
126
- get '/set_session_value'
127
- assert_response :success
128
- assert cookies['_session_id']
129
- session_id = cookies['_session_id']
130
-
131
- get '/get_session_id'
132
- assert_response :success
133
- assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
134
- end
135
- end
136
-
137
- def test_deserializes_unloaded_class
138
- with_test_route_set do
139
- with_autoload_path "session_autoload_test" do
140
- get '/set_serialized_session_value'
141
- assert_response :success
142
- assert cookies['_session_id']
143
- end
144
- with_autoload_path "session_autoload_test" do
145
- get '/get_session_id'
146
- assert_response :success
147
- end
148
- with_autoload_path "session_autoload_test" do
149
- get '/get_session_value'
150
- assert_response :success
151
- assert_equal 'foo: #<Foo bar:"baz">', response.body, "should auto-load unloaded class"
152
- end
153
- end
154
- end
155
-
156
- def test_doesnt_write_session_cookie_if_session_id_is_already_exists
157
- with_test_route_set do
158
- get '/set_session_value'
159
- assert_response :success
160
- assert cookies['_session_id']
161
-
162
- get '/get_session_value'
163
- assert_response :success
164
- assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
165
- end
166
- end
167
-
168
- def test_prevents_session_fixation
169
- with_test_route_set do
170
- get '/get_session_value'
171
- assert_response :success
172
- assert_equal 'foo: nil', response.body
173
- session_id = cookies['_session_id']
174
-
175
- reset!
176
-
177
- get '/set_session_value', :_session_id => session_id
178
- assert_response :success
179
- assert_not_equal session_id, cookies['_session_id']
180
- end
181
- end
182
-
183
- def test_expire_after
184
- with_test_route_set(:expire_after => 1) do
185
- get '/set_session_value'
186
- assert_match(/expires/, @response.headers['Set-Cookie'])
187
-
188
- sleep(1)
189
-
190
- get '/get_session_value'
191
- assert_equal 'foo: nil', response.body
192
- end
193
- end
194
-
195
- def test_expires_in
196
- with_test_route_set(:expires_in => 1) do
197
- get '/set_session_value'
198
- assert_no_match(/expires/, @response.headers['Set-Cookie'])
199
-
200
- sleep(1)
201
-
202
- get '/get_session_value'
203
- assert_equal 'foo: nil', response.body
204
- end
205
- end
206
-
207
- def test_without_raise_errors_option
208
- memcached(29125) do
209
- with_test_route_set(:memcache_server => '127.0.0.1:29125') do
210
- get '/set_session_value'
211
- assert_response :success
212
-
213
- get '/get_session_value'
214
- assert_response :success
215
- assert_equal 'foo: "bar"', response.body
216
-
217
- memcached_kill(29125)
218
-
219
- get '/get_session_value'
220
- assert_response :success
221
- assert_equal 'foo: nil', response.body
222
- end
223
- end
224
- end
225
-
226
- def test_with_raise_errors_option
227
- memcached(29125) do
228
- with_test_route_set(:memcache_server => '127.0.0.1:29125', :raise_errors => true) do
229
- get '/set_session_value'
230
- assert_response :success
231
-
232
- memcached_kill(29125)
233
-
234
- exception = [Dalli::RingError, { :message => "No server available" }]
235
-
236
- assert_raises(*exception) { get '/get_session_value' }
237
- assert_raises(*exception) { get '/set_session_value' }
238
- assert_raises(*exception) { get '/call_reset_session' }
239
- end
240
- end
241
- end
242
- rescue LoadError, RuntimeError
243
- $stderr.puts "Skipping SessionStore tests. Start memcached and try again: #{$!.message}"
244
- end
245
-
246
- private
247
-
248
- def with_test_route_set(options = {})
249
- options = {:key => '_session_id', :memcache_server => '127.0.0.1:11211'}.merge(options)
250
- with_routing do |set|
251
- set.draw do
252
- match ':action', :to => ::TestSessionStore::TestController
253
- end
254
-
255
- @app = self.class.build_app(set) do |middleware|
256
- middleware.use ActionDispatch::Session::DalliStore, options
257
- end
258
-
259
- yield
260
- end
261
- end
262
- end