dalli 4.3.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e97e2a407956737b411c33627d1f771be758017b881c68fab66edee95dc3249e
4
- data.tar.gz: b57638f133a592e9d57b71530bc925ff6589b27549e8785bc6c7f334906636ff
3
+ metadata.gz: c21c31c46fc4381658892703d01baf8611392f5c1f72b9473013d2e53bc23254
4
+ data.tar.gz: 1fd1a3ea06cc4bab1ebc1192e62ede8318521e7867a84b3dd09096364560979f
5
5
  SHA512:
6
- metadata.gz: 7dd43e9e5b09b65174f2e46b84de40e4e5bcfae9645c6bb67017ca81a64d344e7345cc4bf685f11d4779aeaafd7c7163df5a67bf8410153034f4adf523385b95
7
- data.tar.gz: b57506702dbdcb387490b3c0c56d2407aff4f0f789aec01e2b1a92c9f8d925a0225b8563e3dbc1ccf3c3ca72a25ca58f4199f1738d93bdf65a07893ed2930b6d
6
+ metadata.gz: 9c79888857cb9c9edc9a1f86447e630b54f52040305806ffe92d0966d326e78607e06387c6bbbcac8b96b380737389da2bcf98a9f93ef91e487e2a2152e7aad7
7
+ data.tar.gz: f1783996f1d49db8985b755e78c3df6cee25776504504aa1ce303d665fc4798d3b09b8136d97e23dc2a3de8243bca68e7db2e00dbd2942c05e2f918d8efd8d1a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,40 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 4.3.1
5
+ ==========
6
+
7
+ Bug Fixes:
8
+
9
+ - Fix socket compatibility with gems that monkey-patch TCPSocket (#996, #1012)
10
+ - Gems like `socksify` and `resolv-replace` modify `TCPSocket#initialize`, breaking Ruby 3.0+'s `connect_timeout:` keyword argument
11
+ - Detection now uses parameter signature checking instead of gem-specific method detection
12
+ - Falls back to `Timeout.timeout` when monkey-patching is detected
13
+ - Detection result is cached for performance
14
+
15
+ - Fix network retry bug with `socket_max_failures: 0` (#1065)
16
+ - Previously, setting `socket_max_failures: 0` could still cause retries due to error handling
17
+ - Introduced `RetryableNetworkError` subclass to distinguish retryable vs non-retryable errors
18
+ - `down!` now raises non-retryable `NetworkError`, `reconnect!` raises `RetryableNetworkError`
19
+ - Thanks to Graham Cooper (Shopify) for this fix
20
+
21
+ - Fix "character class has duplicated range" Ruby warning (#1067)
22
+ - Fixed regex in `KeyManager::VALID_NAMESPACE_SEPARATORS` that caused warnings on newer Ruby versions
23
+ - Thanks to Hartley McGuire for this fix
24
+
25
+ Improvements:
26
+
27
+ - Add StrictWarnings test helper to catch Ruby warnings early (#1067)
28
+
29
+ - Use bulk attribute setter for OpenTelemetry spans (#1068)
30
+ - Reduces lock acquisitions when setting span attributes
31
+ - Thanks to Robert Laurin (Shopify) for this optimization
32
+
33
+ - Fix double recording of exceptions on OpenTelemetry spans (#1069)
34
+ - OpenTelemetry's `in_span` method already records exceptions and sets error status automatically
35
+ - Removed redundant explicit exception recording that caused exceptions to appear twice in traces
36
+ - Thanks to Robert Laurin (Shopify) for this fix
37
+
4
38
  4.3.0
5
39
  ==========
6
40
 
data/Gemfile CHANGED
@@ -27,4 +27,8 @@ end
27
27
 
28
28
  group :test do
29
29
  gem 'ruby-prof', platform: :mri
30
+
31
+ # For socket compatibility testing (these gems monkey-patch TCPSocket)
32
+ gem 'resolv-replace', require: false
33
+ gem 'socksify', require: false
30
34
  end
data/lib/dalli/client.rb CHANGED
@@ -522,8 +522,10 @@ module Dalli
522
522
  def record_hit_miss_metrics(span, key_count, hit_count)
523
523
  return unless span
524
524
 
525
- span.set_attribute('db.memcached.hit_count', hit_count)
526
- span.set_attribute('db.memcached.miss_count', key_count - hit_count)
525
+ span.add_attributes({
526
+ 'db.memcached.hit_count' => hit_count,
527
+ 'db.memcached.miss_count' => key_count - hit_count
528
+ })
527
529
  end
528
530
 
529
531
  def get_multi_yielding(keys)
@@ -622,7 +624,7 @@ module Dalli
622
624
  }) do
623
625
  server.request(op, key, *args)
624
626
  end
625
- rescue NetworkError => e
627
+ rescue RetryableNetworkError => e
626
628
  Dalli.logger.debug { e.inspect }
627
629
  Dalli.logger.debug { 'retrying request with new server' }
628
630
  retry
@@ -90,12 +90,8 @@ module Dalli
90
90
  def trace(name, attributes = {})
91
91
  return yield unless enabled?
92
92
 
93
- tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client) do |span|
93
+ tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client) do |_span|
94
94
  yield
95
- rescue StandardError => e
96
- span.record_exception(e)
97
- span.status = OpenTelemetry::Trace::Status.error(e.message)
98
- raise
99
95
  end
100
96
  end
101
97
 
@@ -123,16 +119,10 @@ module Dalli
123
119
  # results
124
120
  # end
125
121
  #
126
- def trace_with_result(name, attributes = {})
122
+ def trace_with_result(name, attributes = {}, &)
127
123
  return yield(nil) unless enabled?
128
124
 
129
- tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client) do |span|
130
- yield(span)
131
- rescue StandardError => e
132
- span.record_exception(e)
133
- span.status = OpenTelemetry::Trace::Status.error(e.message)
134
- raise
135
- end
125
+ tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client, &)
136
126
  end
137
127
  end
138
128
  end
@@ -31,7 +31,7 @@ module Dalli
31
31
 
32
32
  # Valid separators: non-alphanumeric, single printable ASCII characters
33
33
  # Excludes: alphanumerics, whitespace, control characters
34
- VALID_NAMESPACE_SEPARATORS = /\A[^a-zA-Z0-9\s\x00-\x1F\x7F]\z/
34
+ VALID_NAMESPACE_SEPARATORS = /\A[^a-zA-Z0-9 \x00-\x1F\x7F]\z/
35
35
 
36
36
  def initialize(client_options)
37
37
  @key_options =
@@ -36,7 +36,7 @@ module Dalli
36
36
 
37
37
  servers = fetch_responses(servers, start_time, @ring.socket_timeout, &block) until servers.empty?
38
38
  end
39
- rescue NetworkError => e
39
+ rescue Dalli::RetryableNetworkError => e
40
40
  Dalli.logger.debug { e.inspect }
41
41
  Dalli.logger.debug { 'retrying pipelined gets because of timeout' }
42
42
  retry
@@ -143,7 +143,7 @@ module Dalli
143
143
  servers
144
144
  rescue NetworkError
145
145
  # Abort and raise if we encountered a network error. This triggers
146
- # a retry at the top level.
146
+ # a retry at the top level on RetryableNetworkError.
147
147
  abort_without_timeout(servers)
148
148
  raise
149
149
  end
@@ -28,7 +28,7 @@ module Dalli
28
28
  servers = setup_requests(hash, ttl, req_options)
29
29
  finish_requests(servers)
30
30
  end
31
- rescue NetworkError => e
31
+ rescue Dalli::RetryableNetworkError => e
32
32
  Dalli.logger.debug { e.inspect }
33
33
  Dalli.logger.debug { 'retrying pipelined sets because of network error' }
34
34
  retry
@@ -199,7 +199,7 @@ module Dalli
199
199
  def reconnect!(message)
200
200
  close
201
201
  sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
202
- raise Dalli::NetworkError, message
202
+ raise Dalli::RetryableNetworkError, message
203
203
  end
204
204
 
205
205
  def reset_down_info
data/lib/dalli/socket.rb CHANGED
@@ -90,6 +90,12 @@ module Dalli
90
90
  # options - supports enhanced logging in the case of a timeout
91
91
  attr_accessor :options
92
92
 
93
+ # Expected parameter signature for unmodified TCPSocket#initialize.
94
+ # Used to detect when gems like socksify or resolv-replace have monkey-patched
95
+ # TCPSocket, which breaks the connect_timeout: keyword argument.
96
+ TCPSOCKET_NATIVE_PARAMETERS = [[:rest]].freeze
97
+ private_constant :TCPSOCKET_NATIVE_PARAMETERS
98
+
93
99
  def self.open(host, port, options = {})
94
100
  create_socket_with_timeout(host, port, options) do |sock|
95
101
  sock.options = { host: host, port: port }.merge(options)
@@ -99,15 +105,18 @@ module Dalli
99
105
  end
100
106
  end
101
107
 
108
+ # Detect and cache whether TCPSocket supports the connect_timeout: keyword argument.
109
+ # Returns false if TCPSocket#initialize has been monkey-patched by gems like
110
+ # socksify or resolv-replace, which don't support keyword arguments.
111
+ def self.supports_connect_timeout?
112
+ return @supports_connect_timeout if defined?(@supports_connect_timeout)
113
+
114
+ @supports_connect_timeout = RUBY_VERSION >= '3.0' &&
115
+ ::TCPSocket.instance_method(:initialize).parameters == TCPSOCKET_NATIVE_PARAMETERS
116
+ end
117
+
102
118
  def self.create_socket_with_timeout(host, port, options)
103
- # Check that TCPSocket#initialize was not overwritten by resolv-replace gem
104
- # (part of ruby standard library since 3.0.0, should be removed in 3.4.0),
105
- # as it does not handle keyword arguments correctly.
106
- # To check this we are using the fact that resolv-replace
107
- # aliases TCPSocket#initialize method to #original_resolv_initialize.
108
- # https://github.com/ruby/resolv-replace/blob/v0.1.1/lib/resolv-replace.rb#L21
109
- if RUBY_VERSION >= '3.0' &&
110
- !::TCPSocket.private_method_defined?(:original_resolv_initialize)
119
+ if supports_connect_timeout?
111
120
  sock = new(host, port, connect_timeout: options[:socket_timeout])
112
121
  yield(sock)
113
122
  else
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '4.3.0'
4
+ VERSION = '4.3.1'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -28,6 +28,9 @@ module Dalli
28
28
  # raised when Memcached response with a SERVER_ERROR
29
29
  class ServerError < DalliError; end
30
30
 
31
+ # socket/server communication error that can be retried
32
+ class RetryableNetworkError < NetworkError; end
33
+
31
34
  # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
32
35
  class NilObject; end # rubocop:disable Lint/EmptyClass
33
36
  NOT_FOUND = NilObject.new
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: 4.3.0
4
+ version: 4.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein