ninjudd-ninjudd-memcache-client 1.5.0.3
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/History.txt +85 -0
- data/README.txt +54 -0
- data/Rakefile +24 -0
- data/ext/crc32/crc32.c +28 -0
- data/ext/crc32/extconf.rb +5 -0
- data/lib/memcache.rb +791 -0
- data/lib/memcache_extended.rb +112 -0
- data/lib/memcache_mock.rb +126 -0
- data/lib/memcache_util.rb +90 -0
- data/test/test_mem_cache.rb +744 -0
- data/test/test_memcache_extended.rb +27 -0
- data/test/test_memcache_mock.rb +94 -0
- metadata +66 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
if not defined?(MemCache)
|
2
|
+
require File.dirname(__FILE__) + '/memcache'
|
3
|
+
end
|
4
|
+
|
5
|
+
module MemCacheExtensions
|
6
|
+
LOCK_TIMEOUT = 5 if not defined? LOCK_TIMEOUT
|
7
|
+
WRITE_LOCK_WAIT = 0.001 if not defined? WRITE_LOCK_WAIT
|
8
|
+
|
9
|
+
def get_some(keys, disable = false)
|
10
|
+
keys = keys.collect {|key| key.to_s}
|
11
|
+
|
12
|
+
records = {}
|
13
|
+
records = self.get_multi(keys) unless disable
|
14
|
+
keys_to_fetch = keys - records.keys
|
15
|
+
|
16
|
+
if keys_to_fetch.any?
|
17
|
+
yield(keys_to_fetch).each do |key, data_item|
|
18
|
+
self.set(key, data_item) unless disable
|
19
|
+
records[key] = data_item
|
20
|
+
end
|
21
|
+
end
|
22
|
+
records
|
23
|
+
end
|
24
|
+
|
25
|
+
def in_namespace(namespace)
|
26
|
+
begin
|
27
|
+
# Temporarily change the namespace for convenience.
|
28
|
+
ns = self.namespace
|
29
|
+
self.instance_variable_set(:@namespace, "#{ns}#{namespace}")
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
self.instance_variable_set(:@namespace, ns)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_or_set(key)
|
37
|
+
get(key) || begin
|
38
|
+
value = yield
|
39
|
+
set(key, value)
|
40
|
+
value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_reset_expiry(key, expiry)
|
45
|
+
result = get(key)
|
46
|
+
set(key, result, expiry) if result
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def lock(key)
|
51
|
+
# Returns true if the lock already exists.
|
52
|
+
response = add(lock_key(key), true, LOCK_TIMEOUT)
|
53
|
+
response.index('STORED') != 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def unlock(key)
|
57
|
+
response = delete(lock_key(key))
|
58
|
+
response.index('DELETED') == 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_lock(key, flag = nil)
|
62
|
+
while lock(key) do
|
63
|
+
return if flag == :ignore
|
64
|
+
sleep(WRITE_LOCK_WAIT) # just wait
|
65
|
+
end
|
66
|
+
yield
|
67
|
+
unlock(key) unless flag == :keep
|
68
|
+
end
|
69
|
+
|
70
|
+
def lock_key(key)
|
71
|
+
"lock:#{key}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def locked?(key)
|
75
|
+
not get(lock_key(key)).nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_with_lock(*args)
|
79
|
+
with_lock(args.first, :ignore) do
|
80
|
+
set(*args)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_with_lock(*args)
|
85
|
+
with_lock(args.first, :ignore) do
|
86
|
+
add(*args)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def delete_with_lock(*args)
|
91
|
+
# leave a :delete lock around to prevent someone from
|
92
|
+
# adding stale data for a little while
|
93
|
+
with_lock(args.first, :keep) do
|
94
|
+
delete(*args)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
### To support using memcache in testing.
|
99
|
+
def clear; end
|
100
|
+
def empty?; false; end
|
101
|
+
###
|
102
|
+
end
|
103
|
+
|
104
|
+
class MemCache
|
105
|
+
include MemCacheExtensions
|
106
|
+
end
|
107
|
+
|
108
|
+
if defined?(MemCacheMock)
|
109
|
+
class MemCacheMock
|
110
|
+
include MemCacheExtensions
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,126 @@
|
|
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
|
+
# Note: This doesn't work exactly like memcache's incr
|
19
|
+
# because MemCacheMock doesn't support raw storage.
|
20
|
+
# This version will work on marshalled data.
|
21
|
+
# This is also not atomic.
|
22
|
+
def incr(key, amount=1)
|
23
|
+
oldval = get(key).to_i or return nil
|
24
|
+
newval = oldval + amount
|
25
|
+
set(key, newval) # Note: Loses the expiry.
|
26
|
+
return newval
|
27
|
+
end
|
28
|
+
|
29
|
+
def decr(key, amount=1)
|
30
|
+
incr(key, amount * -1)
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(*args)
|
34
|
+
do_set(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Note: Raw not implemented.
|
38
|
+
def do_set(key, value, expiry = 0, raw=false)
|
39
|
+
return '' if @auto_clear
|
40
|
+
key = cache_key(key)
|
41
|
+
|
42
|
+
@data[key] = Marshal.dump(value)
|
43
|
+
@expiry[key] = Time.now + expiry if expiry != 0
|
44
|
+
'STORED'
|
45
|
+
end
|
46
|
+
|
47
|
+
def add(key, value, expiry = 0)
|
48
|
+
do_set(key, value, expiry) unless get(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def kind_of?(type)
|
52
|
+
(type == MemCache) || super
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete(key)
|
56
|
+
key = cache_key(key)
|
57
|
+
@data.delete(key)
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear
|
61
|
+
@data.clear
|
62
|
+
@expiry.clear
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_multi(*keys)
|
66
|
+
hash = {}
|
67
|
+
keys.each do |key|
|
68
|
+
val = get(key)
|
69
|
+
key = cache_key(key).sub("#{namespace}:",'')
|
70
|
+
hash[key] = val if val
|
71
|
+
end
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
# Note: Raw not implemented.
|
76
|
+
def get(key, raw=false)
|
77
|
+
key = cache_key(key)
|
78
|
+
clear if @auto_clear
|
79
|
+
if @expiry[key] and Time.now > @expiry[key]
|
80
|
+
@data[key] = nil
|
81
|
+
@expiry[key] = nil
|
82
|
+
end
|
83
|
+
return if not @data[key]
|
84
|
+
Marshal.load(@data[key])
|
85
|
+
end
|
86
|
+
|
87
|
+
def [](key)
|
88
|
+
get(key)
|
89
|
+
end
|
90
|
+
|
91
|
+
def []=(key, value)
|
92
|
+
set(key, value)
|
93
|
+
end
|
94
|
+
|
95
|
+
def empty?
|
96
|
+
@data.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
def keys
|
100
|
+
@data.keys
|
101
|
+
end
|
102
|
+
|
103
|
+
def auto_clear_on(&block)
|
104
|
+
if block_given?
|
105
|
+
auto_clear_block(true, &block)
|
106
|
+
else
|
107
|
+
@auto_clear = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def auto_clear_off(&block)
|
112
|
+
if block_given?
|
113
|
+
auto_clear_block(false, &block)
|
114
|
+
else
|
115
|
+
@auto_clear = false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def auto_clear_block(value, &block)
|
120
|
+
old_auto_clear = @auto_clear
|
121
|
+
@auto_clear = value
|
122
|
+
block.call
|
123
|
+
@auto_clear = old_auto_clear
|
124
|
+
end
|
125
|
+
|
126
|
+
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
|
+
|