localmemcache_store 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -0
- data/lib/active_support/cache/localmemcache_store.rb +9 -38
- data/lib/expiry_cache.rb +144 -0
- data/lib/localmemcache_store.rb +1 -0
- data/test/expiry_cache_test.rb +59 -0
- data/test/localmemcache_store_test.rb +26 -2
- data/test/test_helper.rb +2 -1
- metadata +7 -3
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.7
|
@@ -1,20 +1,11 @@
|
|
1
|
-
begin
|
2
|
-
gem 'localmemcache', '>=0.4.3'
|
3
|
-
require 'localmemcache'
|
4
|
-
rescue LoadError
|
5
|
-
raise '"localmemcache>=0.4.3" gem is not installed!'
|
6
|
-
end
|
7
|
-
|
8
1
|
module ActiveSupport::Cache
|
9
2
|
class LocalmemcacheStore < Store
|
10
|
-
|
11
|
-
DataExpiresPair = Struct.new(:data, :expires_at) #:nodoc:
|
12
3
|
|
13
4
|
# Useful options:
|
14
5
|
# * +:namespace+: Namespace to avoid name collisions, defaults to
|
15
6
|
# +:lmc_store+.
|
16
7
|
#
|
17
|
-
# This is
|
8
|
+
# This is especially useful if to run separated caches on one machine.
|
18
9
|
# * +:size_mb+: Size of the cache, defaults to +64+.
|
19
10
|
def initialize options = {}
|
20
11
|
options.reverse_merge!({
|
@@ -22,7 +13,7 @@ module ActiveSupport::Cache
|
|
22
13
|
:size_mb => 64
|
23
14
|
})
|
24
15
|
@size_mb = options[:size_mb]
|
25
|
-
@
|
16
|
+
@cache = ExpiryCache.new options
|
26
17
|
end
|
27
18
|
|
28
19
|
# Reads a value by +name+.
|
@@ -30,19 +21,7 @@ module ActiveSupport::Cache
|
|
30
21
|
# (options are ignored at the time)
|
31
22
|
def read(name, options = nil)
|
32
23
|
super
|
33
|
-
|
34
|
-
return nil unless data_expires_pair
|
35
|
-
|
36
|
-
# entry expired?
|
37
|
-
if data_expires_pair.expires_at &&
|
38
|
-
data_expires_pair.expires_at <= Time.now
|
39
|
-
|
40
|
-
# delete entry from database
|
41
|
-
@lmc.hash.delete(name)
|
42
|
-
nil
|
43
|
-
else
|
44
|
-
data_expires_pair.data
|
45
|
-
end
|
24
|
+
@cache.read name
|
46
25
|
end
|
47
26
|
|
48
27
|
# Writes a +name+-+value+ pair to the cache.
|
@@ -50,13 +29,7 @@ module ActiveSupport::Cache
|
|
50
29
|
# * +:expires_in+: Number of seconds an entry is valid
|
51
30
|
def write(name, value, options = {})
|
52
31
|
super
|
53
|
-
|
54
|
-
expires_in = options[:expires_in]
|
55
|
-
expires_at = if expires_in && expires_in.to_i > 0
|
56
|
-
Time.now + expires_in.to_i
|
57
|
-
end
|
58
|
-
@lmc[name] = DataExpiresPair.new(data, expires_at)
|
59
|
-
data
|
32
|
+
@cache.write name, value, options[:expires_in]
|
60
33
|
end
|
61
34
|
|
62
35
|
# Delete a pair by key name
|
@@ -64,7 +37,7 @@ module ActiveSupport::Cache
|
|
64
37
|
# (options are ignored at the time)
|
65
38
|
def delete(name, options = nil)
|
66
39
|
super
|
67
|
-
@
|
40
|
+
@cache.delete name
|
68
41
|
end
|
69
42
|
|
70
43
|
# Delete all pair with key matching matcher
|
@@ -72,9 +45,7 @@ module ActiveSupport::Cache
|
|
72
45
|
# (options are ignored at the time)
|
73
46
|
def delete_matched(matcher, options = nil)
|
74
47
|
super
|
75
|
-
@
|
76
|
-
@lmc.delete(key) if key =~ matcher
|
77
|
-
end
|
48
|
+
@cache.delete_matched matcher
|
78
49
|
end
|
79
50
|
|
80
51
|
# Checks key for existance
|
@@ -82,18 +53,18 @@ module ActiveSupport::Cache
|
|
82
53
|
# (options are ignored at the time)
|
83
54
|
def exist?(name, options = nil)
|
84
55
|
super
|
85
|
-
@
|
56
|
+
@cache.has_key?(name)
|
86
57
|
end
|
87
58
|
|
88
59
|
# Clears the entire cache.
|
89
60
|
def clear
|
90
|
-
@
|
61
|
+
@cache.clear
|
91
62
|
end
|
92
63
|
|
93
64
|
# Returns the status of the cache in form of a hash. Elements are:
|
94
65
|
# +:free_bytes+, +:used_bytes+, +:total_bytes+ and +:usage+
|
95
66
|
def status
|
96
|
-
s = @
|
67
|
+
s = @cache.shm_status
|
97
68
|
s[:usage] = s[:used_bytes].to_f / s[:total_bytes].to_f
|
98
69
|
s
|
99
70
|
end
|
data/lib/expiry_cache.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
begin
|
2
|
+
gem 'localmemcache', '>=0.4.4'
|
3
|
+
require 'localmemcache'
|
4
|
+
rescue LoadError
|
5
|
+
raise '"localmemcache>=0.4.4" gem is not installed!'
|
6
|
+
end
|
7
|
+
|
8
|
+
class ExpiryCache
|
9
|
+
|
10
|
+
class Entry < Struct.new(:data, :expires_at)
|
11
|
+
def expired?
|
12
|
+
expires_at && expires_at <= Time.now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
opts = {
|
18
|
+
:expiration_check_interval => 1_000
|
19
|
+
}.merge(options)
|
20
|
+
@cache = LocalMemCache::SharedObjectStorage.new opts
|
21
|
+
@expiration_check_interval = opts[:expiration_check_interval]
|
22
|
+
@expiration_check_counter = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_key? key
|
26
|
+
verify_key_not_expired key
|
27
|
+
@cache.has_key? key
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
@cache.clear
|
32
|
+
end
|
33
|
+
def shm_status
|
34
|
+
@cache.shm_status
|
35
|
+
end
|
36
|
+
|
37
|
+
def read key
|
38
|
+
do_expiration_check
|
39
|
+
entry = @cache[key]
|
40
|
+
|
41
|
+
# return if nothing in cache
|
42
|
+
return nil unless entry
|
43
|
+
|
44
|
+
# entry expired?
|
45
|
+
if verify_entry_not_expired(key, entry)
|
46
|
+
entry.data
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def write key, value, expires_in = nil
|
53
|
+
do_expiration_check
|
54
|
+
|
55
|
+
value.freeze
|
56
|
+
|
57
|
+
# calculate expiration
|
58
|
+
expires_at = if expires_in && expires_in.to_i > 0
|
59
|
+
Time.now + expires_in.to_i
|
60
|
+
end
|
61
|
+
|
62
|
+
# store data
|
63
|
+
if expires_at.nil? || expires_at > Time.now
|
64
|
+
entry = Entry.new(value, expires_at)
|
65
|
+
safe_write key, entry
|
66
|
+
end
|
67
|
+
|
68
|
+
value
|
69
|
+
end
|
70
|
+
|
71
|
+
def delete key
|
72
|
+
@cache.delete key
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete_matched matcher
|
76
|
+
@cache.each_pair do |key, value|
|
77
|
+
@cache.delete(key) if key =~ matcher
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def verify_key_not_expired key
|
84
|
+
entry = @cache[key]
|
85
|
+
verify_entry_not_expired(key, entry) if entry
|
86
|
+
end
|
87
|
+
def verify_entry_not_expired key, entry
|
88
|
+
if entry.expired?
|
89
|
+
@cache.delete(key)
|
90
|
+
false
|
91
|
+
else
|
92
|
+
true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def do_expiration_check
|
97
|
+
@expiration_check_counter += 1
|
98
|
+
return unless @expiration_check_counter >= @expiration_check_interval
|
99
|
+
@expiration_check_counter = 0
|
100
|
+
expire_random_entries
|
101
|
+
end
|
102
|
+
|
103
|
+
def expire_random_entries count = 1_000
|
104
|
+
[count, @cache.size].min.times do
|
105
|
+
key, entry = @cache.random_pair
|
106
|
+
break if key.nil?
|
107
|
+
verify_entry_not_expired key, entry
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def expire_some_entries count = 100
|
112
|
+
count = [count, @cache.size].min
|
113
|
+
@cache.each_pair do |key, entry|
|
114
|
+
break if count <= 0
|
115
|
+
count -= 1 unless verify_entry_not_expired(key, entry)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# TODO: Performance?
|
120
|
+
def safe_write key, entry
|
121
|
+
random_tries = 10
|
122
|
+
some_tries = 10
|
123
|
+
cleared = false
|
124
|
+
begin
|
125
|
+
@cache[key] = entry
|
126
|
+
rescue LocalMemCache::MemoryPoolFull
|
127
|
+
if random_tries > 0
|
128
|
+
random_tries -= 1
|
129
|
+
expire_random_entries
|
130
|
+
retry
|
131
|
+
elsif some_tries > 0
|
132
|
+
some_tries -= 1
|
133
|
+
expire_some_entries
|
134
|
+
retry
|
135
|
+
else
|
136
|
+
raise if cleared
|
137
|
+
@cache.clear
|
138
|
+
cleared = true
|
139
|
+
retry
|
140
|
+
end
|
141
|
+
end
|
142
|
+
entry
|
143
|
+
end
|
144
|
+
end
|
data/lib/localmemcache_store.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ExpiryCacheTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@cache = ExpiryCache.new(:namespace => 'expiry_cache_test', :size_mb => 16)
|
7
|
+
@cache.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
def expire_entries_test method
|
11
|
+
1.upto(10) { |i| @cache.write(i, 'x' * 1_000, 1.second) }
|
12
|
+
used = @cache.shm_status[:used_bytes]
|
13
|
+
sleep 2
|
14
|
+
@cache.send method
|
15
|
+
assert_operator used, :>, @cache.shm_status[:used_bytes]
|
16
|
+
end
|
17
|
+
|
18
|
+
test "expiration by expire_random_entries" do
|
19
|
+
expire_entries_test :expire_random_entries
|
20
|
+
end
|
21
|
+
test "expiration by expire_some_entries" do
|
22
|
+
expire_entries_test :expire_some_entries
|
23
|
+
end
|
24
|
+
|
25
|
+
test "automatic deletion of some expired entries" do
|
26
|
+
@cache.write :foo, :bar, 1.second
|
27
|
+
sleep 2
|
28
|
+
used = @cache.shm_status[:used_bytes]
|
29
|
+
999.times { @cache.write :baz, :baz }
|
30
|
+
assert_operator used, :>, @cache.shm_status[:used_bytes]
|
31
|
+
end
|
32
|
+
|
33
|
+
test "safe_write with full pool" do
|
34
|
+
five_mb = 1_024 * 1_024 * 5
|
35
|
+
@cache.write :foo, 'x' * five_mb, 1.second
|
36
|
+
sleep 2
|
37
|
+
assert_nothing_raised do
|
38
|
+
@cache.write :bar, 'x' * five_mb
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
test "safe_write with full pool and no chance to random expire entries" do
|
43
|
+
five_mb = 1_024 * 1_024 * 5
|
44
|
+
@cache.write :foo, 'x' * five_mb, 1.second
|
45
|
+
1.upto(50_000) { |i| @cache.write i, 'x' }
|
46
|
+
assert_nothing_raised do
|
47
|
+
@cache.write :bar, 'x' * five_mb
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
test "safe_write with full pool and no expirable entries" do
|
52
|
+
five_mb = 1_024 * 1_024 * 5
|
53
|
+
@cache.write :foo, 'x' * five_mb
|
54
|
+
sleep 2
|
55
|
+
assert_nothing_raised do
|
56
|
+
@cache.write :bar, 'x' * five_mb
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -7,7 +7,8 @@ require File.dirname(__FILE__) + '/cache_store_behavior.rb'
|
|
7
7
|
class LocalmemcacheStoreTest < ActiveSupport::TestCase
|
8
8
|
|
9
9
|
def setup
|
10
|
-
@cache = ActiveSupport::Cache.lookup_store(:localmemcache_store,
|
10
|
+
@cache = ActiveSupport::Cache.lookup_store(:localmemcache_store,
|
11
|
+
{ :namespace => 'lmc_store_test' })
|
11
12
|
@cache.clear
|
12
13
|
@cache.silence!
|
13
14
|
@cache.logger = Logger.new("/dev/null")
|
@@ -36,6 +37,30 @@ class LocalmemcacheStoreTest < ActiveSupport::TestCase
|
|
36
37
|
sleep 2
|
37
38
|
assert_nil @cache.read(:key)
|
38
39
|
end
|
40
|
+
|
41
|
+
test "delete an entry" do
|
42
|
+
@cache.write :key, :value
|
43
|
+
@cache.delete :key
|
44
|
+
assert_nil @cache.read(:key)
|
45
|
+
end
|
46
|
+
|
47
|
+
test "entry exists" do
|
48
|
+
assert !@cache.exist?(:foo)
|
49
|
+
@cache.write :foo, :bar
|
50
|
+
assert @cache.exist?(:foo)
|
51
|
+
end
|
52
|
+
|
53
|
+
test "delete entries by regex" do
|
54
|
+
@cache.write :foo1, :bar
|
55
|
+
@cache.write :foo2, :bar
|
56
|
+
@cache.write :baz, :bar
|
57
|
+
|
58
|
+
@cache.delete_matched /^foo/
|
59
|
+
|
60
|
+
assert !@cache.exist?(:foo1)
|
61
|
+
assert !@cache.exist?(:foo2)
|
62
|
+
assert @cache.exist?(:baz)
|
63
|
+
end
|
39
64
|
|
40
65
|
include CacheStoreBehavior
|
41
66
|
|
@@ -71,5 +96,4 @@ class LocalmemcacheStoreTest < ActiveSupport::TestCase
|
|
71
96
|
test "status has usage" do
|
72
97
|
assert_not_nil @cache.status[:usage]
|
73
98
|
end
|
74
|
-
|
75
99
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: localmemcache_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- "Florian D\xC3\xBCtsch (der_flo)"
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-14 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.4.
|
23
|
+
version: 0.4.4
|
24
24
|
version:
|
25
25
|
description:
|
26
26
|
email: mail@florian-duetsch.de
|
@@ -33,10 +33,13 @@ extra_rdoc_files:
|
|
33
33
|
files:
|
34
34
|
- MIT-LICENSE
|
35
35
|
- README.rdoc
|
36
|
+
- VERSION
|
36
37
|
- lib/active_support/cache/localmemcache_store.rb
|
38
|
+
- lib/expiry_cache.rb
|
37
39
|
- lib/localmemcache_store.rb
|
38
40
|
- rails/init.rb
|
39
41
|
- test/cache_store_behavior.rb
|
42
|
+
- test/expiry_cache_test.rb
|
40
43
|
- test/localmemcache_store_test.rb
|
41
44
|
- test/test_helper.rb
|
42
45
|
has_rdoc: true
|
@@ -69,5 +72,6 @@ specification_version: 3
|
|
69
72
|
summary: A Rails cache store implementation for localmemcache
|
70
73
|
test_files:
|
71
74
|
- test/cache_store_behavior.rb
|
75
|
+
- test/expiry_cache_test.rb
|
72
76
|
- test/localmemcache_store_test.rb
|
73
77
|
- test/test_helper.rb
|