localmemcache_store 0.0.6 → 0.0.7
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/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
|