dalli 2.6.4 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/Gemfile +1 -0
- data/History.md +26 -2
- data/LICENSE +1 -1
- data/README.md +21 -9
- data/dalli.gemspec +1 -1
- data/lib/active_support/cache/dalli_store.rb +79 -19
- data/lib/dalli/cas/client.rb +58 -0
- data/lib/dalli/client.rb +148 -108
- data/lib/dalli/server.rb +45 -28
- data/lib/dalli/version.rb +1 -1
- data/test/benchmark_test.rb +1 -1
- data/test/helper.rb +15 -0
- data/test/memcached_mock.rb +11 -1
- data/test/sasldb +1 -0
- data/test/test_active_support.rb +47 -9
- data/test/test_cas_client.rb +107 -0
- data/test/test_dalli.rb +70 -22
- data/test/test_sasl.rb +4 -4
- metadata +18 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4e1d2bd1cdadde91a31a3e1311e4189e54b57623
|
4
|
+
data.tar.gz: 6e2fbab2dd27e1cf5f62eef39d7292d1ac6f93b8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ad6caea0b89835f15f7ddc4fd4ecab66eaa87ed969020c41fae9324da520b3e9f3b15afe53d1b14e515eafcb29cbc76c62267ce12a8c708f525ab2863e62f682
|
7
|
+
data.tar.gz: 8646c41bab51faa972bd16c5e06157c1c201a862f911f3f961b252ce9fd96c1b583345068090b2606f1a302f5d106df072eb7a91acb2ecb1c73464b3ca2f6f58
|
data/Gemfile
CHANGED
data/History.md
CHANGED
@@ -1,11 +1,35 @@
|
|
1
1
|
Dalli Changelog
|
2
2
|
=====================
|
3
3
|
|
4
|
-
|
4
|
+
2.7.0
|
5
|
+
==========
|
6
|
+
|
7
|
+
- Multithreading support with dalli\_store:
|
8
|
+
Use :pool\_size to create a pool of shared, threadsafe Dalli clients in Rails:
|
9
|
+
```ruby
|
10
|
+
config.cache_store = :dalli_store, "cache-1.example.com", "cache-2.example.com", :compress => true, :pool_size => 5, :expires_in => 300
|
11
|
+
```
|
12
|
+
This will ensure the Rails.cache singleton does not become a source of contention.
|
13
|
+
**PLEASE NOTE** Rails's :mem\_cache\_store does not support pooling as of
|
14
|
+
Rails 4.0. You must use :dalli\_store.
|
15
|
+
|
16
|
+
- Implement `version` for retrieving version of connected servers [dterei, #384]
|
17
|
+
- Implement `fetch_multi` for batched read/write [sorentwo, #380]
|
18
|
+
- Add more support for safe updates with multiple writers: [philipmw, #395]
|
19
|
+
`require 'dalli/cas/client'` augments Dalli::Client with the following methods:
|
20
|
+
* Get value with CAS: `[value, cas] = get_cas(key)`
|
21
|
+
`get_cas(key) {|value, cas| ...}`
|
22
|
+
* Get multiple values with CAS: `get_multi_cas(k1, k2, ...) {|value, metadata| cas = metadata[:cas]}`
|
23
|
+
* Set value with CAS: `new_cas = set_cas(key, value, cas, ttl, options)`
|
24
|
+
* Replace value with CAS: `replace_cas(key, new_value, cas, ttl, options)`
|
25
|
+
* Delete value with CAS: `delete_cas(key, cas)`
|
26
|
+
- Fix bug with get key with "Not found" value [uzzz, #375]
|
27
|
+
|
28
|
+
2.6.4
|
5
29
|
=======
|
6
30
|
|
7
31
|
- Fix ADD command, aka `write(unless_exist: true)` (pitr, #365)
|
8
|
-
- Upgrade test suite from
|
32
|
+
- Upgrade test suite from mini\_shoulda to minitest.
|
9
33
|
- Even more performance improvements for get\_multi (xaop, #331)
|
10
34
|
|
11
35
|
2.6.3
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Dalli [![Build Status](https://secure.travis-ci.org/mperham/dalli.png)](http://travis-ci.org/mperham/dalli) [![Dependency Status](https://gemnasium.com/mperham/dalli.png)](https://gemnasium.com/mperham/dalli)
|
1
|
+
Dalli [![Build Status](https://secure.travis-ci.org/mperham/dalli.png)](http://travis-ci.org/mperham/dalli) [![Dependency Status](https://gemnasium.com/mperham/dalli.png)](https://gemnasium.com/mperham/dalli) [![Code Climate](https://codeclimate.com/github/mperham/dalli.png)](https://codeclimate.com/github/mperham/dalli)
|
2
2
|
=====
|
3
3
|
|
4
4
|
Dalli is a high performance pure Ruby client for accessing memcached servers. It works with memcached 1.4+ only as it uses the newer binary protocol. It should be considered a replacement for the memcache-client gem.
|
@@ -35,8 +35,7 @@ Supported Ruby versions and implementations
|
|
35
35
|
Dalli should work identically on:
|
36
36
|
|
37
37
|
* JRuby 1.6+
|
38
|
-
* Ruby 1.9.
|
39
|
-
* Ruby 1.8.7+
|
38
|
+
* Ruby 1.9.3+
|
40
39
|
* Rubinius 2.0
|
41
40
|
|
42
41
|
If you have problems, please enter an issue.
|
@@ -73,7 +72,7 @@ Dalli has no runtime dependencies and never will. You can optionally install th
|
|
73
72
|
give Dalli a 20-30% performance boost.
|
74
73
|
|
75
74
|
|
76
|
-
Usage with Rails 3.x
|
75
|
+
Usage with Rails 3.x and 4.x
|
77
76
|
---------------------------
|
78
77
|
|
79
78
|
In your Gemfile:
|
@@ -113,8 +112,22 @@ Rails.application.config.session_store :dalli_store, :memcache_server => ['host1
|
|
113
112
|
Dalli does not support Rails 2.x.
|
114
113
|
|
115
114
|
|
115
|
+
Multithreading and Rails
|
116
|
+
--------------------------
|
117
|
+
|
118
|
+
If you use Puma or another threaded app server, as of Dalli 2.7, you can use a pool
|
119
|
+
of Dalli clients with Rails to ensure the `Rails.cache` singleton does not become a
|
120
|
+
source of thread contention. You must add `gem 'connection_pool'` to your Gemfile and
|
121
|
+
add :pool\_size to your `dalli_store` config:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
config.cache_store = :dalli_store, 'cache-1.example.com', { :pool_size => 5 }
|
125
|
+
```
|
126
|
+
|
127
|
+
|
116
128
|
Configuration
|
117
129
|
------------------------
|
130
|
+
|
118
131
|
Dalli::Client accepts the following options. All times are in seconds.
|
119
132
|
|
120
133
|
**expires_in**: Global default for key TTL. Default is 0, which means no expiry.
|
@@ -161,13 +174,14 @@ socket.
|
|
161
174
|
|
162
175
|
Note that Dalli does not require ActiveSupport or Rails. You can safely use it in your own Ruby projects.
|
163
176
|
|
164
|
-
[View the API](http://www.
|
177
|
+
[View the Client API](http://www.rubydoc.info/github/mperham/dalli/Dalli/Client)
|
165
178
|
|
166
179
|
Helping Out
|
167
180
|
-------------
|
168
181
|
|
169
182
|
If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update History.md with a one sentence description of your fix so you get credit as a contributor.
|
170
183
|
|
184
|
+
We're not accepting new compressors. They are trivial to add in an initializer. See #385 (LZ4), #406 (Snappy)
|
171
185
|
|
172
186
|
Thanks
|
173
187
|
------------
|
@@ -182,12 +196,10 @@ Brian Mitchell - for his remix-stash project which was helpful when implementing
|
|
182
196
|
Author
|
183
197
|
----------
|
184
198
|
|
185
|
-
Mike Perham,
|
186
|
-
|
187
|
-
<a href='http://www.pledgie.com/campaigns/16623'><img alt='Click here to lend your support to Open Source and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/16623.png?skin_name=chrome' border='0' /></a>
|
199
|
+
Mike Perham, [mikeperham.com](http://mikeperham.com), [@mperham](http://twitter.com/mperham)
|
188
200
|
|
189
201
|
|
190
202
|
Copyright
|
191
203
|
-----------
|
192
204
|
|
193
|
-
Copyright (c)
|
205
|
+
Copyright (c) Mike Perham. See LICENSE for details.
|
data/dalli.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.require_paths = ["lib"]
|
23
23
|
s.summary = %q{High performance memcached client for Ruby}
|
24
24
|
s.test_files = Dir.glob("test/**/*")
|
25
|
-
s.add_development_dependency(%q<minitest>, [">=
|
25
|
+
s.add_development_dependency(%q<minitest>, [">= 4.2.0"])
|
26
26
|
s.add_development_dependency(%q<mocha>, [">= 0"])
|
27
27
|
s.add_development_dependency(%q<rails>, ["~> 3"])
|
28
28
|
end
|
@@ -33,29 +33,52 @@ module ActiveSupport
|
|
33
33
|
# If no addresses are specified, then DalliStore will connect to
|
34
34
|
# localhost port 11211 (the default memcached port).
|
35
35
|
#
|
36
|
+
# Connection Pool support
|
37
|
+
#
|
38
|
+
# If you are using multithreaded Rails, the Rails.cache singleton can become a source
|
39
|
+
# of contention. You can use a connection pool of Dalli clients with Rails.cache by
|
40
|
+
# passing :pool_size and/or :pool_timeout:
|
41
|
+
#
|
42
|
+
# config.cache_store = :dalli_store, 'localhost:11211', :pool_size => 10
|
43
|
+
#
|
44
|
+
# Both pool options default to 5. You must include the `connection_pool` gem if you
|
45
|
+
# wish to use pool support.
|
46
|
+
#
|
36
47
|
def initialize(*addresses)
|
37
48
|
addresses = addresses.flatten
|
38
49
|
options = addresses.extract_options!
|
39
50
|
@options = options.dup
|
51
|
+
|
52
|
+
pool_options = {}
|
53
|
+
pool_options[:size] = options[:pool_size] if options[:pool_size]
|
54
|
+
pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout]
|
55
|
+
|
40
56
|
@options[:compress] ||= @options[:compression]
|
41
|
-
@raise_errors = !!@options[:raise_errors]
|
42
57
|
servers = if addresses.empty?
|
43
58
|
nil # use the default from Dalli::Client
|
44
59
|
else
|
45
60
|
addresses
|
46
61
|
end
|
47
|
-
|
62
|
+
if pool_options.empty?
|
63
|
+
@data = Dalli::Client.new(servers, @options)
|
64
|
+
else
|
65
|
+
@data = ::ConnectionPool.new(pool_options) { Dalli::Client.new(servers, @options.merge(:threadsafe => false)) }
|
66
|
+
end
|
48
67
|
|
49
68
|
extend Strategy::LocalCache
|
50
69
|
end
|
51
70
|
|
52
71
|
##
|
53
|
-
# Access the underlying Dalli::Client instance for
|
72
|
+
# Access the underlying Dalli::Client or ConnectionPool instance for
|
54
73
|
# access to get_multi, etc.
|
55
74
|
def dalli
|
56
75
|
@data
|
57
76
|
end
|
58
77
|
|
78
|
+
def with(&block)
|
79
|
+
@data.with(&block)
|
80
|
+
end
|
81
|
+
|
59
82
|
def fetch(name, options=nil)
|
60
83
|
options ||= {}
|
61
84
|
name = expanded_key name
|
@@ -103,7 +126,10 @@ module ActiveSupport
|
|
103
126
|
name = expanded_key name
|
104
127
|
|
105
128
|
instrument(:write, name, options) do |payload|
|
106
|
-
|
129
|
+
with do |connection|
|
130
|
+
options = options.merge(:connection => connection)
|
131
|
+
write_entry(name, value, options)
|
132
|
+
end
|
107
133
|
end
|
108
134
|
end
|
109
135
|
|
@@ -139,7 +165,8 @@ module ActiveSupport
|
|
139
165
|
end
|
140
166
|
end
|
141
167
|
|
142
|
-
|
168
|
+
data = with { |c| c.get_multi(mapping.keys - results.keys) }
|
169
|
+
results.merge!(data)
|
143
170
|
results.inject({}) do |memo, (inner, _)|
|
144
171
|
entry = results[inner]
|
145
172
|
# NB Backwards data compatibility, to be removed at some point
|
@@ -151,6 +178,35 @@ module ActiveSupport
|
|
151
178
|
end
|
152
179
|
end
|
153
180
|
|
181
|
+
# Fetches data from the cache, using the given keys. If there is data in
|
182
|
+
# the cache with the given keys, then that data is returned. Otherwise,
|
183
|
+
# the supplied block is called for each key for which there was no data,
|
184
|
+
# and the result will be written to the cache and returned.
|
185
|
+
def fetch_multi(*names)
|
186
|
+
options = names.extract_options!
|
187
|
+
mapping = names.inject({}) { |memo, name| memo[expanded_key(name)] = name; memo }
|
188
|
+
|
189
|
+
instrument(:fetch_multi, names) do
|
190
|
+
with do |connection|
|
191
|
+
results = connection.get_multi(mapping.keys)
|
192
|
+
|
193
|
+
connection.multi do
|
194
|
+
mapping.inject({}) do |memo, (expanded, name)|
|
195
|
+
memo[name] = results[expanded]
|
196
|
+
if memo[name].nil?
|
197
|
+
value = yield(name)
|
198
|
+
memo[name] = value
|
199
|
+
options = options.merge(:connection => connection)
|
200
|
+
write_entry(expanded, value, options)
|
201
|
+
end
|
202
|
+
|
203
|
+
memo
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
154
210
|
# Increment a cached value. This method uses the memcached incr atomic
|
155
211
|
# operator and can only be used on values written with the :raw option.
|
156
212
|
# Calling it on a value not stored with :raw will fail.
|
@@ -162,11 +218,11 @@ module ActiveSupport
|
|
162
218
|
initial = options.has_key?(:initial) ? options[:initial] : amount
|
163
219
|
expires_in = options[:expires_in]
|
164
220
|
instrument(:increment, name, :amount => amount) do
|
165
|
-
|
221
|
+
with { |c| c.incr(name, amount, expires_in, initial) }
|
166
222
|
end
|
167
223
|
rescue Dalli::DalliError => e
|
168
224
|
logger.error("DalliError: #{e.message}") if logger
|
169
|
-
raise if
|
225
|
+
raise if raise_errors?
|
170
226
|
nil
|
171
227
|
end
|
172
228
|
|
@@ -181,11 +237,11 @@ module ActiveSupport
|
|
181
237
|
initial = options.has_key?(:initial) ? options[:initial] : 0
|
182
238
|
expires_in = options[:expires_in]
|
183
239
|
instrument(:decrement, name, :amount => amount) do
|
184
|
-
|
240
|
+
with { |c| c.decr(name, amount, expires_in, initial) }
|
185
241
|
end
|
186
242
|
rescue Dalli::DalliError => e
|
187
243
|
logger.error("DalliError: #{e.message}") if logger
|
188
|
-
raise if
|
244
|
+
raise if raise_errors?
|
189
245
|
nil
|
190
246
|
end
|
191
247
|
|
@@ -193,11 +249,11 @@ module ActiveSupport
|
|
193
249
|
# be used with care when using a shared cache.
|
194
250
|
def clear(options=nil)
|
195
251
|
instrument(:clear, 'flushing all keys') do
|
196
|
-
|
252
|
+
with { |c| c.flush_all }
|
197
253
|
end
|
198
254
|
rescue Dalli::DalliError => e
|
199
255
|
logger.error("DalliError: #{e.message}") if logger
|
200
|
-
raise if
|
256
|
+
raise if raise_errors?
|
201
257
|
nil
|
202
258
|
end
|
203
259
|
|
@@ -207,11 +263,11 @@ module ActiveSupport
|
|
207
263
|
|
208
264
|
# Get the statistics from the memcached servers.
|
209
265
|
def stats
|
210
|
-
|
266
|
+
with { |c| c.stats }
|
211
267
|
end
|
212
268
|
|
213
269
|
def reset
|
214
|
-
|
270
|
+
with { |c| c.reset }
|
215
271
|
end
|
216
272
|
|
217
273
|
def logger
|
@@ -226,12 +282,12 @@ module ActiveSupport
|
|
226
282
|
|
227
283
|
# Read an entry from the cache.
|
228
284
|
def read_entry(key, options) # :nodoc:
|
229
|
-
entry =
|
285
|
+
entry = with { |c| c.get(key, options) }
|
230
286
|
# NB Backwards data compatibility, to be removed at some point
|
231
287
|
entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry
|
232
288
|
rescue Dalli::DalliError => e
|
233
289
|
logger.error("DalliError: #{e.message}") if logger
|
234
|
-
raise if
|
290
|
+
raise if raise_errors?
|
235
291
|
nil
|
236
292
|
end
|
237
293
|
|
@@ -241,19 +297,20 @@ module ActiveSupport
|
|
241
297
|
cleanup if options[:unless_exist]
|
242
298
|
method = options[:unless_exist] ? :add : :set
|
243
299
|
expires_in = options[:expires_in]
|
244
|
-
|
300
|
+
connection = options.delete(:connection)
|
301
|
+
connection.send(method, key, value, expires_in, options)
|
245
302
|
rescue Dalli::DalliError => e
|
246
303
|
logger.error("DalliError: #{e.message}") if logger
|
247
|
-
raise if
|
304
|
+
raise if raise_errors?
|
248
305
|
false
|
249
306
|
end
|
250
307
|
|
251
308
|
# Delete an entry from the cache.
|
252
309
|
def delete_entry(key, options) # :nodoc:
|
253
|
-
|
310
|
+
with { |c| c.delete(key) }
|
254
311
|
rescue Dalli::DalliError => e
|
255
312
|
logger.error("DalliError: #{e.message}") if logger
|
256
|
-
raise if
|
313
|
+
raise if raise_errors?
|
257
314
|
false
|
258
315
|
end
|
259
316
|
|
@@ -296,6 +353,9 @@ module ActiveSupport
|
|
296
353
|
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
|
297
354
|
end
|
298
355
|
|
356
|
+
def raise_errors?
|
357
|
+
!!@options[:raise_errors]
|
358
|
+
end
|
299
359
|
end
|
300
360
|
end
|
301
361
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'dalli/client'
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
class Client
|
5
|
+
##
|
6
|
+
# Get the value and CAS ID associated with the key. If a block is provided,
|
7
|
+
# value and CAS will be passed to the block.
|
8
|
+
def get_cas(key)
|
9
|
+
(value, cas) = perform(:cas, key)
|
10
|
+
value = (!value || value == 'Not found') ? nil : value
|
11
|
+
if block_given?
|
12
|
+
yield value, cas
|
13
|
+
else
|
14
|
+
[value, cas]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Fetch multiple keys efficiently, including available metadata such as CAS.
|
20
|
+
# If a block is given, yields key/data pairs one a time. Data is an array:
|
21
|
+
# [value, cas_id]
|
22
|
+
# If no block is given, returns a hash of
|
23
|
+
# { 'key' => [value, cas_id] }
|
24
|
+
def get_multi_cas(*keys)
|
25
|
+
if block_given?
|
26
|
+
get_multi_yielder(keys) {|*args| yield(*args)}
|
27
|
+
else
|
28
|
+
Hash.new.tap do |hash|
|
29
|
+
get_multi_yielder(keys) {|k, data| hash[k] = data}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Set the key-value pair, verifying existing CAS.
|
36
|
+
# Returns the resulting CAS value if succeeded, and false otherwise.
|
37
|
+
def set_cas(key, value, cas, ttl=nil, options=nil)
|
38
|
+
ttl ||= @options[:expires_in].to_i
|
39
|
+
perform(:set, key, value, ttl, cas, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Conditionally add a key/value pair, verifying existing CAS, only if the
|
44
|
+
# key already exists on the server. Returns the new CAS value if the
|
45
|
+
# operation succeeded, or false otherwise.
|
46
|
+
def replace_cas(key, value, cas, ttl=nil, options=nil)
|
47
|
+
ttl ||= @options[:expires_in].to_i
|
48
|
+
perform(:replace, key, value, ttl, cas, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Delete a key/value pair, verifying existing CAS.
|
52
|
+
# Returns true if succeeded, and false otherwise.
|
53
|
+
def delete_cas(key, cas=0)
|
54
|
+
perform(:delete, key, cas)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/dalli/client.rb
CHANGED
@@ -31,17 +31,6 @@ module Dalli
|
|
31
31
|
@ring = nil
|
32
32
|
end
|
33
33
|
|
34
|
-
##
|
35
|
-
# Normalizes the argument into an array of servers. If the argument is a string, it's expected to be of
|
36
|
-
# the format "memcache1.example.com:11211[,memcache2.example.com:11211[,memcache3.example.com:11211[...]]]
|
37
|
-
def normalize_servers(servers)
|
38
|
-
if servers.is_a? String
|
39
|
-
return servers.split(",")
|
40
|
-
else
|
41
|
-
return servers
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
34
|
#
|
46
35
|
# The standard memcached instruction set
|
47
36
|
#
|
@@ -58,107 +47,22 @@ module Dalli
|
|
58
47
|
Thread.current[:dalli_multi] = old
|
59
48
|
end
|
60
49
|
|
50
|
+
##
|
51
|
+
# Get the value associated with the key.
|
61
52
|
def get(key, options=nil)
|
62
|
-
|
63
|
-
resp.nil? || 'Not found' == resp ? nil : resp
|
53
|
+
perform(:get, key)
|
64
54
|
end
|
65
55
|
|
66
56
|
##
|
67
57
|
# Fetch multiple keys efficiently.
|
68
|
-
#
|
58
|
+
# If a block is given, yields key/value pairs one at a time.
|
59
|
+
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
69
60
|
def get_multi(*keys)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
begin
|
76
|
-
mapped_keys = keys.flatten.map {|a| validate_key(a.to_s)}
|
77
|
-
groups = mapped_keys.flatten.group_by do |key|
|
78
|
-
begin
|
79
|
-
ring.server_for_key(key)
|
80
|
-
rescue Dalli::RingError
|
81
|
-
Dalli.logger.debug { "unable to get key #{key}" }
|
82
|
-
nil
|
83
|
-
end
|
84
|
-
end
|
85
|
-
if unfound_keys = groups.delete(nil)
|
86
|
-
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
87
|
-
end
|
88
|
-
|
89
|
-
groups.each do |server, keys_for_server|
|
90
|
-
begin
|
91
|
-
# TODO: do this with the perform chokepoint?
|
92
|
-
# But given the fact that fetching the response doesn't take place
|
93
|
-
# in that slot it's misleading anyway. Need to move all of this method
|
94
|
-
# into perform to be meaningful
|
95
|
-
server.request(:send_multiget, keys_for_server)
|
96
|
-
rescue DalliError, NetworkError => e
|
97
|
-
Dalli.logger.debug { e.inspect }
|
98
|
-
Dalli.logger.debug { "unable to get keys for server #{server.hostname}:#{server.port}" }
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
servers = groups.keys
|
103
|
-
values = {}
|
104
|
-
return values if servers.empty?
|
105
|
-
|
106
|
-
servers.each do |server|
|
107
|
-
next unless server.alive?
|
108
|
-
begin
|
109
|
-
server.multi_response_start
|
110
|
-
rescue DalliError, NetworkError => e
|
111
|
-
Dalli.logger.debug { e.inspect }
|
112
|
-
Dalli.logger.debug { "results from this server will be missing" }
|
113
|
-
servers.delete(server)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
start = Time.now
|
118
|
-
loop do
|
119
|
-
# remove any dead servers
|
120
|
-
servers.delete_if { |s| s.sock.nil? }
|
121
|
-
break if servers.empty?
|
122
|
-
|
123
|
-
# calculate remaining timeout
|
124
|
-
elapsed = Time.now - start
|
125
|
-
timeout = servers.first.options[:socket_timeout]
|
126
|
-
if elapsed > timeout
|
127
|
-
readable = nil
|
128
|
-
else
|
129
|
-
sockets = servers.map(&:sock)
|
130
|
-
readable, _ = IO.select(sockets, nil, nil, timeout - elapsed)
|
131
|
-
end
|
132
|
-
|
133
|
-
if readable.nil?
|
134
|
-
# no response within timeout; abort pending connections
|
135
|
-
servers.each do |server|
|
136
|
-
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
137
|
-
server.multi_response_abort
|
138
|
-
end
|
139
|
-
break
|
140
|
-
|
141
|
-
else
|
142
|
-
readable.each do |sock|
|
143
|
-
server = sock.server
|
144
|
-
|
145
|
-
begin
|
146
|
-
server.multi_response_nonblock.each do |key, value|
|
147
|
-
values[key_without_namespace(key)] = value
|
148
|
-
end
|
149
|
-
|
150
|
-
if server.multi_response_completed?
|
151
|
-
servers.delete(server)
|
152
|
-
end
|
153
|
-
rescue NetworkError
|
154
|
-
servers.delete(server)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
values
|
161
|
-
end
|
61
|
+
if block_given?
|
62
|
+
get_multi_yielder(keys) {|k, data| yield k, data.first}
|
63
|
+
else
|
64
|
+
Hash.new.tap do |hash|
|
65
|
+
get_multi_yielder(keys) {|k, data| hash[k] = data.first}
|
162
66
|
end
|
163
67
|
end
|
164
68
|
end
|
@@ -212,11 +116,11 @@ module Dalli
|
|
212
116
|
# on the server. Returns true if the operation succeeded.
|
213
117
|
def replace(key, value, ttl=nil, options=nil)
|
214
118
|
ttl ||= @options[:expires_in].to_i
|
215
|
-
perform(:replace, key, value, ttl, options)
|
119
|
+
perform(:replace, key, value, ttl, 0, options)
|
216
120
|
end
|
217
121
|
|
218
122
|
def delete(key)
|
219
|
-
perform(:delete, key)
|
123
|
+
perform(:delete, key, 0)
|
220
124
|
end
|
221
125
|
|
222
126
|
##
|
@@ -308,6 +212,16 @@ module Dalli
|
|
308
212
|
end
|
309
213
|
end
|
310
214
|
|
215
|
+
##
|
216
|
+
## Version of the memcache servers.
|
217
|
+
def version
|
218
|
+
values = {}
|
219
|
+
ring.servers.each do |server|
|
220
|
+
values["#{server.hostname}:#{server.port}"] = server.alive? ? server.request(:version) : nil
|
221
|
+
end
|
222
|
+
values
|
223
|
+
end
|
224
|
+
|
311
225
|
##
|
312
226
|
# Close our connection to each server.
|
313
227
|
# If you perform another operation after this, the connections will be re-established.
|
@@ -319,8 +233,69 @@ module Dalli
|
|
319
233
|
end
|
320
234
|
alias_method :reset, :close
|
321
235
|
|
236
|
+
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
237
|
+
def with
|
238
|
+
yield self
|
239
|
+
end
|
240
|
+
|
322
241
|
private
|
323
242
|
|
243
|
+
def groups_for_keys(*keys)
|
244
|
+
groups = mapped_keys(keys).flatten.group_by do |key|
|
245
|
+
begin
|
246
|
+
ring.server_for_key(key)
|
247
|
+
rescue Dalli::RingError
|
248
|
+
Dalli.logger.debug { "unable to get key #{key}" }
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
end
|
252
|
+
return groups
|
253
|
+
end
|
254
|
+
|
255
|
+
def mapped_keys(keys)
|
256
|
+
keys.flatten.map {|a| validate_key(a.to_s)}
|
257
|
+
end
|
258
|
+
|
259
|
+
def make_multi_get_requests(groups)
|
260
|
+
groups.each do |server, keys_for_server|
|
261
|
+
begin
|
262
|
+
# TODO: do this with the perform chokepoint?
|
263
|
+
# But given the fact that fetching the response doesn't take place
|
264
|
+
# in that slot it's misleading anyway. Need to move all of this method
|
265
|
+
# into perform to be meaningful
|
266
|
+
server.request(:send_multiget, keys_for_server)
|
267
|
+
rescue DalliError, NetworkError => e
|
268
|
+
Dalli.logger.debug { e.inspect }
|
269
|
+
Dalli.logger.debug { "unable to get keys for server #{server.hostname}:#{server.port}" }
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def perform_multi_response_start(servers)
|
275
|
+
servers.each do |server|
|
276
|
+
next unless server.alive?
|
277
|
+
begin
|
278
|
+
server.multi_response_start
|
279
|
+
rescue DalliError, NetworkError => e
|
280
|
+
Dalli.logger.debug { e.inspect }
|
281
|
+
Dalli.logger.debug { "results from this server will be missing" }
|
282
|
+
servers.delete(server)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
servers
|
286
|
+
end
|
287
|
+
|
288
|
+
##
|
289
|
+
# Normalizes the argument into an array of servers. If the argument is a string, it's expected to be of
|
290
|
+
# the format "memcache1.example.com:11211[,memcache2.example.com:11211[,memcache3.example.com:11211[...]]]
|
291
|
+
def normalize_servers(servers)
|
292
|
+
if servers.is_a? String
|
293
|
+
return servers.split(",")
|
294
|
+
else
|
295
|
+
return servers
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
324
299
|
def ring
|
325
300
|
@ring ||= Dalli::Ring.new(
|
326
301
|
@servers.map do |s|
|
@@ -388,5 +363,70 @@ module Dalli
|
|
388
363
|
end
|
389
364
|
opts
|
390
365
|
end
|
366
|
+
|
367
|
+
##
|
368
|
+
# Yields, one at a time, keys and their values+attributes.
|
369
|
+
def get_multi_yielder(keys)
|
370
|
+
perform do
|
371
|
+
return {} if keys.empty?
|
372
|
+
ring.lock do
|
373
|
+
begin
|
374
|
+
groups = groups_for_keys(keys)
|
375
|
+
if unfound_keys = groups.delete(nil)
|
376
|
+
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
377
|
+
end
|
378
|
+
make_multi_get_requests(groups)
|
379
|
+
|
380
|
+
servers = groups.keys
|
381
|
+
return if servers.empty?
|
382
|
+
servers = perform_multi_response_start(servers)
|
383
|
+
|
384
|
+
start = Time.now
|
385
|
+
loop do
|
386
|
+
# remove any dead servers
|
387
|
+
servers.delete_if { |s| s.sock.nil? }
|
388
|
+
break if servers.empty?
|
389
|
+
|
390
|
+
# calculate remaining timeout
|
391
|
+
elapsed = Time.now - start
|
392
|
+
timeout = servers.first.options[:socket_timeout]
|
393
|
+
if elapsed > timeout
|
394
|
+
readable = nil
|
395
|
+
else
|
396
|
+
sockets = servers.map(&:sock)
|
397
|
+
readable, _ = IO.select(sockets, nil, nil, timeout - elapsed)
|
398
|
+
end
|
399
|
+
|
400
|
+
if readable.nil?
|
401
|
+
# no response within timeout; abort pending connections
|
402
|
+
servers.each do |server|
|
403
|
+
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
404
|
+
server.multi_response_abort
|
405
|
+
end
|
406
|
+
break
|
407
|
+
|
408
|
+
else
|
409
|
+
readable.each do |sock|
|
410
|
+
server = sock.server
|
411
|
+
|
412
|
+
begin
|
413
|
+
server.multi_response_nonblock.each_pair do |key, value_list|
|
414
|
+
yield key_without_namespace(key), value_list
|
415
|
+
end
|
416
|
+
|
417
|
+
if server.multi_response_completed?
|
418
|
+
servers.delete(server)
|
419
|
+
end
|
420
|
+
rescue NetworkError
|
421
|
+
servers.delete(server)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
391
431
|
end
|
392
432
|
end
|