libmemcached_store 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.travis.yml +4 -1
- data/CHANGELOG.md +5 -0
- data/gemfiles/rails40.gemfile +7 -0
- data/lib/action_dispatch/session/libmemcached_store.rb +1 -1
- data/lib/active_support/cache/libmemcached_store.rb +75 -20
- data/lib/version.rb +1 -1
- data/libmemcached_store.gemspec +1 -0
- data/test/action_dispatch/abstract_unit.rb +11 -3
- data/test/action_dispatch/libmemcached_store_test.rb +12 -12
- data/test/active_support/libmemcached_store_test.rb +287 -240
- data/test/test_helper.rb +3 -2
- metadata +19 -2
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -9,7 +9,7 @@ module ActionDispatch
|
|
9
9
|
options[:expire_after] ||= options[:expires]
|
10
10
|
super
|
11
11
|
client_options = { default_ttl: options.fetch(:expire_after, 0) }
|
12
|
-
client_options[:
|
12
|
+
client_options[:prefix_key] = options[:prefix_key] || options[:namespace] || 'rack:session'
|
13
13
|
@mutex = Mutex.new
|
14
14
|
@pool = options[:cache] || Memcached.new(@default_options[:memcache_server], client_options)
|
15
15
|
end
|
@@ -27,6 +27,23 @@ module ActiveSupport
|
|
27
27
|
include GetWithFlags
|
28
28
|
end
|
29
29
|
|
30
|
+
class FetchWithRaceConditionTTLEntry
|
31
|
+
attr_accessor :value, :extended
|
32
|
+
|
33
|
+
def initialize(value, expires_in)
|
34
|
+
@value, @extended = value, false
|
35
|
+
@expires_at = Time.now.to_i + expires_in
|
36
|
+
end
|
37
|
+
|
38
|
+
def expires_in
|
39
|
+
[@expires_at - Time.now.to_i, 1].max # never set to 0 -> never expires
|
40
|
+
end
|
41
|
+
|
42
|
+
def expired?
|
43
|
+
@expires_at <= Time.now.to_i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
30
47
|
attr_reader :addresses
|
31
48
|
|
32
49
|
DEFAULT_CLIENT_OPTIONS = { distribution: :consistent_ketama, binary_protocol: true, default_ttl: 0 }
|
@@ -64,40 +81,69 @@ module ActiveSupport
|
|
64
81
|
end
|
65
82
|
client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]
|
66
83
|
|
67
|
-
@options =
|
84
|
+
@options = {compress_threshold: DEFAULT_COMPRESS_THRESHOLD}.merge(options)
|
68
85
|
@addresses = addresses
|
69
|
-
@cache = MemcachedWithFlags.new(@addresses,
|
86
|
+
@cache = MemcachedWithFlags.new(@addresses, DEFAULT_CLIENT_OPTIONS.merge(client_options))
|
70
87
|
end
|
71
88
|
|
72
|
-
def fetch(key, options = nil)
|
89
|
+
def fetch(key, options = nil, &block)
|
73
90
|
if block_given?
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
91
|
+
if options && options[:race_condition_ttl]
|
92
|
+
fetch_with_race_condition_ttl(key, options, &block)
|
93
|
+
else
|
94
|
+
key = expanded_key(key)
|
95
|
+
unless options && options[:force]
|
96
|
+
entry = instrument(:read, key, options) do |payload|
|
97
|
+
read_entry(key, options).tap do |result|
|
98
|
+
if payload
|
99
|
+
payload[:super_operation] = :fetch
|
100
|
+
payload[:hit] = !!result
|
101
|
+
end
|
81
102
|
end
|
82
103
|
end
|
83
104
|
end
|
84
|
-
end
|
85
105
|
|
86
|
-
|
87
|
-
|
88
|
-
|
106
|
+
if entry.nil?
|
107
|
+
result = instrument(:generate, key, options) do |payload|
|
108
|
+
yield
|
109
|
+
end
|
110
|
+
write_entry(key, result, options)
|
111
|
+
result
|
112
|
+
else
|
113
|
+
instrument(:fetch_hit, key, options) { |payload| }
|
114
|
+
entry
|
89
115
|
end
|
90
|
-
write_entry(key, result, options)
|
91
|
-
result
|
92
|
-
else
|
93
|
-
instrument(:fetch_hit, key, options) { |payload| }
|
94
|
-
entry
|
95
116
|
end
|
96
117
|
else
|
97
118
|
read(key, options)
|
98
119
|
end
|
99
120
|
end
|
100
121
|
|
122
|
+
def fetch_with_race_condition_ttl(key, options={}, &block)
|
123
|
+
options = options.dup
|
124
|
+
|
125
|
+
race_ttl = options.delete(:race_condition_ttl) || raise("Use :race_condition_ttl option or normal fetch")
|
126
|
+
expires_in = options.fetch(:expires_in)
|
127
|
+
options[:expires_in] = expires_in + race_ttl
|
128
|
+
options[:preserve_race_condition_entry] = true
|
129
|
+
|
130
|
+
value = fetch(key, options) { FetchWithRaceConditionTTLEntry.new(yield, expires_in) }
|
131
|
+
|
132
|
+
return value unless value.is_a?(FetchWithRaceConditionTTLEntry)
|
133
|
+
|
134
|
+
if value.expired? && !value.extended
|
135
|
+
# we take care of refreshing the cache, all others should keep reading
|
136
|
+
value.extended = true
|
137
|
+
write(key, value, options.merge(:expires_in => value.expires_in + race_ttl))
|
138
|
+
|
139
|
+
# calculate new value and store it
|
140
|
+
value = FetchWithRaceConditionTTLEntry.new(yield, expires_in)
|
141
|
+
write(key, value, options)
|
142
|
+
end
|
143
|
+
|
144
|
+
value.value
|
145
|
+
end
|
146
|
+
|
101
147
|
def read(key, options = nil)
|
102
148
|
key = expanded_key(key)
|
103
149
|
instrument(:read, key, options) do |payload|
|
@@ -191,7 +237,8 @@ module ActiveSupport
|
|
191
237
|
def read_entry(key, options = nil)
|
192
238
|
options ||= {}
|
193
239
|
raw_value, flags = @cache.get(escape_and_normalize(key), false, true)
|
194
|
-
deserialize(raw_value, options[:raw], flags)
|
240
|
+
value = deserialize(raw_value, options[:raw], flags)
|
241
|
+
convert_race_condition_entry(value, options)
|
195
242
|
rescue Memcached::NotFound
|
196
243
|
nil
|
197
244
|
rescue Memcached::Error => e
|
@@ -229,6 +276,14 @@ module ActiveSupport
|
|
229
276
|
|
230
277
|
private
|
231
278
|
|
279
|
+
def convert_race_condition_entry(value, options)
|
280
|
+
if !options[:preserve_race_condition_entry] && value.is_a?(FetchWithRaceConditionTTLEntry)
|
281
|
+
value.value
|
282
|
+
else
|
283
|
+
value
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
232
287
|
def deserialize(value, raw = false, flags = 0)
|
233
288
|
value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
|
234
289
|
raw ? value : Marshal.load(value)
|
data/lib/version.rb
CHANGED
data/libmemcached_store.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.add_development_dependency('rack')
|
25
25
|
s.add_development_dependency('rake')
|
26
26
|
s.add_development_dependency('mocha')
|
27
|
+
s.add_development_dependency('minitest', '~> 4.7')
|
27
28
|
s.add_development_dependency('activesupport', '>= 3')
|
28
29
|
s.add_development_dependency('actionpack', '>= 3')
|
29
30
|
end
|
@@ -3,7 +3,11 @@ require 'active_support/core_ext/module/delegation'
|
|
3
3
|
require 'active_support/core_ext/module/attribute_accessors'
|
4
4
|
require 'action_controller'
|
5
5
|
require 'action_dispatch/routing'
|
6
|
-
|
6
|
+
if ActionPack::VERSION::MAJOR >= 4
|
7
|
+
require 'rack/head'
|
8
|
+
else
|
9
|
+
require 'action_dispatch/middleware/head'
|
10
|
+
end
|
7
11
|
require 'action_dispatch/testing/assertions'
|
8
12
|
require 'action_dispatch/testing/test_process'
|
9
13
|
require 'action_dispatch/testing/integration'
|
@@ -36,8 +40,12 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
|
|
36
40
|
middleware.use "ActionDispatch::ParamsParser"
|
37
41
|
middleware.use "ActionDispatch::Cookies"
|
38
42
|
middleware.use "ActionDispatch::Flash"
|
39
|
-
|
43
|
+
if ActionPack::VERSION::MAJOR >= 4
|
44
|
+
middleware.use "Rack::Head"
|
45
|
+
else
|
46
|
+
middleware.use "ActionDispatch::Head"
|
47
|
+
end
|
40
48
|
yield(middleware) if block_given?
|
41
49
|
end
|
42
50
|
end
|
43
|
-
end
|
51
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative '../test_helper'
|
2
2
|
require File.expand_path('../abstract_unit', __FILE__)
|
3
3
|
require 'action_dispatch/session/libmemcached_store'
|
4
4
|
|
@@ -38,7 +38,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
41
|
+
test "setting and getting session value" do
|
42
42
|
with_test_route_set do
|
43
43
|
get '/set_session_value'
|
44
44
|
assert_response :success
|
@@ -50,7 +50,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
|
53
|
+
test "getting nil session value" do
|
54
54
|
with_test_route_set do
|
55
55
|
get '/get_session_value'
|
56
56
|
assert_response :success
|
@@ -58,7 +58,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
|
61
|
+
test "getting session value after session reset" do
|
62
62
|
with_test_route_set do
|
63
63
|
get '/set_session_value'
|
64
64
|
assert_response :success
|
@@ -77,7 +77,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
|
80
|
+
test "getting from nonexistent session" do
|
81
81
|
with_test_route_set do
|
82
82
|
get '/get_session_value'
|
83
83
|
assert_response :success
|
@@ -86,7 +86,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
|
89
|
+
test "setting session value after session reset" do
|
90
90
|
with_test_route_set do
|
91
91
|
get '/set_session_value'
|
92
92
|
assert_response :success
|
@@ -107,7 +107,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
110
|
-
|
110
|
+
test "getting session id" do
|
111
111
|
with_test_route_set do
|
112
112
|
get '/set_session_value'
|
113
113
|
assert_response :success
|
@@ -120,7 +120,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
123
|
-
|
123
|
+
test "deserializes unloaded class" do
|
124
124
|
with_test_route_set do
|
125
125
|
with_autoload_path "session_autoload_test" do
|
126
126
|
get '/set_serialized_session_value'
|
@@ -139,7 +139,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
-
|
142
|
+
test "doesnt write session cookie if session id is already exists" do
|
143
143
|
with_test_route_set do
|
144
144
|
get '/set_session_value'
|
145
145
|
assert_response :success
|
@@ -151,7 +151,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
154
|
-
|
154
|
+
test "prevents session fixation" do
|
155
155
|
with_test_route_set do
|
156
156
|
get '/get_session_value'
|
157
157
|
assert_response :success
|
@@ -171,7 +171,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
171
171
|
def with_test_route_set
|
172
172
|
with_routing do |set|
|
173
173
|
set.draw do
|
174
|
-
|
174
|
+
get ':action', :to => TestController
|
175
175
|
end
|
176
176
|
|
177
177
|
@app = self.class.build_app(set) do |middleware|
|
@@ -197,4 +197,4 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
|
|
197
197
|
end
|
198
198
|
end
|
199
199
|
end
|
200
|
-
end
|
200
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../test_helper'
|
4
4
|
require 'memcached'
|
5
5
|
require 'active_support'
|
6
6
|
require 'active_support/core_ext/module/aliasing'
|
@@ -8,318 +8,319 @@ require 'active_support/core_ext/object/duplicable'
|
|
8
8
|
require 'active_support/cache/libmemcached_store'
|
9
9
|
|
10
10
|
# Make it easier to get at the underlying cache options during testing.
|
11
|
-
|
11
|
+
ActiveSupport::Cache::LibmemcachedStore.class_eval do
|
12
12
|
def client_options
|
13
13
|
@cache.options
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
describe ActiveSupport::Cache::LibmemcachedStore do
|
18
|
+
class MockUser
|
19
|
+
def cache_key
|
20
|
+
'foo'
|
21
|
+
end
|
20
22
|
end
|
21
|
-
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
@cache.
|
26
|
-
@cache.
|
27
|
-
assert_equal 'bar', @cache.fetch('foo') { 'baz' }
|
24
|
+
before do
|
25
|
+
@cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, expires_in: 60)
|
26
|
+
@cache.clear
|
27
|
+
@cache.silence!
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
describe "cache store behavior" do
|
31
|
+
def really_long_keys_test
|
32
|
+
key = "a" * 251
|
33
|
+
assert @cache.write(key, "bar")
|
34
|
+
assert_equal "bar", @cache.read(key)
|
35
|
+
assert_equal "bar", @cache.fetch(key)
|
36
|
+
assert_nil @cache.read("#{key}x")
|
37
|
+
assert_equal({key => "bar"}, @cache.read_multi(key))
|
38
|
+
assert @cache.delete(key)
|
39
|
+
refute @cache.exist?(key)
|
40
|
+
assert @cache.write(key, '2', :raw => true)
|
41
|
+
assert_equal 3, @cache.increment(key)
|
42
|
+
assert_equal 2, @cache.decrement(key)
|
43
|
+
end
|
34
44
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
45
|
+
it "fetch_without_cache_miss" do
|
46
|
+
@cache.write('foo', 'bar')
|
47
|
+
@cache.expects(:write_entry).never
|
48
|
+
assert_equal 'bar', @cache.fetch('foo') { 'baz' }
|
49
|
+
end
|
41
50
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
51
|
+
it "fetch_with_cache_miss" do
|
52
|
+
@cache.expects(:write_entry).with('foo', 'baz', nil)
|
53
|
+
assert_equal 'baz', @cache.fetch('foo') { 'baz' }
|
54
|
+
end
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
it "fetch_with_forced_cache_miss" do
|
57
|
+
@cache.write('foo', 'bar')
|
58
|
+
@cache.expects(:read_entry).never
|
59
|
+
@cache.expects(:write_entry).with('foo', 'baz', force: true)
|
60
|
+
assert_equal 'baz', @cache.fetch('foo', force: true) { 'baz' }
|
61
|
+
end
|
52
62
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
63
|
+
it "fetch_with_cached_false" do
|
64
|
+
@cache.write('foo', false)
|
65
|
+
refute @cache.fetch('foo') { raise }
|
66
|
+
end
|
58
67
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
68
|
+
it "fetch_with_raw_object" do
|
69
|
+
o = Object.new
|
70
|
+
o.instance_variable_set :@foo, 'bar'
|
71
|
+
assert_equal o, @cache.fetch('foo', raw: true) { o }
|
72
|
+
end
|
63
73
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
74
|
+
it "fetch_with_cache_key" do
|
75
|
+
u = MockUser.new
|
76
|
+
@cache.write(u.cache_key, 'bar')
|
77
|
+
assert_equal 'bar', @cache.fetch(u) { raise }
|
78
|
+
end
|
68
79
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
80
|
+
it "should_read_and_write_strings" do
|
81
|
+
assert @cache.write('foo', 'bar')
|
82
|
+
assert_equal 'bar', @cache.read('foo')
|
83
|
+
end
|
73
84
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
85
|
+
it "should_read_and_write_hash" do
|
86
|
+
assert @cache.write('foo', { a: 'b' })
|
87
|
+
assert_equal({ a: 'b' }, @cache.read('foo'))
|
88
|
+
end
|
78
89
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
90
|
+
it "should_read_and_write_integer" do
|
91
|
+
assert @cache.write('foo', 1)
|
92
|
+
assert_equal 1, @cache.read('foo')
|
93
|
+
end
|
83
94
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
95
|
+
it "should_read_and_write_nil" do
|
96
|
+
assert @cache.write('foo', nil)
|
97
|
+
assert_equal nil, @cache.read('foo')
|
98
|
+
end
|
88
99
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
100
|
+
it "should_read_and_write_false" do
|
101
|
+
assert @cache.write('foo', false)
|
102
|
+
assert_equal false, @cache.read('foo')
|
103
|
+
end
|
94
104
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
105
|
+
it "read_and_write_compressed_data" do
|
106
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
|
107
|
+
assert_equal 'bar', @cache.read('foo')
|
108
|
+
end
|
99
109
|
|
100
|
-
|
101
|
-
|
102
|
-
|
110
|
+
it "write_should_overwrite" do
|
111
|
+
@cache.write('foo', 'bar')
|
112
|
+
@cache.write('foo', 'baz')
|
113
|
+
assert_equal 'baz', @cache.read('foo')
|
114
|
+
end
|
103
115
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
116
|
+
it "write_compressed_data" do
|
117
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 1, :raw => true)
|
118
|
+
assert_equal Zlib::Deflate.deflate('bar'), @cache.instance_variable_get(:@cache).get('foo', false)
|
119
|
+
end
|
108
120
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
@cache.write('fud', 'biz')
|
113
|
-
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
114
|
-
end
|
121
|
+
it "read_miss" do
|
122
|
+
assert_nil @cache.read('foo')
|
123
|
+
end
|
115
124
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
125
|
+
it "read_should_return_a_different_object_id_each_time_it_is_called" do
|
126
|
+
@cache.write('foo', 'bar')
|
127
|
+
refute_equal @cache.read('foo').object_id, @cache.read('foo').object_id
|
128
|
+
end
|
121
129
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
130
|
+
it "read_multi" do
|
131
|
+
@cache.write('foo', 'bar')
|
132
|
+
@cache.write('fu', 'baz')
|
133
|
+
@cache.write('fud', 'biz')
|
134
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
135
|
+
end
|
127
136
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
it "read_multi_with_array" do
|
138
|
+
@cache.write('foo', 'bar')
|
139
|
+
@cache.write('fu', 'baz')
|
140
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi(['foo', 'fu']))
|
141
|
+
end
|
133
142
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
143
|
+
it "read_multi_with_raw" do
|
144
|
+
@cache.write('foo', 'bar', :raw => true)
|
145
|
+
@cache.write('fu', 'baz', :raw => true)
|
146
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
147
|
+
end
|
139
148
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
'foo'
|
149
|
+
it "read_multi_with_compress" do
|
150
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
|
151
|
+
@cache.write('fu', 'baz', :compress => true, :compress_threshold => 1)
|
152
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
144
153
|
end
|
145
|
-
@cache.write(obj, 'bar')
|
146
|
-
assert_equal 'bar', @cache.read('foo')
|
147
|
-
end
|
148
154
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
155
|
+
it "cache_key" do
|
156
|
+
o = MockUser.new
|
157
|
+
@cache.write(o, 'bar')
|
158
|
+
assert_equal 'bar', @cache.read('foo')
|
159
|
+
end
|
153
160
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
161
|
+
it "param_as_cache_key" do
|
162
|
+
obj = Object.new
|
163
|
+
def obj.to_param
|
164
|
+
'foo'
|
165
|
+
end
|
166
|
+
@cache.write(obj, 'bar')
|
167
|
+
assert_equal 'bar', @cache.read('foo')
|
168
|
+
end
|
158
169
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
170
|
+
it "array_as_cache_key" do
|
171
|
+
@cache.write([:fu, 'foo'], 'bar')
|
172
|
+
assert_equal 'bar', @cache.read('fu/foo')
|
173
|
+
end
|
163
174
|
|
164
|
-
|
165
|
-
|
166
|
-
|
175
|
+
it "hash_as_cache_key" do
|
176
|
+
@cache.write({:foo => 1, :fu => 2}, 'bar')
|
177
|
+
assert_equal 'bar', @cache.read('foo=1/fu=2')
|
178
|
+
end
|
167
179
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
180
|
+
it "keys_are_case_sensitive" do
|
181
|
+
@cache.write('foo', 'bar')
|
182
|
+
assert_nil @cache.read('FOO')
|
183
|
+
end
|
173
184
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
assert @cache.delete('foo')
|
178
|
-
refute @cache.exist?('foo')
|
179
|
-
end
|
185
|
+
it "keys_with_spaces" do
|
186
|
+
assert_equal 'baz', @cache.fetch('foo bar') { 'baz' }
|
187
|
+
end
|
180
188
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
189
|
+
it "exist" do
|
190
|
+
@cache.write('foo', 'bar')
|
191
|
+
assert @cache.exist?('foo')
|
192
|
+
refute @cache.exist?('bar')
|
193
|
+
end
|
186
194
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
195
|
+
it "delete" do
|
196
|
+
@cache.write('foo', 'bar')
|
197
|
+
assert @cache.exist?('foo')
|
198
|
+
assert @cache.delete('foo')
|
199
|
+
refute @cache.exist?('foo')
|
200
|
+
end
|
192
201
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
202
|
+
it "delete_with_unexistent_key" do
|
203
|
+
@cache.expects(:log_error).never
|
204
|
+
refute @cache.exist?('foo')
|
205
|
+
refute @cache.delete('foo')
|
206
|
+
end
|
198
207
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
assert @cache.delete(crazy_key)
|
205
|
-
refute @cache.exist?(crazy_key)
|
206
|
-
assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
|
207
|
-
assert_equal 3, @cache.increment(crazy_key)
|
208
|
-
assert_equal 2, @cache.decrement(crazy_key)
|
209
|
-
end
|
208
|
+
it "store_objects_should_be_immutable" do
|
209
|
+
@cache.write('foo', 'bar')
|
210
|
+
@cache.read('foo').gsub!(/.*/, 'baz')
|
211
|
+
assert_equal 'bar', @cache.read('foo')
|
212
|
+
end
|
210
213
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
assert_nil @cache.read("#{key}x")
|
217
|
-
assert_equal({key => "bar"}, @cache.read_multi(key))
|
218
|
-
assert @cache.delete(key)
|
219
|
-
refute @cache.exist?(key)
|
220
|
-
assert @cache.write(key, '2', :raw => true)
|
221
|
-
assert_equal 3, @cache.increment(key)
|
222
|
-
assert_equal 2, @cache.decrement(key)
|
223
|
-
end
|
214
|
+
it "original_store_objects_should_not_be_immutable" do
|
215
|
+
bar = 'bar'
|
216
|
+
@cache.write('foo', bar)
|
217
|
+
assert_equal 'baz', bar.gsub!(/r/, 'z')
|
218
|
+
end
|
224
219
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
220
|
+
it "crazy_key_characters" do
|
221
|
+
crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
|
222
|
+
assert @cache.write(crazy_key, "1", :raw => true)
|
223
|
+
assert_equal "1", @cache.read(crazy_key)
|
224
|
+
assert_equal "1", @cache.fetch(crazy_key)
|
225
|
+
assert @cache.delete(crazy_key)
|
226
|
+
refute @cache.exist?(crazy_key)
|
227
|
+
assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
|
228
|
+
assert_equal 3, @cache.increment(crazy_key)
|
229
|
+
assert_equal 2, @cache.decrement(crazy_key)
|
230
|
+
end
|
230
231
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
assert_nil @cache.read("foo")
|
235
|
-
end
|
232
|
+
it "really_long_keys" do
|
233
|
+
really_long_keys_test
|
234
|
+
end
|
236
235
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
236
|
+
it "really_long_keys_with_namespace" do
|
237
|
+
@cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, :expires_in => 60, :namespace => 'namespace')
|
238
|
+
@cache.silence!
|
239
|
+
really_long_keys_test
|
240
|
+
end
|
243
241
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
assert_equal 2, @cache.read('foo').to_i
|
250
|
-
assert_equal 3, @cache.increment('foo')
|
251
|
-
assert_equal 3, @cache.read('foo').to_i
|
252
|
-
end
|
242
|
+
it "clear" do
|
243
|
+
@cache.write("foo", "bar")
|
244
|
+
@cache.clear
|
245
|
+
assert_nil @cache.read("foo")
|
246
|
+
end
|
253
247
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
assert_equal 1, @cache.decrement('foo')
|
260
|
-
assert_equal 1, @cache.read('foo').to_i
|
248
|
+
it "clear_with_options" do
|
249
|
+
@cache.write("foo", "bar")
|
250
|
+
@cache.clear(:some_option => true)
|
251
|
+
assert_nil @cache.read("foo")
|
252
|
+
end
|
261
253
|
end
|
262
254
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
255
|
+
describe "compression" do
|
256
|
+
it "read_and_write_compressed_small_data" do
|
257
|
+
@cache.write('foo', 'bar', :compress => true)
|
258
|
+
raw_value = @cache.send(:read_entry, 'foo', {})
|
259
|
+
assert_equal 'bar', @cache.read('foo')
|
260
|
+
value = Marshal.load(raw_value) rescue raw_value
|
261
|
+
assert_equal 'bar', value
|
262
|
+
end
|
269
263
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
assert_equal 'bar', value
|
264
|
+
it "read_and_write_compressed_large_data" do
|
265
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
|
266
|
+
raw_value = @cache.send(:read_entry, 'foo', :raw => true)
|
267
|
+
assert_equal 'bar', @cache.read('foo')
|
268
|
+
assert_equal 'bar', Marshal.load(raw_value)
|
269
|
+
end
|
277
270
|
end
|
278
271
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
272
|
+
describe "increment / decrement" do
|
273
|
+
it "increment" do
|
274
|
+
@cache.write('foo', '1', :raw => true)
|
275
|
+
assert_equal 1, @cache.read('foo').to_i
|
276
|
+
assert_equal 2, @cache.increment('foo')
|
277
|
+
assert_equal 2, @cache.read('foo').to_i
|
278
|
+
assert_equal 3, @cache.increment('foo')
|
279
|
+
assert_equal 3, @cache.read('foo').to_i
|
280
|
+
end
|
286
281
|
|
287
|
-
|
288
|
-
|
289
|
-
|
282
|
+
it "decrement" do
|
283
|
+
@cache.write('foo', '3', :raw => true)
|
284
|
+
assert_equal 3, @cache.read('foo').to_i
|
285
|
+
assert_equal 2, @cache.decrement('foo')
|
286
|
+
assert_equal 2, @cache.read('foo').to_i
|
287
|
+
assert_equal 1, @cache.decrement('foo')
|
288
|
+
assert_equal 1, @cache.read('foo').to_i
|
289
|
+
end
|
290
290
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
291
|
+
it "increment_decrement_non_existing_keys" do
|
292
|
+
@cache.expects(:log_error).never
|
293
|
+
assert_nil @cache.increment('foo')
|
294
|
+
assert_nil @cache.decrement('bar')
|
295
|
+
end
|
295
296
|
end
|
296
297
|
|
297
|
-
|
298
|
+
it "should_identify_cache_store" do
|
298
299
|
assert_kind_of ActiveSupport::Cache::LibmemcachedStore, @cache
|
299
300
|
end
|
300
301
|
|
301
|
-
|
302
|
+
it "should_set_server_addresses_to_nil_if_none_are_given" do
|
302
303
|
assert_equal [], @cache.addresses
|
303
304
|
end
|
304
305
|
|
305
|
-
|
306
|
+
it "should_set_custom_server_addresses" do
|
306
307
|
store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', '192.168.1.1'
|
307
308
|
assert_equal %w(localhost 192.168.1.1), store.addresses
|
308
309
|
end
|
309
310
|
|
310
|
-
|
311
|
+
it "should_enable_consistent_ketema_hashing_by_default" do
|
311
312
|
assert_equal :consistent_ketama, @cache.client_options[:distribution]
|
312
313
|
end
|
313
314
|
|
314
|
-
|
315
|
+
it "should_not_enable_non_blocking_io_by_default" do
|
315
316
|
assert_equal false, @cache.client_options[:no_block]
|
316
317
|
end
|
317
318
|
|
318
|
-
|
319
|
+
it "should_not_enable_server_failover_by_default" do
|
319
320
|
assert_nil @cache.client_options[:failover]
|
320
321
|
end
|
321
322
|
|
322
|
-
|
323
|
+
it "should_allow_configuration_of_custom_options" do
|
323
324
|
options = { client: { tcp_nodelay: true, distribution: :modula } }
|
324
325
|
|
325
326
|
store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', options
|
@@ -328,7 +329,7 @@ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
|
|
328
329
|
assert_equal true, store.client_options[:tcp_nodelay]
|
329
330
|
end
|
330
331
|
|
331
|
-
|
332
|
+
it "should_allow_mute_and_silence" do
|
332
333
|
cache = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost'
|
333
334
|
cache.mute do
|
334
335
|
assert cache.write('foo', 'bar')
|
@@ -338,4 +339,50 @@ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
|
|
338
339
|
cache.silence!
|
339
340
|
assert cache.silence?
|
340
341
|
end
|
342
|
+
|
343
|
+
describe "#fetch with :race_condition_ttl" do
|
344
|
+
let(:options) { {:expires_in => 1, :race_condition_ttl => 5} }
|
345
|
+
|
346
|
+
def fetch(&block)
|
347
|
+
@cache.fetch("unknown", options, &block)
|
348
|
+
end
|
349
|
+
|
350
|
+
after do
|
351
|
+
Thread.list.each { |t| t.exit unless t == Thread.current }
|
352
|
+
end
|
353
|
+
|
354
|
+
it "works like a normal fetch" do
|
355
|
+
fetch { 1 }.must_equal 1
|
356
|
+
end
|
357
|
+
|
358
|
+
it "keeps a cached value even if the cache expires" do
|
359
|
+
fetch { 1 } # fill it
|
360
|
+
|
361
|
+
future = Time.now + 3 * 60
|
362
|
+
Time.stubs(:now).returns future
|
363
|
+
|
364
|
+
Thread.new do
|
365
|
+
sleep 0.1
|
366
|
+
fetch { raise }.must_equal 1 # 3rd fetch -> read expired value
|
367
|
+
end
|
368
|
+
fetch { sleep 0.2; 2 }.must_equal 2 # 2nd fetch -> takes time to generate but returns correct value
|
369
|
+
fetch { 3 }.must_equal 2 # 4th fetch still correct value
|
370
|
+
end
|
371
|
+
|
372
|
+
it "can be read by a normal read" do
|
373
|
+
fetch { 1 }
|
374
|
+
@cache.read("unknown").must_equal 1
|
375
|
+
end
|
376
|
+
|
377
|
+
it "can be read by a normal fetch" do
|
378
|
+
fetch { 1 }
|
379
|
+
@cache.fetch("unknown") { 2 }.must_equal 1
|
380
|
+
end
|
381
|
+
|
382
|
+
it "can write to things that get fetched" do
|
383
|
+
fetch { 1 }
|
384
|
+
@cache.write "unknown", 2
|
385
|
+
fetch { 1 }.must_equal 2
|
386
|
+
end
|
387
|
+
end
|
341
388
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: libmemcached_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-
|
14
|
+
date: 2013-10-31 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: memcached
|
@@ -77,6 +77,22 @@ dependencies:
|
|
77
77
|
- - ! '>='
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '0'
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: minitest
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '4.7'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ~>
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '4.7'
|
80
96
|
- !ruby/object:Gem::Dependency
|
81
97
|
name: activesupport
|
82
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,6 +145,7 @@ files:
|
|
129
145
|
- gemfiles/rails30.gemfile
|
130
146
|
- gemfiles/rails31.gemfile
|
131
147
|
- gemfiles/rails32.gemfile
|
148
|
+
- gemfiles/rails40.gemfile
|
132
149
|
- lib/action_dispatch/session/libmemcached_store.rb
|
133
150
|
- lib/active_support/cache/libmemcached_store.rb
|
134
151
|
- lib/libmemcached_store.rb
|