libmemcached_store 0.2.3 → 0.6.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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +7 -0
- data/BENCHMARKS +37 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +78 -0
- data/Rakefile +24 -0
- data/gemfiles/rails30.gemfile +7 -0
- data/gemfiles/rails31.gemfile +7 -0
- data/gemfiles/rails32.gemfile +7 -0
- data/lib/action_dispatch/session/libmemcached_store.rb +80 -0
- data/lib/active_support/cache/libmemcached_store.rb +227 -81
- data/lib/libmemcached_store.rb +1 -1
- data/lib/memcached/get_with_flags.rb +42 -0
- data/lib/version.rb +3 -0
- data/libmemcached_store.gemspec +30 -0
- data/test/action_dispatch/abstract_unit.rb +43 -0
- data/test/action_dispatch/libmemcached_store_test.rb +200 -0
- data/test/active_support/libmemcached_store_test.rb +341 -0
- data/test/fixtures/session_autoload_test.rb +10 -0
- data/test/profile/benchmark.rb +94 -0
- data/test/test_helper.rb +5 -0
- metadata +132 -86
- data/README +0 -47
- data/lib/active_support/cache/compressed_libmemcached_store.rb +0 -15
- data/test/libmemcached_store_test.rb +0 -84
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd3263466b00f991543c49eae9ed95cf60808d48
|
4
|
+
data.tar.gz: 3f8780d95967e725cc6b85a2d11236616ee34baa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60147888a5c9aedc996e3597dc99deba43fac634c6e57febba5447208da63147fdd5387ede6cff4c3f6e6d5edccd25a137b6fe1840b6ca692e70c1cb68764a0a
|
7
|
+
data.tar.gz: 8be98b2735c5c6f9a0d004463622523defce2ca0e76263aae6f616fffc19dfedf7ffd3eedf026796bf46455b17843de910efd5ee4702d9b8c94d930dbffdd2bc
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/BENCHMARKS
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Testing with
|
2
|
+
ruby 1.9.3p286 (2012-10-12 revision 37165) [x86_64-darwin12.2.0]
|
3
|
+
Dalli 2.6.3
|
4
|
+
Libmemcached_store 0.6.0
|
5
|
+
user system total real
|
6
|
+
write:short:dalli 0.380000 0.040000 0.420000 ( 0.422342)
|
7
|
+
write:short:libm 0.040000 0.020000 0.060000 ( 0.125838)
|
8
|
+
write:long:dalli 0.370000 0.040000 0.410000 ( 0.407400)
|
9
|
+
write:long:libm 0.050000 0.030000 0.080000 ( 0.130482)
|
10
|
+
write:raw:dalli 0.330000 0.030000 0.360000 ( 0.371981)
|
11
|
+
write:raw:libm 0.050000 0.030000 0.080000 ( 0.122899)
|
12
|
+
|
13
|
+
read:miss:dalli 0.310000 0.040000 0.350000 ( 0.352729)
|
14
|
+
read:miss:libm 0.080000 0.040000 0.120000 ( 0.182143)
|
15
|
+
read:miss2:dalli 0.350000 0.040000 0.390000 ( 0.394071)
|
16
|
+
read:miss2:libm 0.090000 0.050000 0.140000 ( 0.190611)
|
17
|
+
read:exist:dalli 0.350000 0.040000 0.390000 ( 0.391384)
|
18
|
+
read:exist:libm 0.060000 0.040000 0.100000 ( 0.161597)
|
19
|
+
read:expired:dalli 0.350000 0.040000 0.390000 ( 0.399342)
|
20
|
+
read:expired:libm 0.080000 0.050000 0.130000 ( 0.175674)
|
21
|
+
read:raw:dalli 0.360000 0.040000 0.400000 ( 0.400142)
|
22
|
+
read:raw:libm 0.050000 0.040000 0.090000 ( 0.160905)
|
23
|
+
|
24
|
+
exist:miss:dalli 0.310000 0.040000 0.350000 ( 0.342454)
|
25
|
+
exist:miss:libm 0.040000 0.030000 0.070000 ( 0.141901)
|
26
|
+
exist:hit:dalli 0.320000 0.040000 0.360000 ( 0.354205)
|
27
|
+
exist:hit:libm 0.030000 0.020000 0.050000 ( 0.107114)
|
28
|
+
|
29
|
+
delete:miss:dalli 0.310000 0.040000 0.350000 ( 0.342343)
|
30
|
+
delete:miss:libm 0.060000 0.030000 0.090000 ( 0.141960)
|
31
|
+
delete:hit:dalli 0.350000 0.040000 0.390000 ( 0.389187)
|
32
|
+
delete:hit:libm 0.050000 0.020000 0.070000 ( 0.130604)
|
33
|
+
|
34
|
+
increment:dalli 0.380000 0.050000 0.430000 ( 0.422802)
|
35
|
+
increment:libm 0.040000 0.020000 0.060000 ( 0.119200)
|
36
|
+
decrement:dalli 0.390000 0.040000 0.430000 ( 0.426890)
|
37
|
+
decrement:libm 0.030000 0.030000 0.060000 ( 0.118954)
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.6.0
|
4
|
+
* New gem name _libmemcached_store_
|
5
|
+
* Handle Memcached::Error in read_multi (staugaard)
|
6
|
+
|
7
|
+
## 0.5.1
|
8
|
+
* Remove warning from latest version of mocha
|
9
|
+
* Make #clear compatible with Rails.cache#clear (grosser)
|
10
|
+
|
11
|
+
## 0.5.0
|
12
|
+
* Use Memcached#exist if available (performance improvement ~25%)
|
13
|
+
* Correctly escape bad characters and too long keys
|
14
|
+
* Add benchmarks
|
15
|
+
* Remove the use of ActiveSupport::Entry which was a performance bottleneck #3
|
16
|
+
|
17
|
+
## 0.4.0
|
18
|
+
* Optimize read_multi to only make one call to memecached server
|
19
|
+
* Update test suite to reflect Rails' one
|
20
|
+
* Add session store tests
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 37signals
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# LibmemcachedStore
|
2
|
+
|
3
|
+
An ActiveSupport cache store that uses the C-based libmemcached client through Evan Weaver's Ruby/SWIG wrapper, [memcached](https://github.com/evan/memcached). libmemcached is fast (fastest memcache client for Ruby), lightweight, and supports consistent hashing, non-blocking IO, and graceful server failover.
|
4
|
+
|
5
|
+
This cache is designed for Rails 3+ applications.
|
6
|
+
|
7
|
+
## Prerequisites
|
8
|
+
|
9
|
+
You'll need the memcached gem installed:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem install memcached
|
13
|
+
```
|
14
|
+
|
15
|
+
or in your Gemfile
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'memcached'
|
19
|
+
```
|
20
|
+
|
21
|
+
There are no other dependencies.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Just add to your Gemfile
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'libmemcached_store', '~> 0.6.0'
|
29
|
+
```
|
30
|
+
|
31
|
+
and you're set.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
This is a drop-in replacement for the memcache store that ships with Rails. To
|
36
|
+
enable, set the `config.cache_store` option to `libmemcached_store`
|
37
|
+
in the config for your environment
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
config.cache_store = :libmemcached_store
|
41
|
+
```
|
42
|
+
|
43
|
+
If no servers are specified, localhost is assumed. You can specify a list of
|
44
|
+
server addresses, either as hostnames or IP addresses, with or without a port
|
45
|
+
designation. If no port is given, 11211 is assumed:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
config.cache_store = :libmemcached_store, %w(cache-01 cache-02 127.0.0.1:11212)
|
49
|
+
```
|
50
|
+
|
51
|
+
Standard Rails cache store options can be used
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
config.cache_store = :libmemcached_store, '127.0.0.1:11211', {:compress => true, :expires_in => 3600}
|
55
|
+
```
|
56
|
+
|
57
|
+
More advanced options can be passed directly to the client
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
config.cache_store = :libmemcached_store, '127.0.0.1:11211', {:client => { :binary_protocol => true, :no_block => true }}
|
61
|
+
```
|
62
|
+
|
63
|
+
You can also use `:libmemcached_store` to store your application sessions
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
require 'action_dispatch/session/libmemcached_store'
|
67
|
+
config.session_store = :libmemcached_store, :namespace => '_session', :expire_after => 1800
|
68
|
+
```
|
69
|
+
|
70
|
+
## Performance
|
71
|
+
|
72
|
+
Used with Rails, __libmemcached_store__ is at least 1.5x faster than __dalli__. See [BENCHMARKS](https://github.com/ccocchi/libmemcached_store/blob/master/BENCHMARKS)
|
73
|
+
for details
|
74
|
+
|
75
|
+
## Props
|
76
|
+
|
77
|
+
Thanks to Brian Aker ([http://tangent.org](http://tangent.org)) for creating libmemcached, and Evan
|
78
|
+
Weaver ([http://blog.evanweaver.com](http://blog.evanweaver.com)) for the Ruby wrapper.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rdoc/task'
|
7
|
+
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.warning = false
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generate documentation for the libmemcached_store plugin.'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'LibmemcachedStore'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'memcached'
|
2
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
3
|
+
|
4
|
+
module ActionDispatch
|
5
|
+
module Session
|
6
|
+
class LibmemcachedStore < AbstractStore
|
7
|
+
|
8
|
+
def initialize(app, options = {})
|
9
|
+
options[:expire_after] ||= options[:expires]
|
10
|
+
super
|
11
|
+
client_options = { default_ttl: options.fetch(:expire_after, 0) }
|
12
|
+
client_options[:namespace] = options[:namespace] || 'rack:session'
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@pool = options[:cache] || Memcached.new(@default_options[:memcache_server], client_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_sid
|
20
|
+
loop do
|
21
|
+
sid = super
|
22
|
+
begin
|
23
|
+
@pool.exist(sid)
|
24
|
+
rescue Memcached::NotFound
|
25
|
+
break sid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_session(env, sid)
|
31
|
+
sid ||= generate_sid
|
32
|
+
session = with_lock(env, {}) do
|
33
|
+
begin
|
34
|
+
@pool.get(sid)
|
35
|
+
rescue Memcached::NotFound
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
[sid, session]
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_session(env, session_id, new_session, options = {})
|
43
|
+
expiry = options[:expire_after].to_i
|
44
|
+
|
45
|
+
with_lock(env, false) do
|
46
|
+
@pool.set(session_id, new_session, expiry)
|
47
|
+
session_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_session(env, session_id, options = {})
|
52
|
+
with_lock(env, nil) do
|
53
|
+
@pool.delete(session_id)
|
54
|
+
generate_sid unless options[:drop]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Deprecated since Rails 3.1.0
|
60
|
+
#
|
61
|
+
def destroy(env)
|
62
|
+
if sid = current_session_id(env)
|
63
|
+
with_lock(env, false) do
|
64
|
+
@pool.delete(sid)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_lock(env, default)
|
70
|
+
@mutex.lock if env['rack.multithread']
|
71
|
+
yield
|
72
|
+
rescue Memcached::Error => e
|
73
|
+
default
|
74
|
+
ensure
|
75
|
+
@mutex.unlock if @mutex.locked?
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,53 +1,138 @@
|
|
1
1
|
require 'memcached'
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
# The latest version of memcached (0.11) doesn't support hostnames with dashes
|
6
|
-
# in their names, so we overwrite it here to be more lenient.
|
7
|
-
def set_servers(servers)
|
8
|
-
[*servers].each_with_index do |server, index|
|
9
|
-
host, port = server.split(":")
|
10
|
-
Lib.memcached_server_add(@struct, host, port.to_i)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
2
|
+
require 'memcached/get_with_flags'
|
3
|
+
|
4
|
+
require 'digest/md5'
|
14
5
|
|
15
6
|
module ActiveSupport
|
16
7
|
module Cache
|
17
|
-
|
8
|
+
|
9
|
+
#
|
10
|
+
# Store using memcached gem as client
|
11
|
+
#
|
12
|
+
# Global options can be passed to be applied to each method by default.
|
13
|
+
# Supported options are
|
14
|
+
# * <tt>:compress</tt> : if set to true, data will be compress before stored
|
15
|
+
# * <tt>:compress_threshold</tt> : specify the threshold at which to compress
|
16
|
+
# value, default is 4K
|
17
|
+
# * <tt>:namespace</tt> : prepend each key with this value for simple namespacing
|
18
|
+
# * <tt>:expires_in</tt> : default TTL in seconds for each. Default value is 0, i.e. forever
|
19
|
+
# Specific value can be passed per key with write and fetch command.
|
20
|
+
#
|
21
|
+
# Options can also be passed direclty to the memcache client, via the <tt>:client</tt>
|
22
|
+
# option. For example, if you want to use pipelining, you can use
|
23
|
+
# :client => { :no_block => true }
|
24
|
+
#
|
25
|
+
class LibmemcachedStore
|
18
26
|
attr_reader :addresses
|
19
27
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
28
|
+
DEFAULT_CLIENT_OPTIONS = { distribution: :consistent_ketama, binary_protocol: true, default_ttl: 0 }
|
29
|
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
30
|
+
DEFAULT_COMPRESS_THRESHOLD = 4096
|
31
|
+
FLAG_COMPRESSED = 0x2
|
32
|
+
|
33
|
+
attr_reader :silence, :options
|
34
|
+
alias_method :silence?, :silence
|
35
|
+
|
36
|
+
# Silence the logger.
|
37
|
+
def silence!
|
38
|
+
@silence = true
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Silence the logger within a block.
|
43
|
+
def mute
|
44
|
+
previous_silence, @silence = defined?(@silence) && @silence, true
|
45
|
+
yield
|
46
|
+
ensure
|
47
|
+
@silence = previous_silence
|
48
|
+
end
|
25
49
|
|
26
50
|
def initialize(*addresses)
|
27
51
|
addresses.flatten!
|
28
52
|
options = addresses.extract_options!
|
29
|
-
|
53
|
+
client_options = options.delete(:client) || {}
|
54
|
+
if options[:namespace]
|
55
|
+
client_options[:prefix_key] = options.delete(:namespace)
|
56
|
+
client_options[:prefix_delimiter] = ':'
|
57
|
+
@namespace_length = client_options[:prefix_key].length + 1
|
58
|
+
else
|
59
|
+
@namespace_length = 0
|
60
|
+
end
|
61
|
+
client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]
|
30
62
|
|
63
|
+
@options = options.reverse_merge(compress_threshold: DEFAULT_COMPRESS_THRESHOLD)
|
31
64
|
@addresses = addresses
|
32
|
-
@cache = Memcached.new(@addresses,
|
33
|
-
extend
|
65
|
+
@cache = Memcached.new(@addresses, client_options.reverse_merge(DEFAULT_CLIENT_OPTIONS))
|
66
|
+
@cache.instance_eval { send(:extend, GetWithFlags) }
|
34
67
|
end
|
35
68
|
|
36
|
-
def
|
37
|
-
if
|
38
|
-
key
|
39
|
-
|
40
|
-
|
41
|
-
|
69
|
+
def fetch(key, options = nil)
|
70
|
+
if block_given?
|
71
|
+
key = expanded_key(key)
|
72
|
+
unless options && options[:force]
|
73
|
+
entry = instrument(:read, key, options) do |payload|
|
74
|
+
payload[:super_operation] = :fetch if payload
|
75
|
+
read_entry(key, options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if entry.nil?
|
80
|
+
result = instrument(:generate, key, options) do |payload|
|
81
|
+
yield
|
82
|
+
end
|
83
|
+
write_entry(key, result, options)
|
84
|
+
result
|
42
85
|
else
|
43
|
-
key
|
86
|
+
instrument(:fetch_hit, key, options) { |payload| }
|
87
|
+
entry
|
44
88
|
end
|
89
|
+
else
|
90
|
+
read(key, options)
|
45
91
|
end
|
46
92
|
end
|
47
93
|
|
48
94
|
def read(key, options = nil)
|
49
|
-
|
50
|
-
|
95
|
+
key = expanded_key(key)
|
96
|
+
instrument(:read, key, options) do |payload|
|
97
|
+
entry = read_entry(key, options)
|
98
|
+
payload[:hit] = !!entry if payload
|
99
|
+
entry
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def write(key, value, options = nil)
|
104
|
+
key = expanded_key(key)
|
105
|
+
instrument(:write, key, options) do |payload|
|
106
|
+
write_entry(key, value, options)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete(key, options = nil)
|
111
|
+
key = expanded_key(key)
|
112
|
+
instrument(:delete, key) do |payload|
|
113
|
+
delete_entry(key, options)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def exist?(key, options = nil)
|
118
|
+
key = expanded_key(key)
|
119
|
+
instrument(:exist?, key) do |payload|
|
120
|
+
if @cache.respond_to?(:exist)
|
121
|
+
@cache.exist(escape_and_normalize(key))
|
122
|
+
true
|
123
|
+
else
|
124
|
+
read_entry(key, options) != nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue Memcached::NotFound
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
def increment(key, amount = 1, options = nil)
|
132
|
+
key = expanded_key(key)
|
133
|
+
instrument(:increment, key, amount: amount) do
|
134
|
+
@cache.incr(escape_and_normalize(key), amount)
|
135
|
+
end
|
51
136
|
rescue Memcached::NotFound
|
52
137
|
nil
|
53
138
|
rescue Memcached::Error => e
|
@@ -55,86 +140,147 @@ module ActiveSupport
|
|
55
140
|
nil
|
56
141
|
end
|
57
142
|
|
58
|
-
def
|
59
|
-
|
143
|
+
def decrement(key, amount = 1, options = nil)
|
144
|
+
key = expanded_key(key)
|
145
|
+
instrument(:decrement, key, amount: amount) do
|
146
|
+
@cache.decr(escape_and_normalize(key), amount)
|
147
|
+
end
|
148
|
+
rescue Memcached::NotFound
|
149
|
+
nil
|
150
|
+
rescue Memcached::Error => e
|
151
|
+
log_error(e)
|
152
|
+
nil
|
60
153
|
end
|
61
154
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
155
|
+
def read_multi(*names)
|
156
|
+
names.flatten!
|
157
|
+
options = names.extract_options!
|
158
|
+
|
159
|
+
return {} if names.empty?
|
160
|
+
|
161
|
+
mapping = Hash[names.map {|name| [escape_and_normalize(expanded_key(name)), name] }]
|
162
|
+
raw_values, flags = @cache.get(mapping.keys, false, true)
|
163
|
+
|
164
|
+
values = {}
|
165
|
+
raw_values.each do |key, value|
|
166
|
+
values[mapping[key]] = deserialize(value, options[:raw], flags[key])
|
167
|
+
end
|
168
|
+
values
|
169
|
+
rescue Memcached::Error => e
|
170
|
+
log_error(e)
|
171
|
+
{}
|
172
|
+
end
|
173
|
+
|
174
|
+
def clear(options = nil)
|
175
|
+
@cache.flush
|
176
|
+
end
|
177
|
+
|
178
|
+
def stats
|
179
|
+
@cache.stats
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
def read_entry(key, options = nil)
|
185
|
+
options ||= {}
|
186
|
+
raw_value, flags = @cache.get(escape_and_normalize(key), false, true)
|
187
|
+
deserialize(raw_value, options[:raw], flags)
|
188
|
+
rescue Memcached::NotFound
|
189
|
+
nil
|
190
|
+
rescue Memcached::Error => e
|
191
|
+
log_error(e)
|
192
|
+
nil
|
193
|
+
end
|
194
|
+
|
195
|
+
def write_entry(key, entry, options = nil)
|
196
|
+
options = options ? @options.merge(options) : @options
|
197
|
+
method = options[:unless_exist] ? :add : :set
|
198
|
+
entry = options[:raw] ? entry.to_s : Marshal.dump(entry)
|
199
|
+
flags = 0
|
200
|
+
|
201
|
+
if options[:compress] && entry.bytesize >= options[:compress_threshold]
|
202
|
+
entry = Zlib::Deflate.deflate(entry)
|
203
|
+
flags |= FLAG_COMPRESSED
|
204
|
+
end
|
205
|
+
|
206
|
+
@cache.send(method, escape_and_normalize(key), entry, options[:expires_in].to_i, false, flags)
|
68
207
|
true
|
69
208
|
rescue Memcached::Error => e
|
70
209
|
log_error(e)
|
71
210
|
false
|
72
211
|
end
|
73
212
|
|
74
|
-
def
|
75
|
-
|
76
|
-
@cache.delete(valid_key(key))
|
213
|
+
def delete_entry(key, options = nil)
|
214
|
+
@cache.delete(escape_and_normalize(key))
|
77
215
|
true
|
78
216
|
rescue Memcached::NotFound
|
79
|
-
|
217
|
+
false
|
80
218
|
rescue Memcached::Error => e
|
81
219
|
log_error(e)
|
82
220
|
false
|
83
221
|
end
|
84
222
|
|
85
|
-
|
86
|
-
!read(key, options).nil?
|
87
|
-
end
|
223
|
+
private
|
88
224
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
rescue
|
93
|
-
|
225
|
+
def deserialize(value, raw = false, flags = 0)
|
226
|
+
value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
|
227
|
+
raw ? value : Marshal.load(value)
|
228
|
+
rescue TypeError, ArgumentError
|
229
|
+
value
|
94
230
|
end
|
95
231
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
rescue Memcached::Error
|
100
|
-
nil
|
101
|
-
end
|
232
|
+
def escape_and_normalize(key)
|
233
|
+
key = key.to_s.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
234
|
+
key_length = key.length
|
102
235
|
|
103
|
-
|
104
|
-
super
|
105
|
-
raise NotImplementedError
|
106
|
-
end
|
236
|
+
return key if @namespace_length + key_length <= 250
|
107
237
|
|
108
|
-
|
109
|
-
|
110
|
-
@cache.flush
|
238
|
+
max_key_length = 213 - @namespace_length
|
239
|
+
"#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
|
111
240
|
end
|
112
241
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
242
|
+
def expanded_key(key) # :nodoc:
|
243
|
+
return key.cache_key.to_s if key.respond_to?(:cache_key)
|
244
|
+
|
245
|
+
case key
|
246
|
+
when Array
|
247
|
+
if key.size > 1
|
248
|
+
key = key.collect { |element| expanded_key(element) }
|
249
|
+
else
|
250
|
+
key = key.first
|
251
|
+
end
|
252
|
+
when Hash
|
253
|
+
key = key.sort_by { |k,_| k.to_s }.collect { |k, v| "#{k}=#{v}" }
|
254
|
+
end
|
116
255
|
|
117
|
-
|
118
|
-
# cases where processes fork, but continue sharing the same memcached
|
119
|
-
# connection. You want to call this after the fork to make sure the
|
120
|
-
# new process has its own connection.
|
121
|
-
def reset
|
122
|
-
@cache.reset
|
256
|
+
key.to_param
|
123
257
|
end
|
124
258
|
|
125
|
-
|
259
|
+
def instrument(operation, key, options=nil)
|
260
|
+
log(operation, key, options)
|
126
261
|
|
127
|
-
|
128
|
-
|
262
|
+
if ActiveSupport::Cache::Store.instrument
|
263
|
+
payload = { :key => key }
|
264
|
+
payload.merge!(options) if options.is_a?(Hash)
|
265
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
|
266
|
+
else
|
267
|
+
yield(nil)
|
129
268
|
end
|
269
|
+
end
|
130
270
|
|
131
|
-
|
132
|
-
|
133
|
-
|
271
|
+
def log(operation, key, options=nil)
|
272
|
+
return unless !silence? && logger && logger.debug?
|
273
|
+
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
|
274
|
+
end
|
134
275
|
|
135
|
-
|
136
|
-
|
137
|
-
|
276
|
+
def log_error(exception)
|
277
|
+
return unless !silence? && logger && logger.error?
|
278
|
+
logger.error "MemcachedError (#{exception.inspect}): #{exception.message}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def logger
|
282
|
+
Rails.logger
|
283
|
+
end
|
138
284
|
end
|
139
285
|
end
|
140
286
|
end
|