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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4afe70ee94b027c2934fa368a16ca42bf41b7a844c0785c706e2035dba372f66
4
- data.tar.gz: 340cf977e332834bfae268995e2d5c70158813f5405fbeee7764e3cc8fa1eb9f
3
+ metadata.gz: de12f6f4e7e074752c4b3d2ccaa24450fd081e6e4ded890c632158858e5aa939
4
+ data.tar.gz: '01093957dc00b0742995b5d9198014dc480bd08788f4f3c2e5160cd2a6ca8725'
5
5
  SHA512:
6
- metadata.gz: a3f8a4ca2792149afa6c5b125edad177a69f2bfdaba5106632da7e1ff8c80d0549ea9f4077240d853115caa1711e10e0e966191b9fbb4a5f7103a95bd70629b2
7
- data.tar.gz: 28b0926d7367973a0aa57b1d9c95b57f82bf3d7ffd1874259715285538eaa3ab07ab40b67d7c7444734d23da8b20c8f099b1c5c7664f43028aa01715efd48239
6
+ metadata.gz: c073d5ce9922b8617c11c507b8b90603ccdf304419c2fe395af33467f880c75e4a22e9e5e6eae387c78ae937b6b8de5d68eba87b323967cce03a8aaffeeba1d8
7
+ data.tar.gz: f7fd88ee88a887c44a1b94d0e3c6c7b0d210d08c38db39ea387585a18a82c5cd0a4e9a57daaa444e927e4c47f81fbd404daeec139ded6d8ff6d39fe3e565022d
data/History.md CHANGED
@@ -1,6 +1,12 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 3.0.4
5
+ ==========
6
+
7
+ - Clean connections and retry after NetworkError in get_multi (andrejbl)
8
+ - Internal refactoring and cleanup (petergoldstein)
9
+
4
10
  3.0.3
5
11
  ==========
6
12
 
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 DalliError, NetworkError => e
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
- servers.delete(server)
390
+ deleted.append(server)
385
391
  end
386
392
  end
387
- servers
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.delete(server)
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 = parse_hostname(attribs)
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 = sanitize_ttl(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 = sanitize_ttl(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 = sanitize_ttl(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 ? sanitize_ttl(ttl) : 0xFFFFFFFF
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 = sanitize_ttl(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 = sanitize_ttl(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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = "3.0.3"
4
+ VERSION = "3.0.4"
5
5
  end
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.3
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-21 00:00:00.000000000 Z
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.29
88
+ rubygems_version: 3.2.30
87
89
  signing_key:
88
90
  specification_version: 4
89
91
  summary: High performance memcached client for Ruby