dalli 2.7.5 → 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 +5 -5
- data/Gemfile +7 -2
- data/History.md +79 -0
- data/README.md +28 -100
- data/lib/dalli/cas/client.rb +1 -58
- data/lib/dalli/client.rb +199 -109
- data/lib/dalli/compressor.rb +5 -3
- data/lib/dalli/options.rb +4 -4
- data/lib/dalli/protocol/binary.rb +753 -0
- data/lib/dalli/protocol.rb +9 -0
- data/lib/dalli/ring.rb +16 -67
- data/lib/dalli/server.rb +3 -733
- data/lib/dalli/socket.rb +59 -126
- data/lib/dalli/version.rb +3 -1
- data/lib/dalli.rb +17 -16
- data/lib/rack/session/dalli.rb +123 -31
- metadata +11 -84
- data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -81
- data/lib/active_support/cache/dalli_store.rb +0 -402
- data/lib/dalli/railtie.rb +0 -7
data/lib/dalli/client.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
require "set"
|
3
5
|
|
4
6
|
# encoding: ascii
|
5
7
|
module Dalli
|
6
8
|
class Client
|
7
|
-
|
8
9
|
##
|
9
10
|
# Dalli::Client is the main class which developers will use to interact with
|
10
11
|
# the memcached server. Usage:
|
@@ -28,9 +29,12 @@ module Dalli
|
|
28
29
|
# - :serializer - defaults to Marshal
|
29
30
|
# - :compressor - defaults to zlib
|
30
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.
|
31
34
|
#
|
32
|
-
def initialize(servers=nil, options={})
|
33
|
-
|
35
|
+
def initialize(servers = nil, options = {})
|
36
|
+
validate_servers_arg(servers)
|
37
|
+
@servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || "127.0.0.1:11211")
|
34
38
|
@options = normalize_options(options)
|
35
39
|
@ring = nil
|
36
40
|
end
|
@@ -54,7 +58,7 @@ module Dalli
|
|
54
58
|
##
|
55
59
|
# Get the value associated with the key.
|
56
60
|
# If a value is not found, then +nil+ is returned.
|
57
|
-
def get(key, options=nil)
|
61
|
+
def get(key, options = nil)
|
58
62
|
perform(:get, key, options)
|
59
63
|
end
|
60
64
|
|
@@ -63,12 +67,15 @@ module Dalli
|
|
63
67
|
# If a block is given, yields key/value pairs one at a time.
|
64
68
|
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
65
69
|
def get_multi(*keys)
|
66
|
-
|
70
|
+
keys.flatten!
|
71
|
+
keys.compact!
|
72
|
+
|
73
|
+
return {} if keys.empty?
|
67
74
|
if block_given?
|
68
|
-
get_multi_yielder(keys) {|k, data| yield k, data.first}
|
75
|
+
get_multi_yielder(keys) { |k, data| yield k, data.first }
|
69
76
|
else
|
70
|
-
|
71
|
-
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 }
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
@@ -83,11 +90,11 @@ module Dalli
|
|
83
90
|
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
84
91
|
# and a block is given, the block will be invoked and its return value
|
85
92
|
# written to the cache and returned.
|
86
|
-
def fetch(key, ttl=nil, options=nil)
|
93
|
+
def fetch(key, ttl = nil, options = nil)
|
87
94
|
options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
|
88
95
|
val = get(key, options)
|
89
96
|
not_found = @options[:cache_nils] ?
|
90
|
-
val == Dalli::
|
97
|
+
val == Dalli::Protocol::NOT_FOUND :
|
91
98
|
val.nil?
|
92
99
|
if not_found && block_given?
|
93
100
|
val = yield
|
@@ -107,30 +114,36 @@ module Dalli
|
|
107
114
|
# - nil if the key did not exist.
|
108
115
|
# - false if the value was changed by someone else.
|
109
116
|
# - true if the value was successfully updated.
|
110
|
-
def cas(key, ttl=nil, options=nil)
|
111
|
-
(
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
+
def cas(key, ttl = nil, options = nil, &block)
|
118
|
+
cas_core(key, false, ttl, options, &block)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# like #cas, but will yield to the block whether or not the value
|
123
|
+
# already exists.
|
124
|
+
#
|
125
|
+
# Returns:
|
126
|
+
# - false if the value was changed by someone else.
|
127
|
+
# - true if the value was successfully updated.
|
128
|
+
def cas!(key, ttl = nil, options = nil, &block)
|
129
|
+
cas_core(key, true, ttl, options, &block)
|
117
130
|
end
|
118
131
|
|
119
|
-
def set(key, value, ttl=nil, options=nil)
|
132
|
+
def set(key, value, ttl = nil, options = nil)
|
120
133
|
perform(:set, key, value, ttl_or_default(ttl), 0, options)
|
121
134
|
end
|
122
135
|
|
123
136
|
##
|
124
137
|
# Conditionally add a key/value pair, if the key does not already exist
|
125
138
|
# on the server. Returns truthy if the operation succeeded.
|
126
|
-
def add(key, value, ttl=nil, options=nil)
|
139
|
+
def add(key, value, ttl = nil, options = nil)
|
127
140
|
perform(:add, key, value, ttl_or_default(ttl), options)
|
128
141
|
end
|
129
142
|
|
130
143
|
##
|
131
144
|
# Conditionally add a key/value pair, only if the key already exists
|
132
145
|
# on the server. Returns truthy if the operation succeeded.
|
133
|
-
def replace(key, value, ttl=nil, options=nil)
|
146
|
+
def replace(key, value, ttl = nil, options = nil)
|
134
147
|
perform(:replace, key, value, ttl_or_default(ttl), 0, options)
|
135
148
|
end
|
136
149
|
|
@@ -152,7 +165,7 @@ module Dalli
|
|
152
165
|
perform(:prepend, key, value.to_s)
|
153
166
|
end
|
154
167
|
|
155
|
-
def flush(delay=0)
|
168
|
+
def flush(delay = 0)
|
156
169
|
time = -delay
|
157
170
|
ring.servers.map { |s| s.request(:flush, time += delay) }
|
158
171
|
end
|
@@ -170,7 +183,7 @@ module Dalli
|
|
170
183
|
# Note that the ttl will only apply if the counter does not already
|
171
184
|
# exist. To increase an existing counter and update its TTL, use
|
172
185
|
# #cas.
|
173
|
-
def incr(key, amt=1, ttl=nil, default=nil)
|
186
|
+
def incr(key, amt = 1, ttl = nil, default = nil)
|
174
187
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
175
188
|
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
176
189
|
end
|
@@ -189,7 +202,7 @@ module Dalli
|
|
189
202
|
# Note that the ttl will only apply if the counter does not already
|
190
203
|
# exist. To decrease an existing counter and update its TTL, use
|
191
204
|
# #cas.
|
192
|
-
def decr(key, amt=1, ttl=nil, default=nil)
|
205
|
+
def decr(key, amt = 1, ttl = nil, default = nil)
|
193
206
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
194
207
|
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
195
208
|
end
|
@@ -198,20 +211,28 @@ module Dalli
|
|
198
211
|
# Touch updates expiration time for a given key.
|
199
212
|
#
|
200
213
|
# Returns true if key exists, otherwise nil.
|
201
|
-
def touch(key, ttl=nil)
|
214
|
+
def touch(key, ttl = nil)
|
202
215
|
resp = perform(:touch, key, ttl_or_default(ttl))
|
203
216
|
resp.nil? ? nil : true
|
204
217
|
end
|
205
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
|
+
|
206
227
|
##
|
207
228
|
# Collect the stats for each server.
|
208
229
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
209
230
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
210
|
-
def stats(type=nil)
|
211
|
-
type = nil
|
231
|
+
def stats(type = nil)
|
232
|
+
type = nil unless [nil, :items, :slabs, :settings].include? type
|
212
233
|
values = {}
|
213
234
|
ring.servers.each do |server|
|
214
|
-
values[
|
235
|
+
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
215
236
|
end
|
216
237
|
values
|
217
238
|
end
|
@@ -235,11 +256,63 @@ module Dalli
|
|
235
256
|
def version
|
236
257
|
values = {}
|
237
258
|
ring.servers.each do |server|
|
238
|
-
values[
|
259
|
+
values[server.name.to_s] = server.alive? ? server.request(:version) : nil
|
239
260
|
end
|
240
261
|
values
|
241
262
|
end
|
242
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
|
+
|
243
316
|
##
|
244
317
|
# Close our connection to each server.
|
245
318
|
# If you perform another operation after this, the connections will be re-established.
|
@@ -258,6 +331,14 @@ module Dalli
|
|
258
331
|
|
259
332
|
private
|
260
333
|
|
334
|
+
def cas_core(key, always_set, ttl = nil, options = nil)
|
335
|
+
(value, cas) = perform(:cas, key)
|
336
|
+
value = !value || value == "Not found" ? nil : value
|
337
|
+
return if value.nil? && !always_set
|
338
|
+
newvalue = yield(value)
|
339
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
|
340
|
+
end
|
341
|
+
|
261
342
|
def ttl_or_default(ttl)
|
262
343
|
(ttl || @options[:expires_in]).to_i
|
263
344
|
rescue NoMethodError
|
@@ -265,33 +346,29 @@ module Dalli
|
|
265
346
|
end
|
266
347
|
|
267
348
|
def groups_for_keys(*keys)
|
268
|
-
|
349
|
+
keys.flatten!
|
350
|
+
keys.map! { |a| validate_key(a.to_s) }
|
351
|
+
|
352
|
+
keys.group_by { |key|
|
269
353
|
begin
|
270
354
|
ring.server_for_key(key)
|
271
355
|
rescue Dalli::RingError
|
272
356
|
Dalli.logger.debug { "unable to get key #{key}" }
|
273
357
|
nil
|
274
358
|
end
|
275
|
-
|
276
|
-
return groups
|
277
|
-
end
|
278
|
-
|
279
|
-
def mapped_keys(keys)
|
280
|
-
keys.flatten.map {|a| validate_key(a.to_s)}
|
359
|
+
}
|
281
360
|
end
|
282
361
|
|
283
362
|
def make_multi_get_requests(groups)
|
284
363
|
groups.each do |server, keys_for_server|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
294
|
-
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}" }
|
295
372
|
end
|
296
373
|
end
|
297
374
|
|
@@ -309,44 +386,55 @@ module Dalli
|
|
309
386
|
servers
|
310
387
|
end
|
311
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
|
+
|
312
399
|
##
|
313
400
|
# Normalizes the argument into an array of servers.
|
314
|
-
# 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.
|
315
402
|
# "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
|
316
403
|
def normalize_servers(servers)
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
404
|
+
Array(servers).flat_map do |server|
|
405
|
+
if server.is_a? String
|
406
|
+
server.split(",")
|
407
|
+
else
|
408
|
+
server
|
409
|
+
end
|
321
410
|
end
|
322
411
|
end
|
323
412
|
|
324
413
|
def ring
|
325
414
|
@ring ||= Dalli::Ring.new(
|
326
|
-
@servers.map
|
327
|
-
|
328
|
-
if s
|
415
|
+
@servers.map { |s|
|
416
|
+
server_options = {}
|
417
|
+
if s.start_with?("memcached://")
|
329
418
|
uri = URI.parse(s)
|
330
419
|
server_options[:username] = uri.user
|
331
420
|
server_options[:password] = uri.password
|
332
421
|
s = "#{uri.host}:#{uri.port}"
|
333
422
|
end
|
334
|
-
Dalli::
|
335
|
-
|
423
|
+
@options.fetch(:protocol_implementation, Dalli::Protocol::Binary).new(s, @options.merge(server_options))
|
424
|
+
}, @options
|
336
425
|
)
|
337
426
|
end
|
338
427
|
|
339
428
|
# Chokepoint method for instrumentation
|
340
|
-
def perform(*all_args
|
341
|
-
return
|
342
|
-
op, key, *args =
|
429
|
+
def perform(*all_args)
|
430
|
+
return yield if block_given?
|
431
|
+
op, key, *args = all_args
|
343
432
|
|
344
433
|
key = key.to_s
|
345
434
|
key = validate_key(key)
|
346
435
|
begin
|
347
436
|
server = ring.server_for_key(key)
|
348
|
-
|
349
|
-
ret
|
437
|
+
server.request(op, key, *args)
|
350
438
|
rescue NetworkError => e
|
351
439
|
Dalli.logger.debug { e.inspect }
|
352
440
|
Dalli.logger.debug { "retrying request with new server" }
|
@@ -358,10 +446,11 @@ module Dalli
|
|
358
446
|
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
|
359
447
|
key = key_with_namespace(key)
|
360
448
|
if key.length > 250
|
361
|
-
|
362
|
-
|
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)}"
|
363
452
|
end
|
364
|
-
|
453
|
+
key
|
365
454
|
end
|
366
455
|
|
367
456
|
def key_with_namespace(key)
|
@@ -369,7 +458,7 @@ module Dalli
|
|
369
458
|
end
|
370
459
|
|
371
460
|
def key_without_namespace(key)
|
372
|
-
(ns = namespace) ? key.sub(%r
|
461
|
+
(ns = namespace) ? key.sub(%r{\A#{Regexp.escape ns}:}, "") : key
|
373
462
|
end
|
374
463
|
|
375
464
|
def namespace
|
@@ -387,6 +476,9 @@ module Dalli
|
|
387
476
|
rescue NoMethodError
|
388
477
|
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
389
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
|
390
482
|
opts
|
391
483
|
end
|
392
484
|
|
@@ -396,54 +488,52 @@ module Dalli
|
|
396
488
|
perform do
|
397
489
|
return {} if keys.empty?
|
398
490
|
ring.lock do
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
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
|
431
522
|
|
432
|
-
|
433
|
-
|
434
|
-
|
523
|
+
else
|
524
|
+
readable.each do |sock|
|
525
|
+
server = sock.server
|
435
526
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
527
|
+
begin
|
528
|
+
server.multi_response_nonblock.each_pair do |key, value_list|
|
529
|
+
yield key_without_namespace(key), value_list
|
530
|
+
end
|
440
531
|
|
441
|
-
|
442
|
-
servers.delete(server)
|
443
|
-
end
|
444
|
-
rescue NetworkError
|
532
|
+
if server.multi_response_completed?
|
445
533
|
servers.delete(server)
|
446
534
|
end
|
535
|
+
rescue NetworkError
|
536
|
+
servers.delete(server)
|
447
537
|
end
|
448
538
|
end
|
449
539
|
end
|
data/lib/dalli/compressor.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
require "stringio"
|
3
5
|
|
4
6
|
module Dalli
|
5
7
|
class Compressor
|
@@ -14,7 +16,7 @@ module Dalli
|
|
14
16
|
|
15
17
|
class GzipCompressor
|
16
18
|
def self.compress(data)
|
17
|
-
io = StringIO.new("w")
|
19
|
+
io = StringIO.new(+"", "w")
|
18
20
|
gz = Zlib::GzipWriter.new(io)
|
19
21
|
gz.write(data)
|
20
22
|
gz.close
|
data/lib/dalli/options.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
|
2
|
-
require 'monitor'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require "monitor"
|
5
4
|
|
5
|
+
module Dalli
|
6
6
|
# Make Dalli threadsafe by using a lock around all
|
7
7
|
# public server methods.
|
8
8
|
#
|
9
|
-
# Dalli::
|
9
|
+
# Dalli::Protocol::Binary.extend(Dalli::Threadsafe)
|
10
10
|
#
|
11
11
|
module Threadsafe
|
12
12
|
def self.extended(obj)
|