ninjudd-memcache 0.9.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.
@@ -0,0 +1,120 @@
1
+ # Need to override entire class with Justin's changes
2
+ require File.dirname(__FILE__) + '/memcache'
3
+
4
+ module MemCacheExtensions
5
+ LOCK_TIMEOUT = 5 if not defined? LOCK_TIMEOUT
6
+ WRITE_LOCK_WAIT = 0.001 if not defined? WRITE_LOCK_WAIT
7
+
8
+ def get_some(keys, opts = {})
9
+ opts[:expiry] ||= default_expiry
10
+ keys = keys.collect {|key| key.to_s}
11
+
12
+ records = {}
13
+ records = self.get_multi(keys) unless opts[:disable]
14
+ if opts[:validation]
15
+ records.delete_if do |key, value|
16
+ not opts[:validation].call(key, value)
17
+ end
18
+ end
19
+ keys_to_fetch = keys - records.keys
20
+
21
+ if keys_to_fetch.any?
22
+ yield(keys_to_fetch).each do |key, data_item|
23
+ self.set(key, data_item, opts[:expiry]) unless opts[:disable] or opts[:disable_write]
24
+ records[key] = data_item
25
+ end
26
+ end
27
+ records
28
+ end
29
+
30
+ def in_namespace(namespace)
31
+ begin
32
+ # Temporarily change the namespace for convenience.
33
+ old_namespace = self.namespace
34
+ self.namespace = "#{old_namespace}#{namespace}"
35
+ yield
36
+ ensure
37
+ self.namespace = old_namespace
38
+ end
39
+ end
40
+
41
+ def get_or_set(key)
42
+ get(key) || begin
43
+ value = yield
44
+ set(key, value)
45
+ value
46
+ end
47
+ end
48
+
49
+ def get_reset_expiry(key, expiry)
50
+ result = get(key)
51
+ set(key, result, expiry) if result
52
+ result
53
+ end
54
+
55
+ def lock(key)
56
+ # Returns true if the lock already exists.
57
+ response = add(lock_key(key), true, LOCK_TIMEOUT)
58
+ response.index('STORED') != 0
59
+ end
60
+
61
+ def unlock(key)
62
+ response = delete(lock_key(key))
63
+ response.index('DELETED') == 0
64
+ end
65
+
66
+ def with_lock(key, flag = nil)
67
+ while lock(key) do
68
+ return if flag == :ignore
69
+ sleep(WRITE_LOCK_WAIT) # just wait
70
+ end
71
+ yield
72
+ unlock(key) unless flag == :keep
73
+ end
74
+
75
+ def lock_key(key)
76
+ "lock:#{key}"
77
+ end
78
+
79
+ def locked?(key)
80
+ not get(lock_key(key)).nil?
81
+ end
82
+
83
+ def set_with_lock(*args)
84
+ with_lock(args.first, :ignore) do
85
+ set(*args)
86
+ end
87
+ end
88
+
89
+ def add_with_lock(*args)
90
+ with_lock(args.first, :ignore) do
91
+ add(*args)
92
+ end
93
+ end
94
+
95
+ def delete_with_lock(*args)
96
+ # leave a :delete lock around to prevent someone from
97
+ # adding stale data for a little while
98
+ with_lock(args.first, :keep) do
99
+ delete(*args)
100
+ end
101
+ end
102
+
103
+ def clear
104
+ flush_all
105
+ end
106
+
107
+ ### To support using memcache in testing.
108
+ def empty?; false; end
109
+ ###
110
+ end
111
+
112
+ class MemCache
113
+ include MemCacheExtensions
114
+ end
115
+
116
+ if defined?(MemCacheMock)
117
+ class MemCacheMock
118
+ include MemCacheExtensions
119
+ end
120
+ end
@@ -0,0 +1,137 @@
1
+ class MemCacheMock
2
+ attr_writer :namespace
3
+
4
+ def initialize
5
+ @data = {}
6
+ @expiry = {}
7
+ @auto_clear = false
8
+ end
9
+
10
+ def namespace
11
+ @namespace.to_s
12
+ end
13
+
14
+ def cache_key(key)
15
+ "#{namespace}:#{key}"
16
+ end
17
+
18
+ def default_expiry
19
+ 0
20
+ end
21
+
22
+ # Note: This doesn't work exactly like memcache's incr
23
+ # because MemCacheMock doesn't support raw storage.
24
+ # This version will work on marshalled data.
25
+ # This is also not atomic.
26
+ def incr(key, amount=1)
27
+ oldval = get(key).to_i or return nil
28
+ newval = oldval + amount
29
+ set(key, newval) # Note: Loses the expiry.
30
+ return newval
31
+ end
32
+
33
+ def decr(key, amount=1)
34
+ incr(key, amount * -1)
35
+ end
36
+
37
+ def set(*args)
38
+ do_set(*args)
39
+ end
40
+
41
+ # Note: Raw not implemented.
42
+ def do_set(key, value, expiry = default_expiry, raw=false)
43
+ return '' if @auto_clear
44
+ key = cache_key(key)
45
+
46
+ @data[key] = Marshal.dump(value)
47
+ @expiry[key] = Time.now + expiry if expiry and expiry != 0
48
+ 'STORED'
49
+ end
50
+
51
+ def add(key, value, expiry = 0)
52
+ return '' if get(key)
53
+ do_set(key, value, expiry)
54
+ end
55
+
56
+ def kind_of?(type)
57
+ (type == MemCache) || super
58
+ end
59
+
60
+ def delete(key)
61
+ key = cache_key(key)
62
+ @data.delete(key)
63
+ end
64
+
65
+ def clear
66
+ @data.clear
67
+ @expiry.clear
68
+ end
69
+
70
+ def reset
71
+ # do nothing
72
+ end
73
+
74
+ def get_multi(*keys)
75
+ opts = keys.last.kind_of?(Hash) ? keys.pop : {}
76
+ keys.flatten!
77
+
78
+ hash = {}
79
+ keys.each do |key|
80
+ val = get(key)
81
+ hash[key.to_s] = val if val
82
+ end
83
+ hash
84
+ end
85
+
86
+ # Note: Raw not implemented.
87
+ def get(key, raw=false)
88
+ key = cache_key(key)
89
+ clear if @auto_clear
90
+ if @expiry[key] and Time.now > @expiry[key]
91
+ @data[key] = nil
92
+ @expiry[key] = nil
93
+ end
94
+ return if not @data[key]
95
+ Marshal.load(@data[key])
96
+ end
97
+
98
+ def [](key)
99
+ get(key)
100
+ end
101
+
102
+ def []=(key, value)
103
+ set(key, value)
104
+ end
105
+
106
+ def empty?
107
+ @data.empty?
108
+ end
109
+
110
+ def keys
111
+ @data.keys
112
+ end
113
+
114
+ def auto_clear_on(&block)
115
+ if block_given?
116
+ auto_clear_block(true, &block)
117
+ else
118
+ @auto_clear = true
119
+ end
120
+ end
121
+
122
+ def auto_clear_off(&block)
123
+ if block_given?
124
+ auto_clear_block(false, &block)
125
+ else
126
+ @auto_clear = false
127
+ end
128
+ end
129
+
130
+ def auto_clear_block(value, &block)
131
+ old_auto_clear = @auto_clear
132
+ @auto_clear = value
133
+ block.call
134
+ @auto_clear = old_auto_clear
135
+ end
136
+
137
+ end
@@ -0,0 +1,90 @@
1
+ ##
2
+ # A utility wrapper around the MemCache client to simplify cache access. All
3
+ # methods silently ignore MemCache errors.
4
+
5
+ module Cache
6
+
7
+ ##
8
+ # Returns the object at +key+ from the cache if successful, or nil if either
9
+ # the object is not in the cache or if there was an error attermpting to
10
+ # access the cache.
11
+ #
12
+ # If there is a cache miss and a block is given the result of the block will
13
+ # be stored in the cache with optional +expiry+, using the +add+ method rather
14
+ # than +set+.
15
+
16
+ def self.get(key, expiry = 0)
17
+ start_time = Time.now
18
+ value = CACHE.get key
19
+ elapsed = Time.now - start_time
20
+ ActiveRecord::Base.logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
21
+ if value.nil? and block_given? then
22
+ value = yield
23
+ add key, value, expiry
24
+ end
25
+ value
26
+ rescue MemCache::MemCacheError => err
27
+ ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
28
+ if block_given? then
29
+ value = yield
30
+ put key, value, expiry
31
+ end
32
+ value
33
+ end
34
+
35
+ ##
36
+ # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
37
+ # seconds.
38
+
39
+ def self.put(key, value, expiry = 0)
40
+ start_time = Time.now
41
+ CACHE.set key, value, expiry
42
+ elapsed = Time.now - start_time
43
+ ActiveRecord::Base.logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key])
44
+ value
45
+ rescue MemCache::MemCacheError => err
46
+ ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
47
+ nil
48
+ end
49
+
50
+ ##
51
+ # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
52
+ # seconds. If +key+ already exists in cache, returns nil.
53
+
54
+ def self.add(key, value, expiry = 0)
55
+ start_time = Time.now
56
+ response = CACHE.add key, value, expiry
57
+ elapsed = Time.now - start_time
58
+ ActiveRecord::Base.logger.debug('MemCache Add (%0.6f) %s' % [elapsed, key])
59
+ (response == "STORED\r\n") ? value : nil
60
+ rescue MemCache::MemCacheError => err
61
+ ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
62
+ nil
63
+ end
64
+
65
+ ##
66
+ # Deletes +key+ from the cache in +delay+ seconds.
67
+
68
+ def self.delete(key, delay = nil)
69
+ start_time = Time.now
70
+ CACHE.delete key, delay
71
+ elapsed = Time.now - start_time
72
+ ActiveRecord::Base.logger.debug('MemCache Delete (%0.6f) %s' %
73
+ [elapsed, key])
74
+ nil
75
+ rescue MemCache::MemCacheError => err
76
+ ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
77
+ nil
78
+ end
79
+
80
+ ##
81
+ # Resets all connections to MemCache servers.
82
+
83
+ def self.reset
84
+ CACHE.reset
85
+ ActiveRecord::Base.logger.debug 'MemCache Connections Reset'
86
+ nil
87
+ end
88
+
89
+ end
90
+
@@ -0,0 +1,11 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/memcache/local_server'
3
+ require File.dirname(__FILE__) + '/memcache_server_test_helper'
4
+
5
+ class MemcacheLocalServerTest < Test::Unit::TestCase
6
+ include MemcacheServerTestHelper
7
+
8
+ def setup
9
+ @memcache = Memcache::LocalServer.new
10
+ end
11
+ end
@@ -0,0 +1,65 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/memcache/null_server'
3
+
4
+ class MemcacheNullServerTest < Test::Unit::TestCase
5
+ def setup
6
+ @memcache = Memcache::NullServer.new
7
+ end
8
+
9
+ def m
10
+ @memcache
11
+ end
12
+
13
+ def test_set_and_get
14
+ m.set(2, 'foo', 0)
15
+
16
+ assert_equal nil, m.get('2')
17
+ assert_equal nil, m.get('2')
18
+ end
19
+
20
+ def test_incr
21
+ m.incr('foo')
22
+ assert_equal nil, m.get('foo')
23
+
24
+ m.incr('foo', -1)
25
+ assert_equal nil, m.get('foo')
26
+
27
+ m.incr('foo', 52)
28
+ assert_equal nil, m.get('foo')
29
+
30
+ m.incr('foo', -43)
31
+ assert_equal nil, m.get('foo')
32
+ end
33
+
34
+ def test_multi_get
35
+ m.set(2, '1,2,3')
36
+ m.set(3, '4,5')
37
+
38
+ assert_equal Hash.new, m.get([2,3])
39
+ end
40
+
41
+ def test_delete
42
+ m.set(2, '1,2,3')
43
+
44
+ assert_equal nil, m.get(2)
45
+
46
+ m.delete(2)
47
+
48
+ assert_equal nil, m.get(2)
49
+ end
50
+
51
+ def test_flush_all
52
+ m.set(2, 'bar')
53
+
54
+ assert_equal nil, m.get(2)
55
+
56
+ m.flush_all
57
+
58
+ assert_equal nil, m.get(2)
59
+ end
60
+
61
+ def test_expiry
62
+ m.add('test', '1', 1)
63
+ assert_equal nil, m.get('test')
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require File.dirname(__FILE__) + '/memcache_server_test_helper'
4
+ require File.dirname(__FILE__) + '/../lib/memcache/pg_server'
5
+
6
+ class MemcacheDBServerTest < Test::Unit::TestCase
7
+ ActiveRecord::Base.establish_connection(
8
+ :adapter => "postgresql",
9
+ :host => "localhost",
10
+ :username => "postgres",
11
+ :password => "",
12
+ :database => "memcache_test"
13
+ )
14
+ ActiveRecord::Migration.verbose = false
15
+ ActiveRecord::Base.connection.client_min_messages = 'panic'
16
+
17
+ include MemcacheServerTestHelper
18
+
19
+ def setup
20
+ Memcache::Migration.table = 'memcache_test'
21
+ Memcache::Migration.up
22
+ @memcache = Memcache::PGServer.new(:table => 'memcache_test')
23
+ end
24
+
25
+ def teardown
26
+ Memcache::Migration.down
27
+ end
28
+ end