mob-dalli 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
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="~> 3.0.0"')
24
+ system('rake test RAILS_VERSION=">= 3.0.0"')
25
+ end
26
+
27
+ # 'gem install rdoc' to upgrade RDoc if this is giving you errors
28
+ begin
29
+ require 'rdoc/task'
30
+ RDoc::Task.new do |rd|
31
+ rd.rdoc_files.include("lib/**/*.rb")
32
+ end
33
+ rescue LoadError
34
+ puts "Unable to load rdoc, run 'gem install rdoc' to fix this."
35
+ end
36
+
37
+ require 'rake/clean'
38
+ CLEAN.include "**/*.rbc"
39
+ CLEAN.include "**/.DS_Store"
@@ -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
@@ -0,0 +1,76 @@
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 = { :namespace => 'rack:session' }.merge(@default_options)
16
+
17
+ @pool = options[:cache] || begin
18
+ Dalli::Client.new(
19
+ @default_options[:memcache_server], @default_options)
20
+ end
21
+ @namespace = @default_options[:namespace]
22
+
23
+ super
24
+ end
25
+
26
+ def reset
27
+ @pool.reset
28
+ end
29
+
30
+ private
31
+
32
+ def get_session(env, sid)
33
+ sid ||= generate_sid
34
+ begin
35
+ session = @pool.get(sid) || {}
36
+ rescue Dalli::DalliError => ex
37
+ # re-raise ArgumentError so Rails' session abstract_store.rb can autoload any missing models
38
+ raise ArgumentError, ex.message if ex.message =~ /unmarshal/
39
+ Rails.logger.warn("Session::DalliStore#get: #{ex.message}")
40
+ session = {}
41
+ end
42
+ [sid, session]
43
+ end
44
+
45
+ def set_session(env, sid, session_data, options = nil)
46
+ options ||= env[ENV_SESSION_OPTIONS_KEY]
47
+ expiry = options[:expire_after]
48
+ @pool.set(sid, session_data, expiry)
49
+ sid
50
+ rescue Dalli::DalliError
51
+ Rails.logger.warn("Session::DalliStore#set: #{$!.message}")
52
+ false
53
+ end
54
+
55
+ def destroy_session(env, session_id, options)
56
+ begin
57
+ @pool.delete(session_id)
58
+ rescue Dalli::DalliError
59
+ Rails.logger.warn("Session::DalliStore#destroy_session: #{$!.message}")
60
+ end
61
+ return nil if options[:drop]
62
+ generate_sid
63
+ end
64
+
65
+ def destroy(env)
66
+ if sid = current_session_id(env)
67
+ @pool.delete(sid)
68
+ end
69
+ rescue Dalli::DalliError
70
+ Rails.logger.warn("Session::DalliStore#destroy: #{$!.message}")
71
+ false
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,184 @@
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
+ require 'active_support/cache'
10
+
11
+ module ActiveSupport
12
+ module Cache
13
+ # A cache store implementation which stores data in Memcached:
14
+ # http://www.memcached.org
15
+ #
16
+ # DalliStore implements the Strategy::LocalCache strategy which implements
17
+ # an in memory cache inside of a block.
18
+ class DalliStore < Store
19
+
20
+ ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/
21
+ RAW = { :raw => true }
22
+
23
+ # Creates a new DalliStore object, with the given memcached server
24
+ # addresses. Each address is either a host name, or a host-with-port string
25
+ # in the form of "host_name:port". For example:
26
+ #
27
+ # ActiveSupport::Cache::DalliStore.new("localhost", "server-downstairs.localnetwork:8229")
28
+ #
29
+ # If no addresses are specified, then DalliStore will connect to
30
+ # localhost port 11211 (the default memcached port).
31
+ #
32
+ def initialize(*addresses)
33
+ addresses = addresses.flatten
34
+ options = addresses.extract_options!
35
+ super(options)
36
+
37
+ addresses << 'localhost:11211' if addresses.empty?
38
+ options = options.dup
39
+ # Extend expiry by stale TTL or else memcached will never return stale data.
40
+ # See ActiveSupport::Cache#fetch.
41
+ options[:expires_in] += options[:race_condition_ttl] if options[:expires_in] && options[:race_condition_ttl]
42
+ @data = Dalli::Client.new(addresses, options)
43
+
44
+ extend Strategy::LocalCache
45
+ extend LocalCacheWithRaw
46
+ end
47
+
48
+ # Reads multiple keys from the cache using a single call to the
49
+ # servers for all keys. Options can be passed in the last argument.
50
+ def read_multi(*names)
51
+ options = names.extract_options!
52
+ options = merged_options(options)
53
+ keys_to_names = names.flatten.inject({}){|map, name| map[escape_key(namespaced_key(name, options))] = name; map}
54
+ raw_values = @data.get_multi(keys_to_names.keys, RAW)
55
+ values = {}
56
+ raw_values.each do |key, value|
57
+ entry = deserialize_entry(value)
58
+ values[keys_to_names[key]] = entry.value unless entry.expired?
59
+ end
60
+ values
61
+ end
62
+
63
+ # Increment a cached value. This method uses the memcached incr atomic
64
+ # operator and can only be used on values written with the :raw option.
65
+ # Calling it on a value not stored with :raw will fail.
66
+ # :initial defaults to the amount passed in, as if the counter was initially zero.
67
+ # memcached counters cannot hold negative values.
68
+ def increment(name, amount = 1, options = nil) # :nodoc:
69
+ options = merged_options(options)
70
+ initial = options[:initial] || amount
71
+ expires_in = options[:expires_in].to_i
72
+ response = instrument(:increment, name, :amount => amount) do
73
+ @data.incr(escape_key(namespaced_key(name, options)), amount, expires_in, initial)
74
+ end
75
+ rescue Dalli::DalliError => e
76
+ logger.error("DalliError: #{e.message}") if logger
77
+ nil
78
+ end
79
+
80
+ # Decrement a cached value. This method uses the memcached decr atomic
81
+ # operator and can only be used on values written with the :raw option.
82
+ # Calling it on a value not stored with :raw will fail.
83
+ # :initial defaults to zero, as if the counter was initially zero.
84
+ # memcached counters cannot hold negative values.
85
+ def decrement(name, amount = 1, options = nil) # :nodoc:
86
+ options = merged_options(options)
87
+ initial = options[:initial] || 0
88
+ expires_in = options[:expires_in].to_i
89
+ response = instrument(:decrement, name, :amount => amount) do
90
+ @data.decr(escape_key(namespaced_key(name, options)), amount, expires_in, initial)
91
+ end
92
+ rescue Dalli::DalliError => e
93
+ logger.error("DalliError: #{e.message}") if logger
94
+ nil
95
+ end
96
+
97
+ # Clear the entire cache on all memcached servers. This method should
98
+ # be used with care when using a shared cache.
99
+ def clear(options = nil)
100
+ @data.flush_all
101
+ end
102
+
103
+ # Get the statistics from the memcached servers.
104
+ def stats
105
+ @data.stats
106
+ end
107
+
108
+ def reset
109
+ @data.reset
110
+ end
111
+
112
+ protected
113
+
114
+ # This CacheStore impl controls value marshalling so we take special
115
+ # care to always pass :raw => true to the Dalli API so it does not
116
+ # double marshal.
117
+
118
+ # Read an entry from the cache.
119
+ def read_entry(key, options) # :nodoc:
120
+ deserialize_entry(@data.get(escape_key(key), RAW))
121
+ rescue Dalli::DalliError => e
122
+ logger.error("DalliError: #{e.message}") if logger
123
+ nil
124
+ end
125
+
126
+ # Write an entry to the cache.
127
+ def write_entry(key, entry, options) # :nodoc:
128
+ method = options[:unless_exist] ? :add : :set
129
+ value = options[:raw] ? entry.value.to_s : entry
130
+ expires_in = options[:expires_in].to_i
131
+ if expires_in > 0 && !options[:raw]
132
+ # Set the memcache expire a few minutes in the future to support race condition ttls on read
133
+ expires_in += 5.minutes
134
+ end
135
+ @data.send(method, escape_key(key), value, expires_in, options)
136
+ rescue Dalli::DalliError => e
137
+ logger.error("DalliError: #{e.message}") if logger
138
+ false
139
+ end
140
+
141
+ # Delete an entry from the cache.
142
+ def delete_entry(key, options) # :nodoc:
143
+ @data.delete(escape_key(key))
144
+ rescue Dalli::DalliError => e
145
+ logger.error("DalliError: #{e.message}") if logger
146
+ false
147
+ end
148
+
149
+ private
150
+ def escape_key(key)
151
+ key = key.to_s
152
+ key = key.force_encoding('ASCII-8BIT') if key.respond_to? :force_encoding
153
+ key = key.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"}
154
+ key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
155
+ key
156
+ end
157
+
158
+ def deserialize_entry(raw_value)
159
+ if raw_value
160
+ # FIXME: This is a terrible implementation for performance reasons:
161
+ # throwing an exception is much slower than some if logic.
162
+ entry = Marshal.load(raw_value) rescue raw_value
163
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
164
+ else
165
+ nil
166
+ end
167
+ end
168
+
169
+ # Provide support for raw values in the local cache strategy.
170
+ module LocalCacheWithRaw # :nodoc:
171
+ protected
172
+ def write_entry(key, entry, options) # :nodoc:
173
+ retval = super
174
+ if options[:raw] && local_cache && retval
175
+ raw_entry = Entry.new(entry.value.to_s)
176
+ raw_entry.expires_at = entry.expires_at
177
+ local_cache.write_entry(key, raw_entry, options)
178
+ end
179
+ retval
180
+ end
181
+ end
182
+ end
183
+ end
184
+ 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