dalli 2.7.8 → 3.0.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 +4 -4
- data/Gemfile +7 -2
- data/History.md +50 -0
- data/README.md +20 -102
- data/lib/dalli/cas/client.rb +1 -59
- data/lib/dalli/client.rb +179 -109
- 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 -429
- 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,12 +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
|
-
|
70
|
+
keys.flatten!
|
71
|
+
keys.compact!
|
72
|
+
|
73
|
+
return {} if keys.empty?
|
68
74
|
if block_given?
|
69
|
-
get_multi_yielder(keys) {|k, data| yield k, data.first}
|
75
|
+
get_multi_yielder(keys) { |k, data| yield k, data.first }
|
70
76
|
else
|
71
|
-
|
72
|
-
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 }
|
73
79
|
end
|
74
80
|
end
|
75
81
|
end
|
@@ -84,11 +90,11 @@ module Dalli
|
|
84
90
|
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
85
91
|
# and a block is given, the block will be invoked and its return value
|
86
92
|
# written to the cache and returned.
|
87
|
-
def fetch(key, ttl=nil, options=nil)
|
93
|
+
def fetch(key, ttl = nil, options = nil)
|
88
94
|
options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
|
89
95
|
val = get(key, options)
|
90
96
|
not_found = @options[:cache_nils] ?
|
91
|
-
val == Dalli::
|
97
|
+
val == Dalli::Protocol::NOT_FOUND :
|
92
98
|
val.nil?
|
93
99
|
if not_found && block_given?
|
94
100
|
val = yield
|
@@ -108,7 +114,7 @@ module Dalli
|
|
108
114
|
# - nil if the key did not exist.
|
109
115
|
# - false if the value was changed by someone else.
|
110
116
|
# - true if the value was successfully updated.
|
111
|
-
def cas(key, ttl=nil, options=nil, &block)
|
117
|
+
def cas(key, ttl = nil, options = nil, &block)
|
112
118
|
cas_core(key, false, ttl, options, &block)
|
113
119
|
end
|
114
120
|
|
@@ -119,25 +125,25 @@ module Dalli
|
|
119
125
|
# Returns:
|
120
126
|
# - false if the value was changed by someone else.
|
121
127
|
# - true if the value was successfully updated.
|
122
|
-
def cas!(key, ttl=nil, options=nil, &block)
|
128
|
+
def cas!(key, ttl = nil, options = nil, &block)
|
123
129
|
cas_core(key, true, ttl, options, &block)
|
124
130
|
end
|
125
131
|
|
126
|
-
def set(key, value, ttl=nil, options=nil)
|
132
|
+
def set(key, value, ttl = nil, options = nil)
|
127
133
|
perform(:set, key, value, ttl_or_default(ttl), 0, options)
|
128
134
|
end
|
129
135
|
|
130
136
|
##
|
131
137
|
# Conditionally add a key/value pair, if the key does not already exist
|
132
138
|
# on the server. Returns truthy if the operation succeeded.
|
133
|
-
def add(key, value, ttl=nil, options=nil)
|
139
|
+
def add(key, value, ttl = nil, options = nil)
|
134
140
|
perform(:add, key, value, ttl_or_default(ttl), options)
|
135
141
|
end
|
136
142
|
|
137
143
|
##
|
138
144
|
# Conditionally add a key/value pair, only if the key already exists
|
139
145
|
# on the server. Returns truthy if the operation succeeded.
|
140
|
-
def replace(key, value, ttl=nil, options=nil)
|
146
|
+
def replace(key, value, ttl = nil, options = nil)
|
141
147
|
perform(:replace, key, value, ttl_or_default(ttl), 0, options)
|
142
148
|
end
|
143
149
|
|
@@ -159,7 +165,7 @@ module Dalli
|
|
159
165
|
perform(:prepend, key, value.to_s)
|
160
166
|
end
|
161
167
|
|
162
|
-
def flush(delay=0)
|
168
|
+
def flush(delay = 0)
|
163
169
|
time = -delay
|
164
170
|
ring.servers.map { |s| s.request(:flush, time += delay) }
|
165
171
|
end
|
@@ -177,7 +183,7 @@ module Dalli
|
|
177
183
|
# Note that the ttl will only apply if the counter does not already
|
178
184
|
# exist. To increase an existing counter and update its TTL, use
|
179
185
|
# #cas.
|
180
|
-
def incr(key, amt=1, ttl=nil, default=nil)
|
186
|
+
def incr(key, amt = 1, ttl = nil, default = nil)
|
181
187
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
182
188
|
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
183
189
|
end
|
@@ -196,7 +202,7 @@ module Dalli
|
|
196
202
|
# Note that the ttl will only apply if the counter does not already
|
197
203
|
# exist. To decrease an existing counter and update its TTL, use
|
198
204
|
# #cas.
|
199
|
-
def decr(key, amt=1, ttl=nil, default=nil)
|
205
|
+
def decr(key, amt = 1, ttl = nil, default = nil)
|
200
206
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
201
207
|
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
202
208
|
end
|
@@ -205,20 +211,28 @@ module Dalli
|
|
205
211
|
# Touch updates expiration time for a given key.
|
206
212
|
#
|
207
213
|
# Returns true if key exists, otherwise nil.
|
208
|
-
def touch(key, ttl=nil)
|
214
|
+
def touch(key, ttl = nil)
|
209
215
|
resp = perform(:touch, key, ttl_or_default(ttl))
|
210
216
|
resp.nil? ? nil : true
|
211
217
|
end
|
212
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
|
+
|
213
227
|
##
|
214
228
|
# Collect the stats for each server.
|
215
229
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
216
230
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
217
|
-
def stats(type=nil)
|
218
|
-
type = nil
|
231
|
+
def stats(type = nil)
|
232
|
+
type = nil unless [nil, :items, :slabs, :settings].include? type
|
219
233
|
values = {}
|
220
234
|
ring.servers.each do |server|
|
221
|
-
values[
|
235
|
+
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
222
236
|
end
|
223
237
|
values
|
224
238
|
end
|
@@ -242,11 +256,63 @@ module Dalli
|
|
242
256
|
def version
|
243
257
|
values = {}
|
244
258
|
ring.servers.each do |server|
|
245
|
-
values[
|
259
|
+
values[server.name.to_s] = server.alive? ? server.request(:version) : nil
|
246
260
|
end
|
247
261
|
values
|
248
262
|
end
|
249
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
|
+
|
250
316
|
##
|
251
317
|
# Close our connection to each server.
|
252
318
|
# If you perform another operation after this, the connections will be re-established.
|
@@ -258,16 +324,11 @@ module Dalli
|
|
258
324
|
end
|
259
325
|
alias_method :reset, :close
|
260
326
|
|
261
|
-
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
262
|
-
def with
|
263
|
-
yield self
|
264
|
-
end
|
265
|
-
|
266
327
|
private
|
267
328
|
|
268
|
-
def cas_core(key, always_set, ttl=nil, options=nil)
|
329
|
+
def cas_core(key, always_set, ttl = nil, options = nil)
|
269
330
|
(value, cas) = perform(:cas, key)
|
270
|
-
value =
|
331
|
+
value = !value || value == "Not found" ? nil : value
|
271
332
|
return if value.nil? && !always_set
|
272
333
|
newvalue = yield(value)
|
273
334
|
perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
|
@@ -280,33 +341,29 @@ module Dalli
|
|
280
341
|
end
|
281
342
|
|
282
343
|
def groups_for_keys(*keys)
|
283
|
-
|
344
|
+
keys.flatten!
|
345
|
+
keys.map! { |a| validate_key(a.to_s) }
|
346
|
+
|
347
|
+
keys.group_by { |key|
|
284
348
|
begin
|
285
349
|
ring.server_for_key(key)
|
286
350
|
rescue Dalli::RingError
|
287
351
|
Dalli.logger.debug { "unable to get key #{key}" }
|
288
352
|
nil
|
289
353
|
end
|
290
|
-
|
291
|
-
return groups
|
292
|
-
end
|
293
|
-
|
294
|
-
def mapped_keys(keys)
|
295
|
-
keys.flatten.map {|a| validate_key(a.to_s)}
|
354
|
+
}
|
296
355
|
end
|
297
356
|
|
298
357
|
def make_multi_get_requests(groups)
|
299
358
|
groups.each do |server, keys_for_server|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
309
|
-
end
|
359
|
+
# TODO: do this with the perform chokepoint?
|
360
|
+
# But given the fact that fetching the response doesn't take place
|
361
|
+
# in that slot it's misleading anyway. Need to move all of this method
|
362
|
+
# into perform to be meaningful
|
363
|
+
server.request(:send_multiget, keys_for_server)
|
364
|
+
rescue DalliError, NetworkError => e
|
365
|
+
Dalli.logger.debug { e.inspect }
|
366
|
+
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
310
367
|
end
|
311
368
|
end
|
312
369
|
|
@@ -324,44 +381,55 @@ module Dalli
|
|
324
381
|
servers
|
325
382
|
end
|
326
383
|
|
384
|
+
##
|
385
|
+
# Ensures that the servers arg is either an array or a string.
|
386
|
+
def validate_servers_arg(servers)
|
387
|
+
return if servers.nil?
|
388
|
+
return if servers.is_a?(Array)
|
389
|
+
return if servers.is_a?(String)
|
390
|
+
|
391
|
+
raise ArgumentError, "An explicit servers argument must be a comma separated string or an array containing strings."
|
392
|
+
end
|
393
|
+
|
327
394
|
##
|
328
395
|
# Normalizes the argument into an array of servers.
|
329
|
-
# If the argument is a string, it's expected that the URIs are comma separated e.g.
|
396
|
+
# If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
|
330
397
|
# "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
|
331
398
|
def normalize_servers(servers)
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
399
|
+
Array(servers).flat_map do |server|
|
400
|
+
if server.is_a? String
|
401
|
+
server.split(",")
|
402
|
+
else
|
403
|
+
server
|
404
|
+
end
|
336
405
|
end
|
337
406
|
end
|
338
407
|
|
339
408
|
def ring
|
340
409
|
@ring ||= Dalli::Ring.new(
|
341
|
-
@servers.map
|
342
|
-
|
343
|
-
if s
|
410
|
+
@servers.map { |s|
|
411
|
+
server_options = {}
|
412
|
+
if s.start_with?("memcached://")
|
344
413
|
uri = URI.parse(s)
|
345
414
|
server_options[:username] = uri.user
|
346
415
|
server_options[:password] = uri.password
|
347
416
|
s = "#{uri.host}:#{uri.port}"
|
348
417
|
end
|
349
|
-
Dalli::
|
350
|
-
|
418
|
+
@options.fetch(:protocol_implementation, Dalli::Protocol::Binary).new(s, @options.merge(server_options))
|
419
|
+
}, @options
|
351
420
|
)
|
352
421
|
end
|
353
422
|
|
354
423
|
# Chokepoint method for instrumentation
|
355
424
|
def perform(*all_args)
|
356
425
|
return yield if block_given?
|
357
|
-
op, key, *args =
|
426
|
+
op, key, *args = all_args
|
358
427
|
|
359
428
|
key = key.to_s
|
360
429
|
key = validate_key(key)
|
361
430
|
begin
|
362
431
|
server = ring.server_for_key(key)
|
363
|
-
|
364
|
-
ret
|
432
|
+
server.request(op, key, *args)
|
365
433
|
rescue NetworkError => e
|
366
434
|
Dalli.logger.debug { e.inspect }
|
367
435
|
Dalli.logger.debug { "retrying request with new server" }
|
@@ -373,10 +441,11 @@ module Dalli
|
|
373
441
|
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
|
374
442
|
key = key_with_namespace(key)
|
375
443
|
if key.length > 250
|
376
|
-
|
377
|
-
|
444
|
+
digest_class = @options[:digest_class] || ::Digest::MD5
|
445
|
+
max_length_before_namespace = 212 - (namespace || "").size
|
446
|
+
key = "#{key[0, max_length_before_namespace]}:md5:#{digest_class.hexdigest(key)}"
|
378
447
|
end
|
379
|
-
|
448
|
+
key
|
380
449
|
end
|
381
450
|
|
382
451
|
def key_with_namespace(key)
|
@@ -384,7 +453,7 @@ module Dalli
|
|
384
453
|
end
|
385
454
|
|
386
455
|
def key_without_namespace(key)
|
387
|
-
(ns = namespace) ? key.sub(%r
|
456
|
+
(ns = namespace) ? key.sub(%r{\A#{Regexp.escape ns}:}, "") : key
|
388
457
|
end
|
389
458
|
|
390
459
|
def namespace
|
@@ -402,6 +471,9 @@ module Dalli
|
|
402
471
|
rescue NoMethodError
|
403
472
|
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
404
473
|
end
|
474
|
+
if opts[:digest_class] && !opts[:digest_class].respond_to?(:hexdigest)
|
475
|
+
raise ArgumentError, "The digest_class object must respond to the hexdigest method"
|
476
|
+
end
|
405
477
|
opts
|
406
478
|
end
|
407
479
|
|
@@ -411,54 +483,52 @@ module Dalli
|
|
411
483
|
perform do
|
412
484
|
return {} if keys.empty?
|
413
485
|
ring.lock do
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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
|
-
break
|
486
|
+
groups = groups_for_keys(keys)
|
487
|
+
if (unfound_keys = groups.delete(nil))
|
488
|
+
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
489
|
+
end
|
490
|
+
make_multi_get_requests(groups)
|
491
|
+
|
492
|
+
servers = groups.keys
|
493
|
+
return if servers.empty?
|
494
|
+
servers = perform_multi_response_start(servers)
|
495
|
+
|
496
|
+
start = Time.now
|
497
|
+
loop do
|
498
|
+
# remove any dead servers
|
499
|
+
servers.delete_if { |s| s.sock.nil? }
|
500
|
+
break if servers.empty?
|
501
|
+
|
502
|
+
# calculate remaining timeout
|
503
|
+
elapsed = Time.now - start
|
504
|
+
timeout = servers.first.options[:socket_timeout]
|
505
|
+
time_left = elapsed > timeout ? 0 : timeout - elapsed
|
506
|
+
|
507
|
+
sockets = servers.map(&:sock)
|
508
|
+
readable, _ = IO.select(sockets, nil, nil, time_left)
|
509
|
+
|
510
|
+
if readable.nil?
|
511
|
+
# no response within timeout; abort pending connections
|
512
|
+
servers.each do |server|
|
513
|
+
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
514
|
+
server.multi_response_abort
|
515
|
+
end
|
516
|
+
break
|
446
517
|
|
447
|
-
|
448
|
-
|
449
|
-
|
518
|
+
else
|
519
|
+
readable.each do |sock|
|
520
|
+
server = sock.server
|
450
521
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
522
|
+
begin
|
523
|
+
server.multi_response_nonblock.each_pair do |key, value_list|
|
524
|
+
yield key_without_namespace(key), value_list
|
525
|
+
end
|
455
526
|
|
456
|
-
|
457
|
-
servers.delete(server)
|
458
|
-
end
|
459
|
-
rescue NetworkError
|
527
|
+
if server.multi_response_completed?
|
460
528
|
servers.delete(server)
|
461
529
|
end
|
530
|
+
rescue NetworkError
|
531
|
+
servers.delete(server)
|
462
532
|
end
|
463
533
|
end
|
464
534
|
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)
|