jashmenn-dalli 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/History.md +169 -0
- data/LICENSE +20 -0
- data/Performance.md +77 -0
- data/README.md +177 -0
- data/Rakefile +34 -0
- data/Upgrade.md +45 -0
- data/dalli.gemspec +33 -0
- data/lib/action_controller/session/dalli_store.rb +62 -0
- data/lib/action_dispatch/middleware/session/dalli_store.rb +67 -0
- data/lib/active_support/cache/dalli_store.rb +185 -0
- data/lib/active_support/cache/dalli_store23.rb +172 -0
- data/lib/dalli.rb +43 -0
- data/lib/dalli/client.rb +264 -0
- data/lib/dalli/compatibility.rb +52 -0
- data/lib/dalli/memcache-client.rb +1 -0
- data/lib/dalli/options.rb +44 -0
- data/lib/dalli/ring.rb +105 -0
- data/lib/dalli/server.rb +516 -0
- data/lib/dalli/socket.rb +37 -0
- data/lib/dalli/version.rb +3 -0
- data/test/abstract_unit.rb +284 -0
- data/test/benchmark_test.rb +170 -0
- data/test/helper.rb +54 -0
- data/test/memcached_mock.rb +106 -0
- data/test/test_active_support.rb +177 -0
- data/test/test_compatibility.rb +33 -0
- data/test/test_dalli.rb +398 -0
- data/test/test_encoding.rb +34 -0
- data/test/test_failover.rb +89 -0
- data/test/test_network.rb +54 -0
- data/test/test_ring.rb +89 -0
- data/test/test_sasl.rb +79 -0
- data/test/test_session_store.rb +230 -0
- metadata +145 -0
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
Rake::TestTask.new(:test) do |test|
|
3
|
+
test.libs << 'test'
|
4
|
+
test.pattern = 'test/**/test_*.rb'
|
5
|
+
end
|
6
|
+
|
7
|
+
Rake::TestTask.new(:bench) do |test|
|
8
|
+
test.libs << 'test'
|
9
|
+
test.pattern = 'test/benchmark_test.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'metric_fu'
|
14
|
+
MetricFu::Configuration.run do |config|
|
15
|
+
config.rcov[:rcov_opts] << "-Itest:lib"
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => :test
|
21
|
+
|
22
|
+
task :test_all do
|
23
|
+
system('rake test RAILS_VERSION="~> 2.3.0"')
|
24
|
+
system('rake test RAILS_VERSION="~> 3.0.0"')
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'rake/rdoctask'
|
28
|
+
Rake::RDocTask.new do |rd|
|
29
|
+
rd.rdoc_files.include("lib/**/*.rb")
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'rake/clean'
|
33
|
+
CLEAN.include "**/*.rbc"
|
34
|
+
CLEAN.include "**/.DS_Store"
|
data/Upgrade.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
Upgrading from memcache-client
|
2
|
+
========
|
3
|
+
|
4
|
+
Dalli is not meant to be 100% compatible with memcache-client, there are a few minor differences in the API.
|
5
|
+
|
6
|
+
|
7
|
+
Compatibility Layer
|
8
|
+
----------------------
|
9
|
+
|
10
|
+
Enable memcache-client compatibility in your application when upgrading by requiring this when
|
11
|
+
initalizing your app:
|
12
|
+
|
13
|
+
require 'dalli/memcache-client'
|
14
|
+
|
15
|
+
This will print out warnings if your code is using the old memcache-client API style, explained below.
|
16
|
+
|
17
|
+
|
18
|
+
Marshalling
|
19
|
+
---------------
|
20
|
+
|
21
|
+
Dalli has changed the raw parameter to a :raw option. The memcache-client API allowed you to control marshalling on a per-method basis using a boolean 'raw' parameter to several of the API methods:
|
22
|
+
|
23
|
+
cache = MemCache.new
|
24
|
+
cache.set('abc', 123, 0, true)
|
25
|
+
cache.get('abc', true) => '123'
|
26
|
+
|
27
|
+
cache.set('abc', 123, 0)
|
28
|
+
cache.get('abc') => 123
|
29
|
+
|
30
|
+
Note that the last parameter is set to true in the first two API calls and so `get` returns a string, not an integer. In the second example, we don't provide the raw parameter. Since it defaults to false, it works exactly like Dalli.
|
31
|
+
|
32
|
+
If the code specifies raw as false, you can simply remove that parameter. If the code is using raw = true, you will need to use the :raw option:
|
33
|
+
|
34
|
+
cache.set('abc', 123, 0, :raw => true)
|
35
|
+
|
36
|
+
|
37
|
+
Return Values
|
38
|
+
----------------
|
39
|
+
|
40
|
+
In memcache-client, `set(key, value)` normally returns "STORED\r\n". This is an artifact of the text protocol used in earlier versions of memcached. Code that checks the return value will need to be updated. Dalli raises errors for exceptional cases but otherwise returns true or false depending on whether the operation succeeded or not. These methods are affected:
|
41
|
+
|
42
|
+
set
|
43
|
+
add
|
44
|
+
replace
|
45
|
+
delete
|
data/dalli.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
require 'dalli/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{jashmenn-dalli}
|
7
|
+
s.version = Dalli::VERSION
|
8
|
+
|
9
|
+
s.authors = ["Mike Perham"]
|
10
|
+
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
11
|
+
s.description = %q{High performance memcached client for Ruby}
|
12
|
+
s.email = %q{mperham@gmail.com}
|
13
|
+
s.files = Dir.glob("lib/**/*") + [
|
14
|
+
"LICENSE",
|
15
|
+
"README.md",
|
16
|
+
"History.md",
|
17
|
+
"Rakefile",
|
18
|
+
"Gemfile",
|
19
|
+
"dalli.gemspec",
|
20
|
+
"Performance.md",
|
21
|
+
"Upgrade.md",
|
22
|
+
]
|
23
|
+
s.homepage = %q{http://github.com/mperham/dalli}
|
24
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
s.summary = %q{High performance memcached client for Ruby}
|
27
|
+
s.test_files = Dir.glob("test/**/*")
|
28
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<rails>, [">= 3.0.1"])
|
31
|
+
s.add_development_dependency(%q<memcache-client>, [">= 1.8.5"])
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Session store for Rails 2.3.x
|
2
|
+
# Tested against 2.3.9.
|
3
|
+
begin
|
4
|
+
require_library_or_gem 'dalli'
|
5
|
+
|
6
|
+
module ActionController
|
7
|
+
module Session
|
8
|
+
class DalliStore < AbstractStore
|
9
|
+
def initialize(app, options = {})
|
10
|
+
# Support old :expires option
|
11
|
+
options[:expire_after] ||= options[:expires]
|
12
|
+
|
13
|
+
super
|
14
|
+
|
15
|
+
@default_options = {
|
16
|
+
:namespace => 'rack:session',
|
17
|
+
:memcache_server => 'localhost:11211'
|
18
|
+
}.merge(@default_options)
|
19
|
+
|
20
|
+
Rails.logger.debug("Using Dalli #{Dalli::VERSION} for session store at #{@default_options[:memcache_server].inspect}")
|
21
|
+
|
22
|
+
@pool = Dalli::Client.new(@default_options[:memcache_server], @default_options)
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def get_session(env, sid)
|
28
|
+
sid ||= generate_sid
|
29
|
+
begin
|
30
|
+
session = @pool.get(sid) || {}
|
31
|
+
rescue Dalli::DalliError
|
32
|
+
Rails.logger.warn("Session::DalliStore#get: #{$!.message}")
|
33
|
+
session = {}
|
34
|
+
end
|
35
|
+
[sid, session]
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_session(env, sid, session_data)
|
39
|
+
options = env['rack.session.options']
|
40
|
+
expiry = options[:expire_after]
|
41
|
+
@pool.set(sid, session_data, expiry)
|
42
|
+
return true
|
43
|
+
rescue Dalli::DalliError
|
44
|
+
Rails.logger.warn("Session::DalliStore#set: #{$!.message}")
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
|
48
|
+
def destroy(env)
|
49
|
+
if sid = current_session_id(env)
|
50
|
+
@pool.delete(sid)
|
51
|
+
end
|
52
|
+
rescue Dalli::DalliError
|
53
|
+
Rails.logger.warn("Session::DalliStore#destroy: #{$!.message}")
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue LoadError
|
61
|
+
# Dalli wasn't available so neither can the store be
|
62
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_support/cache'
|
2
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
3
|
+
require 'dalli'
|
4
|
+
|
5
|
+
# Dalli-based session store for Rails 3.0.
|
6
|
+
module ActionDispatch
|
7
|
+
module Session
|
8
|
+
class DalliStore < AbstractStore
|
9
|
+
def initialize(app, options = {})
|
10
|
+
# Support old :expires option
|
11
|
+
options[:expire_after] ||= options[:expires]
|
12
|
+
|
13
|
+
super
|
14
|
+
|
15
|
+
@default_options = {
|
16
|
+
:namespace => 'rack:session',
|
17
|
+
:memcache_server => 'localhost:11211',
|
18
|
+
}.merge(@default_options)
|
19
|
+
|
20
|
+
@pool = options[:cache] || begin
|
21
|
+
Dalli::Client.new(
|
22
|
+
@default_options[:memcache_server], @default_options)
|
23
|
+
end
|
24
|
+
@namespace = @default_options[:namespace]
|
25
|
+
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset
|
30
|
+
@pool.reset
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_session(env, sid)
|
36
|
+
sid ||= generate_sid
|
37
|
+
begin
|
38
|
+
session = @pool.get(sid) || {}
|
39
|
+
rescue Dalli::DalliError
|
40
|
+
Rails.logger.warn("Session::DalliStore#get: #{$!.message}")
|
41
|
+
session = {}
|
42
|
+
end
|
43
|
+
[sid, session]
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_session(env, sid, session_data, options = nil)
|
47
|
+
options ||= env[ENV_SESSION_OPTIONS_KEY]
|
48
|
+
expiry = options[:expire_after]
|
49
|
+
@pool.set(sid, session_data, expiry)
|
50
|
+
sid
|
51
|
+
rescue Dalli::DalliError
|
52
|
+
Rails.logger.warn("Session::DalliStore#set: #{$!.message}")
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def destroy(env)
|
57
|
+
if sid = current_session_id(env)
|
58
|
+
@pool.delete(sid)
|
59
|
+
end
|
60
|
+
rescue Dalli::DalliError
|
61
|
+
Rails.logger.warn("Session::DalliStore#delete: #{$!.message}")
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# encoding: ascii
|
2
|
+
begin
|
3
|
+
require 'dalli'
|
4
|
+
rescue LoadError => e
|
5
|
+
$stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
|
6
|
+
raise e
|
7
|
+
end
|
8
|
+
require 'digest/md5'
|
9
|
+
|
10
|
+
module ActiveSupport
|
11
|
+
module Cache
|
12
|
+
# A cache store implementation which stores data in Memcached:
|
13
|
+
# http://www.danga.com/memcached/
|
14
|
+
#
|
15
|
+
# DalliStore implements the Strategy::LocalCache strategy which implements
|
16
|
+
# an in memory cache inside of a block.
|
17
|
+
class DalliStore < Store
|
18
|
+
|
19
|
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/
|
20
|
+
RAW = { :raw => true }
|
21
|
+
|
22
|
+
def self.build_mem_cache(*addresses)
|
23
|
+
addresses = addresses.flatten
|
24
|
+
options = addresses.extract_options!
|
25
|
+
addresses = ["localhost:11211"] if addresses.empty?
|
26
|
+
Dalli::Client.new(addresses, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a new DalliStore object, with the given memcached server
|
30
|
+
# addresses. Each address is either a host name, or a host-with-port string
|
31
|
+
# in the form of "host_name:port". For example:
|
32
|
+
#
|
33
|
+
# ActiveSupport::Cache::DalliStore.new("localhost", "server-downstairs.localnetwork:8229")
|
34
|
+
#
|
35
|
+
# If no addresses are specified, then DalliStore will connect to
|
36
|
+
# localhost port 11211 (the default memcached port).
|
37
|
+
#
|
38
|
+
def initialize(*addresses)
|
39
|
+
addresses = addresses.flatten
|
40
|
+
options = addresses.extract_options!
|
41
|
+
super(options)
|
42
|
+
|
43
|
+
mem_cache_options = options.dup
|
44
|
+
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
|
45
|
+
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
|
46
|
+
|
47
|
+
extend Strategy::LocalCache
|
48
|
+
extend LocalCacheWithRaw
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reads multiple keys from the cache using a single call to the
|
52
|
+
# servers for all keys. Options can be passed in the last argument.
|
53
|
+
def read_multi(*names)
|
54
|
+
options = names.extract_options!
|
55
|
+
options = merged_options(options)
|
56
|
+
keys_to_names = names.inject({}){|map, name| map[escape_key(namespaced_key(name, options))] = name; map}
|
57
|
+
raw_values = @data.get_multi(keys_to_names.keys, RAW)
|
58
|
+
values = {}
|
59
|
+
raw_values.each do |key, value|
|
60
|
+
entry = deserialize_entry(value)
|
61
|
+
values[keys_to_names[key]] = entry.value unless entry.expired?
|
62
|
+
end
|
63
|
+
values
|
64
|
+
end
|
65
|
+
|
66
|
+
# Increment a cached value. This method uses the memcached incr atomic
|
67
|
+
# operator and can only be used on values written with the :raw option.
|
68
|
+
# Calling it on a value not stored with :raw will fail.
|
69
|
+
# :initial defaults to the amount passed in, as if the counter was initially zero.
|
70
|
+
# memcached counters cannot hold negative values.
|
71
|
+
def increment(name, amount = 1, options = nil) # :nodoc:
|
72
|
+
options = merged_options(options)
|
73
|
+
initial = options[:initial] || amount
|
74
|
+
expires_in = options[:expires_in].to_i
|
75
|
+
response = instrument(:increment, name, :amount => amount) do
|
76
|
+
@data.incr(escape_key(namespaced_key(name, options)), amount, expires_in, initial)
|
77
|
+
end
|
78
|
+
rescue Dalli::DalliError => e
|
79
|
+
logger.error("DalliError: #{e.message}") if logger
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Decrement a cached value. This method uses the memcached decr atomic
|
84
|
+
# operator and can only be used on values written with the :raw option.
|
85
|
+
# Calling it on a value not stored with :raw will fail.
|
86
|
+
# :initial defaults to zero, as if the counter was initially zero.
|
87
|
+
# memcached counters cannot hold negative values.
|
88
|
+
def decrement(name, amount = 1, options = nil) # :nodoc:
|
89
|
+
options = merged_options(options)
|
90
|
+
initial = options[:initial] || 0
|
91
|
+
expires_in = options[:expires_in].to_i
|
92
|
+
response = instrument(:decrement, name, :amount => amount) do
|
93
|
+
@data.decr(escape_key(namespaced_key(name, options)), amount, expires_in, initial)
|
94
|
+
end
|
95
|
+
rescue Dalli::DalliError => e
|
96
|
+
logger.error("DalliError: #{e.message}") if logger
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
# Clear the entire cache on all memcached servers. This method should
|
101
|
+
# be used with care when using a shared cache.
|
102
|
+
def clear(options = nil)
|
103
|
+
@data.flush_all
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get the statistics from the memcached servers.
|
107
|
+
def stats
|
108
|
+
@data.stats
|
109
|
+
end
|
110
|
+
|
111
|
+
def reset
|
112
|
+
@data.reset
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
# This CacheStore impl controls value marshalling so we take special
|
118
|
+
# care to always pass :raw => true to the Dalli API so it does not
|
119
|
+
# double marshal.
|
120
|
+
|
121
|
+
# Read an entry from the cache.
|
122
|
+
def read_entry(key, options) # :nodoc:
|
123
|
+
deserialize_entry(@data.get(escape_key(key), RAW))
|
124
|
+
rescue Dalli::DalliError => e
|
125
|
+
logger.error("DalliError: #{e.message}") if logger
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
|
129
|
+
# Write an entry to the cache.
|
130
|
+
def write_entry(key, entry, options) # :nodoc:
|
131
|
+
method = options[:unless_exist] ? :add : :set
|
132
|
+
value = options[:raw] ? entry.value.to_s : entry
|
133
|
+
expires_in = options[:expires_in].to_i
|
134
|
+
if expires_in > 0 && !options[:raw]
|
135
|
+
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
136
|
+
expires_in += 5.minutes
|
137
|
+
end
|
138
|
+
@data.send(method, escape_key(key), value, expires_in, options)
|
139
|
+
rescue Dalli::DalliError => e
|
140
|
+
logger.error("DalliError: #{e.message}") if logger
|
141
|
+
false
|
142
|
+
end
|
143
|
+
|
144
|
+
# Delete an entry from the cache.
|
145
|
+
def delete_entry(key, options) # :nodoc:
|
146
|
+
@data.delete(escape_key(key))
|
147
|
+
rescue Dalli::DalliError => e
|
148
|
+
logger.error("DalliError: #{e.message}") if logger
|
149
|
+
false
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def escape_key(key)
|
154
|
+
key = key.to_s.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"}
|
155
|
+
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
|
156
|
+
key
|
157
|
+
end
|
158
|
+
|
159
|
+
def deserialize_entry(raw_value)
|
160
|
+
if raw_value
|
161
|
+
# FIXME: This is a terrible implementation for performance reasons:
|
162
|
+
# throwing an exception is much slower than some if logic.
|
163
|
+
entry = Marshal.load(raw_value) rescue raw_value
|
164
|
+
entry.is_a?(Entry) ? entry : Entry.new(entry)
|
165
|
+
else
|
166
|
+
nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Provide support for raw values in the local cache strategy.
|
171
|
+
module LocalCacheWithRaw # :nodoc:
|
172
|
+
protected
|
173
|
+
def write_entry(key, entry, options) # :nodoc:
|
174
|
+
retval = super
|
175
|
+
if options[:raw] && local_cache && retval
|
176
|
+
raw_entry = Entry.new(entry.value.to_s)
|
177
|
+
raw_entry.expires_at = entry.expires_at
|
178
|
+
local_cache.write_entry(key, raw_entry, options)
|
179
|
+
end
|
180
|
+
retval
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
begin
|
2
|
+
require 'dalli'
|
3
|
+
rescue LoadError => e
|
4
|
+
$stderr.puts "You don't have dalli installed in your application: #{e.message}"
|
5
|
+
raise e
|
6
|
+
end
|
7
|
+
require 'digest/md5'
|
8
|
+
|
9
|
+
module ActiveSupport
|
10
|
+
module Cache
|
11
|
+
# A cache store implementation which stores data in Memcached:
|
12
|
+
# http://www.danga.com/memcached/
|
13
|
+
#
|
14
|
+
# DalliStore implements the Strategy::LocalCache strategy which implements
|
15
|
+
# an in memory cache inside of a block.
|
16
|
+
class DalliStore < Store
|
17
|
+
|
18
|
+
def self.build_mem_cache(*addresses)
|
19
|
+
addresses = addresses.flatten
|
20
|
+
options = addresses.extract_options!
|
21
|
+
addresses = ["localhost"] if addresses.empty?
|
22
|
+
Dalli::Client.new(addresses, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a new DalliStore object, with the given memcached server
|
26
|
+
# addresses. Each address is either a host name, or a host-with-port string
|
27
|
+
# in the form of "host_name:port". For example:
|
28
|
+
#
|
29
|
+
# ActiveSupport::Cache::DalliStore.new("localhost", "server-downstairs.localnetwork:8229")
|
30
|
+
#
|
31
|
+
# If no addresses are specified, then DalliStore will connect to
|
32
|
+
# localhost port 11211 (the default memcached port).
|
33
|
+
#
|
34
|
+
def initialize(*addresses)
|
35
|
+
addresses = addresses.flatten
|
36
|
+
options = addresses.extract_options!
|
37
|
+
|
38
|
+
mem_cache_options = options.dup
|
39
|
+
@namespace = mem_cache_options.delete(:namespace)
|
40
|
+
@expires_in = mem_cache_options[:expires_in]
|
41
|
+
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
|
42
|
+
|
43
|
+
extend Strategy::LocalCache
|
44
|
+
end
|
45
|
+
|
46
|
+
# Reads multiple keys from the cache using a single call to the
|
47
|
+
# servers for all keys. Options can be passed in the last argument.
|
48
|
+
def read_multi(*names)
|
49
|
+
options = nil
|
50
|
+
options = names.pop if names.last.is_a?(Hash)
|
51
|
+
keys_to_names = names.inject({}){|map, name| map[escape_key(name)] = name; map}
|
52
|
+
cache_keys = {}
|
53
|
+
# map keys to servers
|
54
|
+
names.each do |key|
|
55
|
+
cache_key = escape_key key
|
56
|
+
cache_keys[cache_key] = key
|
57
|
+
end
|
58
|
+
|
59
|
+
values = @data.get_multi(keys_to_names.keys, options)
|
60
|
+
results = {}
|
61
|
+
values.each do |key, value|
|
62
|
+
results[cache_keys[key]] = value
|
63
|
+
end
|
64
|
+
results
|
65
|
+
end
|
66
|
+
|
67
|
+
def reset
|
68
|
+
@data.reset
|
69
|
+
end
|
70
|
+
|
71
|
+
# Read an entry from the cache.
|
72
|
+
def read(key, options = nil) # :nodoc:
|
73
|
+
super
|
74
|
+
@data.get(escape_key(key), options)
|
75
|
+
rescue Dalli::DalliError => e
|
76
|
+
logger.error("DalliError: #{e.message}")
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Writes a value to the cache.
|
81
|
+
#
|
82
|
+
# Possible options:
|
83
|
+
# - +:unless_exist+ - set to true if you don't want to update the cache
|
84
|
+
# if the key is already set.
|
85
|
+
# - +:expires_in+ - the number of seconds that this value may stay in
|
86
|
+
# the cache. See ActiveSupport::Cache::Store#write for an example.
|
87
|
+
def write(key, value, options = nil)
|
88
|
+
super
|
89
|
+
value = value.to_s if options && options[:raw]
|
90
|
+
method = options && options[:unless_exist] ? :add : :set
|
91
|
+
@data.send(method, escape_key(key), value, expires_in(options), options)
|
92
|
+
rescue Dalli::DalliError => e
|
93
|
+
logger.error("DalliError: #{e.message}")
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete(key, options = nil) # :nodoc:
|
98
|
+
super
|
99
|
+
@data.delete(escape_key(key))
|
100
|
+
rescue Dalli::DalliError => e
|
101
|
+
logger.error("DalliError: #{e.message}")
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def exist?(key, options = nil) # :nodoc:
|
106
|
+
# Doesn't call super, cause exist? in memcache is in fact a read
|
107
|
+
# But who cares? Reading is very fast anyway
|
108
|
+
# Local cache is checked first, if it doesn't know then memcache itself is read from
|
109
|
+
!read(key, options).nil?
|
110
|
+
end
|
111
|
+
|
112
|
+
# Increment a cached value. This method uses the memcached incr atomic
|
113
|
+
# operator and can only be used on values written with the :raw option.
|
114
|
+
# Calling it on a value not stored with :raw will initialize that value
|
115
|
+
# to zero.
|
116
|
+
def increment(key, amount = 1) # :nodoc:
|
117
|
+
log("incrementing", key, amount)
|
118
|
+
@data.incr(escape_key(key), amount)
|
119
|
+
rescue Dalli::DalliError => e
|
120
|
+
logger.error("DalliError: #{e.message}") if logger
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Decrement a cached value. This method uses the memcached decr atomic
|
125
|
+
# operator and can only be used on values written with the :raw option.
|
126
|
+
# Calling it on a value not stored with :raw will initialize that value
|
127
|
+
# to zero.
|
128
|
+
def decrement(key, amount = 1) # :nodoc:
|
129
|
+
log("decrement", key, amount)
|
130
|
+
@data.decr(escape_key(key), amount)
|
131
|
+
rescue Dalli::DalliError => e
|
132
|
+
logger.error("DalliError: #{e.message}") if logger
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete_matched(matcher, options = nil) # :nodoc:
|
137
|
+
# don't do any local caching at present, just pass
|
138
|
+
# through and let the error happen
|
139
|
+
super
|
140
|
+
raise "Not supported by Memcache"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Clear the entire cache on all memcached servers. This method should
|
144
|
+
# be used with care when using a shared cache.
|
145
|
+
def clear
|
146
|
+
@data.flush_all
|
147
|
+
end
|
148
|
+
|
149
|
+
# Get the statistics from the memcached servers.
|
150
|
+
def stats
|
151
|
+
@data.stats
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# Exists in 2.3.8 but not in 2.3.2 so roll our own version
|
157
|
+
def expires_in(options)
|
158
|
+
expires_in = (options && options[:expires_in]) || @expires_in
|
159
|
+
|
160
|
+
raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric)
|
161
|
+
|
162
|
+
expires_in || 0
|
163
|
+
end
|
164
|
+
|
165
|
+
def escape_key(key)
|
166
|
+
prefix = @namespace.is_a?(Proc) ? @namespace.call : @namespace
|
167
|
+
key = "#{prefix}:#{key}" if prefix
|
168
|
+
key
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|