dalli 2.7.9 → 3.0.1
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 +4 -4
- data/Gemfile +7 -2
- data/History.md +50 -0
- data/README.md +19 -102
- data/lib/dalli/cas/client.rb +1 -59
- data/lib/dalli/client.rb +178 -108
- data/lib/dalli/compressor.rb +4 -3
- data/lib/dalli/options.rb +3 -4
- data/lib/dalli/protocol/binary.rb +753 -0
- data/lib/dalli/protocol.rb +9 -0
- data/lib/dalli/ring.rb +12 -63
- data/lib/dalli/server.rb +2 -745
- data/lib/dalli/socket.rb +59 -134
- data/lib/dalli/version.rb +2 -1
- data/lib/dalli.rb +15 -17
- data/lib/rack/session/dalli.rb +21 -40
- metadata +11 -84
- data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
- data/lib/active_support/cache/dalli_store.rb +0 -432
- data/lib/dalli/railtie.rb +0 -8
data/lib/dalli/client.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
require "set"
|
4
5
|
|
5
6
|
# encoding: ascii
|
6
7
|
module Dalli
|
7
8
|
class Client
|
8
|
-
|
9
9
|
##
|
10
10
|
# Dalli::Client is the main class which developers will use to interact with
|
11
11
|
# the memcached server. Usage:
|
@@ -29,9 +29,12 @@ module Dalli
|
|
29
29
|
# - :serializer - defaults to Marshal
|
30
30
|
# - :compressor - defaults to zlib
|
31
31
|
# - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for #fetch operations.
|
32
|
+
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method, useful for injecting a FIPS compliant hash object.
|
33
|
+
# - :protocol_implementation - defaults to Dalli::Protocol::Binary which uses the binary protocol. Allows you to pass an alternative implementation using another protocol.
|
32
34
|
#
|
33
|
-
def initialize(servers=nil, options={})
|
34
|
-
|
35
|
+
def initialize(servers = nil, options = {})
|
36
|
+
validate_servers_arg(servers)
|
37
|
+
@servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || "127.0.0.1:11211")
|
35
38
|
@options = normalize_options(options)
|
36
39
|
@ring = nil
|
37
40
|
end
|
@@ -55,7 +58,7 @@ module Dalli
|
|
55
58
|
##
|
56
59
|
# Get the value associated with the key.
|
57
60
|
# If a value is not found, then +nil+ is returned.
|
58
|
-
def get(key, options=nil)
|
61
|
+
def get(key, options = nil)
|
59
62
|
perform(:get, key, options)
|
60
63
|
end
|
61
64
|
|
@@ -64,15 +67,15 @@ module Dalli
|
|
64
67
|
# If a block is given, yields key/value pairs one at a time.
|
65
68
|
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
66
69
|
def get_multi(*keys)
|
67
|
-
|
68
|
-
|
70
|
+
keys.flatten!
|
71
|
+
keys.compact!
|
69
72
|
|
70
|
-
return {} if
|
73
|
+
return {} if keys.empty?
|
71
74
|
if block_given?
|
72
|
-
get_multi_yielder(keys) {|k, data| yield k, data.first}
|
75
|
+
get_multi_yielder(keys) { |k, data| yield k, data.first }
|
73
76
|
else
|
74
|
-
|
75
|
-
get_multi_yielder(keys) {|k, data| hash[k] = data.first}
|
77
|
+
{}.tap do |hash|
|
78
|
+
get_multi_yielder(keys) { |k, data| hash[k] = data.first }
|
76
79
|
end
|
77
80
|
end
|
78
81
|
end
|
@@ -87,11 +90,11 @@ module Dalli
|
|
87
90
|
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
88
91
|
# and a block is given, the block will be invoked and its return value
|
89
92
|
# written to the cache and returned.
|
90
|
-
def fetch(key, ttl=nil, options=nil)
|
93
|
+
def fetch(key, ttl = nil, options = nil)
|
91
94
|
options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
|
92
95
|
val = get(key, options)
|
93
96
|
not_found = @options[:cache_nils] ?
|
94
|
-
val == Dalli::
|
97
|
+
val == Dalli::Protocol::NOT_FOUND :
|
95
98
|
val.nil?
|
96
99
|
if not_found && block_given?
|
97
100
|
val = yield
|
@@ -111,7 +114,7 @@ module Dalli
|
|
111
114
|
# - nil if the key did not exist.
|
112
115
|
# - false if the value was changed by someone else.
|
113
116
|
# - true if the value was successfully updated.
|
114
|
-
def cas(key, ttl=nil, options=nil, &block)
|
117
|
+
def cas(key, ttl = nil, options = nil, &block)
|
115
118
|
cas_core(key, false, ttl, options, &block)
|
116
119
|
end
|
117
120
|
|
@@ -122,25 +125,25 @@ module Dalli
|
|
122
125
|
# Returns:
|
123
126
|
# - false if the value was changed by someone else.
|
124
127
|
# - true if the value was successfully updated.
|
125
|
-
def cas!(key, ttl=nil, options=nil, &block)
|
128
|
+
def cas!(key, ttl = nil, options = nil, &block)
|
126
129
|
cas_core(key, true, ttl, options, &block)
|
127
130
|
end
|
128
131
|
|
129
|
-
def set(key, value, ttl=nil, options=nil)
|
132
|
+
def set(key, value, ttl = nil, options = nil)
|
130
133
|
perform(:set, key, value, ttl_or_default(ttl), 0, options)
|
131
134
|
end
|
132
135
|
|
133
136
|
##
|
134
137
|
# Conditionally add a key/value pair, if the key does not already exist
|
135
138
|
# on the server. Returns truthy if the operation succeeded.
|
136
|
-
def add(key, value, ttl=nil, options=nil)
|
139
|
+
def add(key, value, ttl = nil, options = nil)
|
137
140
|
perform(:add, key, value, ttl_or_default(ttl), options)
|
138
141
|
end
|
139
142
|
|
140
143
|
##
|
141
144
|
# Conditionally add a key/value pair, only if the key already exists
|
142
145
|
# on the server. Returns truthy if the operation succeeded.
|
143
|
-
def replace(key, value, ttl=nil, options=nil)
|
146
|
+
def replace(key, value, ttl = nil, options = nil)
|
144
147
|
perform(:replace, key, value, ttl_or_default(ttl), 0, options)
|
145
148
|
end
|
146
149
|
|
@@ -162,7 +165,7 @@ module Dalli
|
|
162
165
|
perform(:prepend, key, value.to_s)
|
163
166
|
end
|
164
167
|
|
165
|
-
def flush(delay=0)
|
168
|
+
def flush(delay = 0)
|
166
169
|
time = -delay
|
167
170
|
ring.servers.map { |s| s.request(:flush, time += delay) }
|
168
171
|
end
|
@@ -180,7 +183,7 @@ module Dalli
|
|
180
183
|
# Note that the ttl will only apply if the counter does not already
|
181
184
|
# exist. To increase an existing counter and update its TTL, use
|
182
185
|
# #cas.
|
183
|
-
def incr(key, amt=1, ttl=nil, default=nil)
|
186
|
+
def incr(key, amt = 1, ttl = nil, default = nil)
|
184
187
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
185
188
|
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
186
189
|
end
|
@@ -199,7 +202,7 @@ module Dalli
|
|
199
202
|
# Note that the ttl will only apply if the counter does not already
|
200
203
|
# exist. To decrease an existing counter and update its TTL, use
|
201
204
|
# #cas.
|
202
|
-
def decr(key, amt=1, ttl=nil, default=nil)
|
205
|
+
def decr(key, amt = 1, ttl = nil, default = nil)
|
203
206
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
204
207
|
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
205
208
|
end
|
@@ -208,20 +211,28 @@ module Dalli
|
|
208
211
|
# Touch updates expiration time for a given key.
|
209
212
|
#
|
210
213
|
# Returns true if key exists, otherwise nil.
|
211
|
-
def touch(key, ttl=nil)
|
214
|
+
def touch(key, ttl = nil)
|
212
215
|
resp = perform(:touch, key, ttl_or_default(ttl))
|
213
216
|
resp.nil? ? nil : true
|
214
217
|
end
|
215
218
|
|
219
|
+
##
|
220
|
+
# Gat (get and touch) fetch an item and simultaneously update its expiration time.
|
221
|
+
#
|
222
|
+
# If a value is not found, then +nil+ is returned.
|
223
|
+
def gat(key, ttl = nil)
|
224
|
+
perform(:gat, key, ttl_or_default(ttl))
|
225
|
+
end
|
226
|
+
|
216
227
|
##
|
217
228
|
# Collect the stats for each server.
|
218
229
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
219
230
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
220
|
-
def stats(type=nil)
|
221
|
-
type = nil
|
231
|
+
def stats(type = nil)
|
232
|
+
type = nil unless [nil, :items, :slabs, :settings].include? type
|
222
233
|
values = {}
|
223
234
|
ring.servers.each do |server|
|
224
|
-
values[
|
235
|
+
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
225
236
|
end
|
226
237
|
values
|
227
238
|
end
|
@@ -245,11 +256,63 @@ module Dalli
|
|
245
256
|
def version
|
246
257
|
values = {}
|
247
258
|
ring.servers.each do |server|
|
248
|
-
values[
|
259
|
+
values[server.name.to_s] = server.alive? ? server.request(:version) : nil
|
249
260
|
end
|
250
261
|
values
|
251
262
|
end
|
252
263
|
|
264
|
+
##
|
265
|
+
# Get the value and CAS ID associated with the key. If a block is provided,
|
266
|
+
# value and CAS will be passed to the block.
|
267
|
+
def get_cas(key)
|
268
|
+
(value, cas) = perform(:cas, key)
|
269
|
+
value = !value || value == "Not found" ? nil : value
|
270
|
+
if block_given?
|
271
|
+
yield value, cas
|
272
|
+
else
|
273
|
+
[value, cas]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
##
|
278
|
+
# Fetch multiple keys efficiently, including available metadata such as CAS.
|
279
|
+
# If a block is given, yields key/data pairs one a time. Data is an array:
|
280
|
+
# [value, cas_id]
|
281
|
+
# If no block is given, returns a hash of
|
282
|
+
# { 'key' => [value, cas_id] }
|
283
|
+
def get_multi_cas(*keys)
|
284
|
+
if block_given?
|
285
|
+
get_multi_yielder(keys) { |*args| yield(*args) }
|
286
|
+
else
|
287
|
+
{}.tap do |hash|
|
288
|
+
get_multi_yielder(keys) { |k, data| hash[k] = data }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
##
|
294
|
+
# Set the key-value pair, verifying existing CAS.
|
295
|
+
# Returns the resulting CAS value if succeeded, and falsy otherwise.
|
296
|
+
def set_cas(key, value, cas, ttl = nil, options = nil)
|
297
|
+
ttl ||= @options[:expires_in].to_i
|
298
|
+
perform(:set, key, value, ttl, cas, options)
|
299
|
+
end
|
300
|
+
|
301
|
+
##
|
302
|
+
# Conditionally add a key/value pair, verifying existing CAS, only if the
|
303
|
+
# key already exists on the server. Returns the new CAS value if the
|
304
|
+
# operation succeeded, or falsy otherwise.
|
305
|
+
def replace_cas(key, value, cas, ttl = nil, options = nil)
|
306
|
+
ttl ||= @options[:expires_in].to_i
|
307
|
+
perform(:replace, key, value, ttl, cas, options)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Delete a key/value pair, verifying existing CAS.
|
311
|
+
# Returns true if succeeded, and falsy otherwise.
|
312
|
+
def delete_cas(key, cas = 0)
|
313
|
+
perform(:delete, key, cas)
|
314
|
+
end
|
315
|
+
|
253
316
|
##
|
254
317
|
# Close our connection to each server.
|
255
318
|
# If you perform another operation after this, the connections will be re-established.
|
@@ -268,9 +331,9 @@ module Dalli
|
|
268
331
|
|
269
332
|
private
|
270
333
|
|
271
|
-
def cas_core(key, always_set, ttl=nil, options=nil)
|
334
|
+
def cas_core(key, always_set, ttl = nil, options = nil)
|
272
335
|
(value, cas) = perform(:cas, key)
|
273
|
-
value =
|
336
|
+
value = !value || value == "Not found" ? nil : value
|
274
337
|
return if value.nil? && !always_set
|
275
338
|
newvalue = yield(value)
|
276
339
|
perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
|
@@ -283,35 +346,29 @@ module Dalli
|
|
283
346
|
end
|
284
347
|
|
285
348
|
def groups_for_keys(*keys)
|
286
|
-
|
349
|
+
keys.flatten!
|
350
|
+
keys.map! { |a| validate_key(a.to_s) }
|
351
|
+
|
352
|
+
keys.group_by { |key|
|
287
353
|
begin
|
288
354
|
ring.server_for_key(key)
|
289
355
|
rescue Dalli::RingError
|
290
356
|
Dalli.logger.debug { "unable to get key #{key}" }
|
291
357
|
nil
|
292
358
|
end
|
293
|
-
|
294
|
-
return groups
|
295
|
-
end
|
296
|
-
|
297
|
-
def mapped_keys(keys)
|
298
|
-
keys_array = keys.flatten
|
299
|
-
keys_array.map! { |a| validate_key(a.to_s) }
|
300
|
-
keys_array
|
359
|
+
}
|
301
360
|
end
|
302
361
|
|
303
362
|
def make_multi_get_requests(groups)
|
304
363
|
groups.each do |server, keys_for_server|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
314
|
-
end
|
364
|
+
# TODO: do this with the perform chokepoint?
|
365
|
+
# But given the fact that fetching the response doesn't take place
|
366
|
+
# in that slot it's misleading anyway. Need to move all of this method
|
367
|
+
# into perform to be meaningful
|
368
|
+
server.request(:send_multiget, keys_for_server)
|
369
|
+
rescue DalliError, NetworkError => e
|
370
|
+
Dalli.logger.debug { e.inspect }
|
371
|
+
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
315
372
|
end
|
316
373
|
end
|
317
374
|
|
@@ -329,44 +386,55 @@ module Dalli
|
|
329
386
|
servers
|
330
387
|
end
|
331
388
|
|
389
|
+
##
|
390
|
+
# Ensures that the servers arg is either an array or a string.
|
391
|
+
def validate_servers_arg(servers)
|
392
|
+
return if servers.nil?
|
393
|
+
return if servers.is_a?(Array)
|
394
|
+
return if servers.is_a?(String)
|
395
|
+
|
396
|
+
raise ArgumentError, "An explicit servers argument must be a comma separated string or an array containing strings."
|
397
|
+
end
|
398
|
+
|
332
399
|
##
|
333
400
|
# Normalizes the argument into an array of servers.
|
334
|
-
# If the argument is a string, it's expected that the URIs are comma separated e.g.
|
401
|
+
# If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
|
335
402
|
# "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
|
336
403
|
def normalize_servers(servers)
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
404
|
+
Array(servers).flat_map do |server|
|
405
|
+
if server.is_a? String
|
406
|
+
server.split(",")
|
407
|
+
else
|
408
|
+
server
|
409
|
+
end
|
341
410
|
end
|
342
411
|
end
|
343
412
|
|
344
413
|
def ring
|
345
414
|
@ring ||= Dalli::Ring.new(
|
346
|
-
@servers.map
|
347
|
-
|
348
|
-
if s
|
415
|
+
@servers.map { |s|
|
416
|
+
server_options = {}
|
417
|
+
if s.start_with?("memcached://")
|
349
418
|
uri = URI.parse(s)
|
350
419
|
server_options[:username] = uri.user
|
351
420
|
server_options[:password] = uri.password
|
352
421
|
s = "#{uri.host}:#{uri.port}"
|
353
422
|
end
|
354
|
-
Dalli::
|
355
|
-
|
423
|
+
@options.fetch(:protocol_implementation, Dalli::Protocol::Binary).new(s, @options.merge(server_options))
|
424
|
+
}, @options
|
356
425
|
)
|
357
426
|
end
|
358
427
|
|
359
428
|
# Chokepoint method for instrumentation
|
360
429
|
def perform(*all_args)
|
361
430
|
return yield if block_given?
|
362
|
-
op, key, *args =
|
431
|
+
op, key, *args = all_args
|
363
432
|
|
364
433
|
key = key.to_s
|
365
434
|
key = validate_key(key)
|
366
435
|
begin
|
367
436
|
server = ring.server_for_key(key)
|
368
|
-
|
369
|
-
ret
|
437
|
+
server.request(op, key, *args)
|
370
438
|
rescue NetworkError => e
|
371
439
|
Dalli.logger.debug { e.inspect }
|
372
440
|
Dalli.logger.debug { "retrying request with new server" }
|
@@ -378,10 +446,11 @@ module Dalli
|
|
378
446
|
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
|
379
447
|
key = key_with_namespace(key)
|
380
448
|
if key.length > 250
|
381
|
-
|
382
|
-
|
449
|
+
digest_class = @options[:digest_class] || ::Digest::MD5
|
450
|
+
max_length_before_namespace = 212 - (namespace || "").size
|
451
|
+
key = "#{key[0, max_length_before_namespace]}:md5:#{digest_class.hexdigest(key)}"
|
383
452
|
end
|
384
|
-
|
453
|
+
key
|
385
454
|
end
|
386
455
|
|
387
456
|
def key_with_namespace(key)
|
@@ -389,7 +458,7 @@ module Dalli
|
|
389
458
|
end
|
390
459
|
|
391
460
|
def key_without_namespace(key)
|
392
|
-
(ns = namespace) ? key.sub(%r
|
461
|
+
(ns = namespace) ? key.sub(%r{\A#{Regexp.escape ns}:}, "") : key
|
393
462
|
end
|
394
463
|
|
395
464
|
def namespace
|
@@ -407,6 +476,9 @@ module Dalli
|
|
407
476
|
rescue NoMethodError
|
408
477
|
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
409
478
|
end
|
479
|
+
if opts[:digest_class] && !opts[:digest_class].respond_to?(:hexdigest)
|
480
|
+
raise ArgumentError, "The digest_class object must respond to the hexdigest method"
|
481
|
+
end
|
410
482
|
opts
|
411
483
|
end
|
412
484
|
|
@@ -416,54 +488,52 @@ module Dalli
|
|
416
488
|
perform do
|
417
489
|
return {} if keys.empty?
|
418
490
|
ring.lock do
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
break
|
491
|
+
groups = groups_for_keys(keys)
|
492
|
+
if (unfound_keys = groups.delete(nil))
|
493
|
+
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
494
|
+
end
|
495
|
+
make_multi_get_requests(groups)
|
496
|
+
|
497
|
+
servers = groups.keys
|
498
|
+
return if servers.empty?
|
499
|
+
servers = perform_multi_response_start(servers)
|
500
|
+
|
501
|
+
start = Time.now
|
502
|
+
loop do
|
503
|
+
# remove any dead servers
|
504
|
+
servers.delete_if { |s| s.sock.nil? }
|
505
|
+
break if servers.empty?
|
506
|
+
|
507
|
+
# calculate remaining timeout
|
508
|
+
elapsed = Time.now - start
|
509
|
+
timeout = servers.first.options[:socket_timeout]
|
510
|
+
time_left = elapsed > timeout ? 0 : timeout - elapsed
|
511
|
+
|
512
|
+
sockets = servers.map(&:sock)
|
513
|
+
readable, _ = IO.select(sockets, nil, nil, time_left)
|
514
|
+
|
515
|
+
if readable.nil?
|
516
|
+
# no response within timeout; abort pending connections
|
517
|
+
servers.each do |server|
|
518
|
+
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
519
|
+
server.multi_response_abort
|
520
|
+
end
|
521
|
+
break
|
451
522
|
|
452
|
-
|
453
|
-
|
454
|
-
|
523
|
+
else
|
524
|
+
readable.each do |sock|
|
525
|
+
server = sock.server
|
455
526
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
527
|
+
begin
|
528
|
+
server.multi_response_nonblock.each_pair do |key, value_list|
|
529
|
+
yield key_without_namespace(key), value_list
|
530
|
+
end
|
460
531
|
|
461
|
-
|
462
|
-
servers.delete(server)
|
463
|
-
end
|
464
|
-
rescue NetworkError
|
532
|
+
if server.multi_response_completed?
|
465
533
|
servers.delete(server)
|
466
534
|
end
|
535
|
+
rescue NetworkError
|
536
|
+
servers.delete(server)
|
467
537
|
end
|
468
538
|
end
|
469
539
|
end
|
data/lib/dalli/compressor.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
require "stringio"
|
4
5
|
|
5
6
|
module Dalli
|
6
7
|
class Compressor
|
@@ -15,7 +16,7 @@ module Dalli
|
|
15
16
|
|
16
17
|
class GzipCompressor
|
17
18
|
def self.compress(data)
|
18
|
-
io = StringIO.new(
|
19
|
+
io = StringIO.new(+"", "w")
|
19
20
|
gz = Zlib::GzipWriter.new(io)
|
20
21
|
gz.write(data)
|
21
22
|
gz.close
|
data/lib/dalli/options.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'thread'
|
3
|
-
require 'monitor'
|
4
2
|
|
5
|
-
|
3
|
+
require "monitor"
|
6
4
|
|
5
|
+
module Dalli
|
7
6
|
# Make Dalli threadsafe by using a lock around all
|
8
7
|
# public server methods.
|
9
8
|
#
|
10
|
-
# Dalli::
|
9
|
+
# Dalli::Protocol::Binary.extend(Dalli::Threadsafe)
|
11
10
|
#
|
12
11
|
module Threadsafe
|
13
12
|
def self.extended(obj)
|