jashmenn-dalli 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"
@@ -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,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