dalli 3.0.3 → 3.0.4
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/History.md +6 -0
- data/lib/dalli/client.rb +18 -9
- data/lib/dalli/protocol/binary.rb +7 -41
- data/lib/dalli/protocol/server_config_parser.rb +67 -0
- data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +2 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de12f6f4e7e074752c4b3d2ccaa24450fd081e6e4ded890c632158858e5aa939
|
4
|
+
data.tar.gz: '01093957dc00b0742995b5d9198014dc480bd08788f4f3c2e5160cd2a6ca8725'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c073d5ce9922b8617c11c507b8b90603ccdf304419c2fe395af33467f880c75e4a22e9e5e6eae387c78ae937b6b8de5d68eba87b323967cce03a8aaffeeba1d8
|
7
|
+
data.tar.gz: f7fd88ee88a887c44a1b94d0e3c6c7b0d210d08c38db39ea387585a18a82c5cd0a4e9a57daaa444e927e4c47f81fbd404daeec139ded6d8ff6d39fe3e565022d
|
data/History.md
CHANGED
data/lib/dalli/client.rb
CHANGED
@@ -374,17 +374,24 @@ module Dalli
|
|
374
374
|
end
|
375
375
|
|
376
376
|
def perform_multi_response_start(servers)
|
377
|
+
deleted = []
|
378
|
+
|
377
379
|
servers.each do |server|
|
378
380
|
next unless server.alive?
|
381
|
+
|
379
382
|
begin
|
380
383
|
server.multi_response_start
|
381
|
-
rescue
|
384
|
+
rescue Dalli::NetworkError
|
385
|
+
servers.each { |s| s.multi_response_abort unless s.sock.nil? }
|
386
|
+
raise
|
387
|
+
rescue Dalli::DalliError => e
|
382
388
|
Dalli.logger.debug { e.inspect }
|
383
389
|
Dalli.logger.debug { "results from this server will be missing" }
|
384
|
-
|
390
|
+
deleted.append(server)
|
385
391
|
end
|
386
392
|
end
|
387
|
-
|
393
|
+
|
394
|
+
servers.delete_if { |server| deleted.include?(server) }
|
388
395
|
end
|
389
396
|
|
390
397
|
##
|
@@ -428,12 +435,13 @@ module Dalli
|
|
428
435
|
|
429
436
|
# Chokepoint method for instrumentation
|
430
437
|
def perform(*all_args)
|
431
|
-
return yield if block_given?
|
432
|
-
op, key, *args = all_args
|
433
|
-
|
434
|
-
key = key.to_s
|
435
|
-
key = validate_key(key)
|
436
438
|
begin
|
439
|
+
return yield if block_given?
|
440
|
+
op, key, *args = all_args
|
441
|
+
|
442
|
+
key = key.to_s
|
443
|
+
key = validate_key(key)
|
444
|
+
|
437
445
|
server = ring.server_for_key(key)
|
438
446
|
server.request(op, key, *args)
|
439
447
|
rescue NetworkError => e
|
@@ -534,7 +542,8 @@ module Dalli
|
|
534
542
|
servers.delete(server)
|
535
543
|
end
|
536
544
|
rescue NetworkError
|
537
|
-
servers.
|
545
|
+
servers.each { |s| s.multi_response_abort unless s.sock.nil? }
|
546
|
+
raise
|
538
547
|
end
|
539
548
|
end
|
540
549
|
end
|
@@ -13,8 +13,6 @@ module Dalli
|
|
13
13
|
attr_reader :sock
|
14
14
|
attr_reader :socket_type # possible values: :unix, :tcp
|
15
15
|
|
16
|
-
DEFAULT_PORT = 11211
|
17
|
-
DEFAULT_WEIGHT = 1
|
18
16
|
DEFAULTS = {
|
19
17
|
# seconds between trying to contact a remote server
|
20
18
|
down_retry_delay: 30,
|
@@ -37,7 +35,7 @@ module Dalli
|
|
37
35
|
}
|
38
36
|
|
39
37
|
def initialize(attribs, options = {})
|
40
|
-
@hostname, @port, @weight, @socket_type =
|
38
|
+
@hostname, @port, @weight, @socket_type = ServerConfigParser.parse(attribs)
|
41
39
|
@fail_count = 0
|
42
40
|
@down_at = nil
|
43
41
|
@last_down_at = nil
|
@@ -278,7 +276,7 @@ module Dalli
|
|
278
276
|
|
279
277
|
def set(key, value, ttl, cas, options)
|
280
278
|
(value, flags) = serialize(key, value, options)
|
281
|
-
ttl =
|
279
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
282
280
|
|
283
281
|
guard_max_value(key, value)
|
284
282
|
|
@@ -289,7 +287,7 @@ module Dalli
|
|
289
287
|
|
290
288
|
def add(key, value, ttl, options)
|
291
289
|
(value, flags) = serialize(key, value, options)
|
292
|
-
ttl =
|
290
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
293
291
|
|
294
292
|
guard_max_value(key, value)
|
295
293
|
|
@@ -300,7 +298,7 @@ module Dalli
|
|
300
298
|
|
301
299
|
def replace(key, value, ttl, cas, options)
|
302
300
|
(value, flags) = serialize(key, value, options)
|
303
|
-
ttl =
|
301
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
304
302
|
|
305
303
|
guard_max_value(key, value)
|
306
304
|
|
@@ -322,7 +320,7 @@ module Dalli
|
|
322
320
|
end
|
323
321
|
|
324
322
|
def decr_incr(opcode, key, count, ttl, default)
|
325
|
-
expiry = default ?
|
323
|
+
expiry = default ? TtlSanitizer.sanitize(ttl) : 0xFFFFFFFF
|
326
324
|
default ||= 0
|
327
325
|
(h, l) = split(count)
|
328
326
|
(dh, dl) = split(default)
|
@@ -390,12 +388,12 @@ module Dalli
|
|
390
388
|
end
|
391
389
|
|
392
390
|
def touch(key, ttl)
|
393
|
-
ttl =
|
391
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
394
392
|
write_generic [REQUEST, OPCODES[:touch], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:touch])
|
395
393
|
end
|
396
394
|
|
397
395
|
def gat(key, ttl, options = nil)
|
398
|
-
ttl =
|
396
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
399
397
|
req = [REQUEST, OPCODES[:gat], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:gat])
|
400
398
|
write(req)
|
401
399
|
generic_response(true, !!(options && options.is_a?(Hash) && options[:cache_nils]))
|
@@ -472,18 +470,6 @@ module Dalli
|
|
472
470
|
raise Dalli::ValueOverMaxSize, message
|
473
471
|
end
|
474
472
|
|
475
|
-
# https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L79
|
476
|
-
# > An expiration time, in seconds. Can be up to 30 days. After 30 days, is treated as a unix timestamp of an exact date.
|
477
|
-
MAX_ACCEPTABLE_EXPIRATION_INTERVAL = 30 * 24 * 60 * 60 # 30 days
|
478
|
-
def sanitize_ttl(ttl)
|
479
|
-
ttl_as_i = ttl.to_i
|
480
|
-
return ttl_as_i if ttl_as_i <= MAX_ACCEPTABLE_EXPIRATION_INTERVAL
|
481
|
-
now = Time.now.to_i
|
482
|
-
return ttl_as_i if ttl_as_i > now # already a timestamp
|
483
|
-
Dalli.logger.debug "Expiration interval (#{ttl_as_i}) too long for Memcached, converting to an expiration timestamp"
|
484
|
-
now + ttl_as_i
|
485
|
-
end
|
486
|
-
|
487
473
|
# Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
|
488
474
|
class NilObject; end
|
489
475
|
NOT_FOUND = NilObject.new
|
@@ -712,26 +698,6 @@ module Dalli
|
|
712
698
|
# (step, msg) = sasl.receive('challenge', content)
|
713
699
|
# raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
|
714
700
|
end
|
715
|
-
|
716
|
-
def parse_hostname(str)
|
717
|
-
res = str.match(/\A(\[([\h:]+)\]|[^:]+)(?::(\d+))?(?::(\d+))?\z/)
|
718
|
-
raise Dalli::DalliError, "Could not parse hostname #{str}" if res.nil? || res[1] == "[]"
|
719
|
-
hostnam = res[2] || res[1]
|
720
|
-
if hostnam.start_with?("/")
|
721
|
-
socket_type = :unix
|
722
|
-
# in case of unix socket, allow only setting of weight, not port
|
723
|
-
raise Dalli::DalliError, "Could not parse hostname #{str}" if res[4]
|
724
|
-
weigh = res[3]
|
725
|
-
else
|
726
|
-
socket_type = :tcp
|
727
|
-
por = res[3] || DEFAULT_PORT
|
728
|
-
por = Integer(por)
|
729
|
-
weigh = res[4]
|
730
|
-
end
|
731
|
-
weigh ||= DEFAULT_WEIGHT
|
732
|
-
weigh = Integer(weigh)
|
733
|
-
[hostnam, por, weigh, socket_type]
|
734
|
-
end
|
735
701
|
end
|
736
702
|
end
|
737
703
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
##
|
6
|
+
# Dalli::Protocol::ServerConfigParser parses a server string passed to
|
7
|
+
# a Dalli::Protocol::Binary instance into the hostname, port, weight, and
|
8
|
+
# socket_type.
|
9
|
+
##
|
10
|
+
class ServerConfigParser
|
11
|
+
# TODO: Revisit this, especially the IP/domain part. Likely
|
12
|
+
# can limit character set to LDH + '.'. Hex digit section
|
13
|
+
# appears to have been added to support IPv6, but as far as
|
14
|
+
# I can tell it doesn't work
|
15
|
+
SERVER_CONFIG_REGEXP = /\A(\[([\h:]+)\]|[^:]+)(?::(\d+))?(?::(\d+))?\z/.freeze
|
16
|
+
|
17
|
+
DEFAULT_PORT = 11_211
|
18
|
+
DEFAULT_WEIGHT = 1
|
19
|
+
|
20
|
+
def self.parse(str)
|
21
|
+
res = deconstruct_string(str)
|
22
|
+
|
23
|
+
hostname = normalize_hostname(str, res)
|
24
|
+
if hostname.start_with?('/')
|
25
|
+
socket_type = :unix
|
26
|
+
port, weight = attributes_for_unix_socket(res)
|
27
|
+
else
|
28
|
+
socket_type = :tcp
|
29
|
+
port, weight = attributes_for_tcp_socket(res)
|
30
|
+
end
|
31
|
+
[hostname, port, weight, socket_type]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.deconstruct_string(str)
|
35
|
+
mtch = str.match(SERVER_CONFIG_REGEXP)
|
36
|
+
raise Dalli::DalliError, "Could not parse hostname #{str}" if mtch.nil? || mtch[1] == '[]'
|
37
|
+
|
38
|
+
mtch
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.attributes_for_unix_socket(res)
|
42
|
+
# in case of unix socket, allow only setting of weight, not port
|
43
|
+
raise Dalli::DalliError, "Could not parse hostname #{res[0]}" if res[4]
|
44
|
+
|
45
|
+
[nil, normalize_weight(res[3])]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.attributes_for_tcp_socket(res)
|
49
|
+
[normalize_port(res[3]), normalize_weight(res[4])]
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.normalize_hostname(str, res)
|
53
|
+
raise Dalli::DalliError, "Could not parse hostname #{str}" if res.nil? || res[1] == '[]'
|
54
|
+
|
55
|
+
res[2] || res[1]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.normalize_port(port)
|
59
|
+
Integer(port || DEFAULT_PORT)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.normalize_weight(weight)
|
63
|
+
Integer(weight || DEFAULT_WEIGHT)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
##
|
6
|
+
# Utility class for sanitizing TTL arguments based on Memcached rules.
|
7
|
+
# TTLs are either expirations times in seconds (with a maximum value of
|
8
|
+
# 30 days) or expiration timestamps. This class sanitizes TTLs to ensure
|
9
|
+
# they meet those restrictions.
|
10
|
+
##
|
11
|
+
class TtlSanitizer
|
12
|
+
# https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L79
|
13
|
+
# > An expiration time, in seconds. Can be up to 30 days. After 30 days, is
|
14
|
+
# treated as a unix timestamp of an exact date.
|
15
|
+
MAX_ACCEPTABLE_EXPIRATION_INTERVAL = 30 * 24 * 60 * 60 # 30 days
|
16
|
+
|
17
|
+
# Ensures the TTL passed to Memcached is a valid TTL in the expected format.
|
18
|
+
def self.sanitize(ttl)
|
19
|
+
ttl_as_i = ttl.to_i
|
20
|
+
return ttl_as_i if less_than_max_expiration_interval?(ttl_as_i)
|
21
|
+
|
22
|
+
as_timestamp(ttl_as_i)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.less_than_max_expiration_interval?(ttl_as_i)
|
26
|
+
ttl_as_i <= MAX_ACCEPTABLE_EXPIRATION_INTERVAL
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.as_timestamp(ttl_as_i)
|
30
|
+
now = current_timestamp
|
31
|
+
return ttl_as_i if ttl_as_i > now # Already a timestamp
|
32
|
+
|
33
|
+
Dalli.logger.debug "Expiration interval (#{ttl_as_i}) too long for Memcached " \
|
34
|
+
'and too short to be a future timestamp,' \
|
35
|
+
'converting to an expiration timestamp'
|
36
|
+
now + ttl_as_i
|
37
|
+
end
|
38
|
+
|
39
|
+
# Pulled out into a method so it's easy to stub time
|
40
|
+
def self.current_timestamp
|
41
|
+
Time.now.to_i
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
@@ -5,6 +5,8 @@ require "dalli/client"
|
|
5
5
|
require "dalli/ring"
|
6
6
|
require "dalli/protocol"
|
7
7
|
require "dalli/protocol/binary"
|
8
|
+
require "dalli/protocol/server_config_parser"
|
9
|
+
require "dalli/protocol/ttl_sanitizer"
|
8
10
|
require 'dalli/protocol/value_compressor'
|
9
11
|
require "dalli/socket"
|
10
12
|
require "dalli/version"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dalli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter M. Goldstein
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-10-
|
12
|
+
date: 2021-10-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -58,6 +58,8 @@ files:
|
|
58
58
|
- lib/dalli/options.rb
|
59
59
|
- lib/dalli/protocol.rb
|
60
60
|
- lib/dalli/protocol/binary.rb
|
61
|
+
- lib/dalli/protocol/server_config_parser.rb
|
62
|
+
- lib/dalli/protocol/ttl_sanitizer.rb
|
61
63
|
- lib/dalli/protocol/value_compressor.rb
|
62
64
|
- lib/dalli/ring.rb
|
63
65
|
- lib/dalli/server.rb
|
@@ -83,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
85
|
- !ruby/object:Gem::Version
|
84
86
|
version: '0'
|
85
87
|
requirements: []
|
86
|
-
rubygems_version: 3.2.
|
88
|
+
rubygems_version: 3.2.30
|
87
89
|
signing_key:
|
88
90
|
specification_version: 4
|
89
91
|
summary: High performance memcached client for Ruby
|