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 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