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 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 escpecially useful if to run seperated caches on one machine.
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
- @lmc = LocalMemCache::ExpiryCache.new options
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
- data_expires_pair = @lmc[name]
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
- data = value.freeze
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
- @lmc.delete(name)
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
- @lmc.each_pair do |key, value|
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
- @lmc.has_key?(name)
56
+ @cache.has_key?(name)
86
57
  end
87
58
 
88
59
  # Clears the entire cache.
89
60
  def clear
90
- @lmc.clear
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 = @lmc.hash.shm_status
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
@@ -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
@@ -1,3 +1,4 @@
1
1
  unless defined? ActiveSupport::Cache::LocalmemcacheStore
2
+ require 'expiry_cache'
2
3
  require 'active_support/cache/localmemcache_store'
3
4
  end
@@ -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, { :namespace => 'lmc_store_test' })
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
@@ -2,5 +2,6 @@ require 'test/unit'
2
2
  require 'rubygems'
3
3
  require 'active_support'
4
4
  require 'active_support/test_case'
5
- require 'lib/active_support/cache/localmemcache_store'
5
+ require 'lib/localmemcache_store'
6
+ #require 'lib/active_support/cache/localmemcache_store'
6
7
 
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.6
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-07 00:00:00 +01:00
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.3
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