dalli 5.0.2 → 5.0.4

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: ae3cbae2603955279bb78b75682ac1f56105d5ec2b2e0d464ab15255ed23369b
4
- data.tar.gz: 2e5a3960e638293cfdf5663ef959d1ab9690620e09f0151a844cdeb9424cbf80
3
+ metadata.gz: b211a810c4d54d2d4d92203b390e8b29260f725e6dae699ca04703ac42cef8e9
4
+ data.tar.gz: 4d765c4491412ad4b9c8ab6038232436cae5824db2abaffc20119f2dfb7fa1ee
5
5
  SHA512:
6
- metadata.gz: 243382482bc345bf982f2cb96fae28b974f37b2eee653a71c7f3a85518ef5acdbfda6f2fddacfa31da6252774000e11a1c96d3707d29808c636951d85ace17be
7
- data.tar.gz: 5938608c26cd307e397cad4fdf6e2fda4519fdbd8df2b7d75352b691996fec4dca59632cc0053b8bf410612c8e6cada73ccad7baf5c000a183bd04bb196d056e
6
+ metadata.gz: a09168f456e5ce4691d0e24b128997c3f87bee7e046592a600490bc8e9526d1ce681d156fc9997c7045e623017ee0df1273d46c16656820b0f4933b8acc42cbb
7
+ data.tar.gz: 14e72c85d60582e03b19f04b4fd822a2e789fc6f780ae3f55944271dc25f0eeb7d6de0acade39bedf4729710731d273f8501f2078dc690fa3093926041b8d178
data/CHANGELOG.md CHANGED
@@ -1,6 +1,41 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ Unreleased
5
+ ==========
6
+
7
+ 5.0.4
8
+ ==========
9
+
10
+ Bug fixes:
11
+
12
+ - Fix `string_fastpath` flag collision with compression (#1099)
13
+ - `ValueSerializer::FLAG_UTF8` and `ValueCompressor::FLAG_COMPRESSED` were both `0x2`, causing `Dalli::UnmarshalError` on any UTF-8 string written with `string_fastpath: true` when compression is enabled, and silent encoding corruption for binary strings
14
+ - Introduces `Dalli::Flags` to centralise bit flag constants; UTF8 is reassigned to `0x4`
15
+ - Adds regression test covering short/long UTF-8, binary, and cross-client read scenarios
16
+ - Thanks to Jean Boussier and Mikael Henriksson for the fix and regression test
17
+
18
+ - Fix client-level `string_fastpath: true` being silently ignored (#1101)
19
+ - `Dalli::Client.new(servers, string_fastpath: true)` had no effect; the fast path was only taken when `string_fastpath: true` was passed as a per-request option on each `set` call
20
+ - Per-request option continues to take precedence over the client-level setting in both directions
21
+
22
+ 5.0.3
23
+ ==========
24
+
25
+ Performance:
26
+
27
+ - Eliminate double array allocation in `Client#perform` (#1093)
28
+ - Changed method signature from `perform(*all_args)` with destructuring to `perform(op, key, *args)`, letting Ruby decompose arguments directly without intermediate array allocations
29
+ - Reduces benchmark time by ~39% across all Dalli operations (get, set, delete, etc.)
30
+ - Thanks to Sam Obeid for this contribution
31
+
32
+ Features:
33
+
34
+ - Support `connect_timeout:` keyword argument with `resolv-replace` >= 0.2.0, which now correctly forwards keyword arguments through its `TCPSocket` patch (#1096)
35
+
36
+ - Add `Dalli::Instrumentation.disable!` to allow disabling OpenTelemetry instrumentation at runtime (#1088)
37
+ - Also exposes `Dalli::Instrumentation.tracer=` for setting a custom tracer
38
+
4
39
  5.0.2
5
40
  ==========
6
41
 
data/README.md CHANGED
@@ -90,6 +90,20 @@ Exceptions are automatically recorded on spans with error status. When an operat
90
90
  2. The span status is set to error with the exception message
91
91
  3. The exception is re-raised to the caller
92
92
 
93
+ ### Disabling Instrumentation
94
+
95
+ To disable instrumentation at runtime (e.g., in tests or specific environments):
96
+
97
+ ```ruby
98
+ Dalli::Instrumentation.disable!
99
+ ```
100
+
101
+ You can also assign a custom tracer directly:
102
+
103
+ ```ruby
104
+ Dalli::Instrumentation.tracer = my_custom_tracer
105
+ ```
106
+
93
107
  ### Zero Overhead
94
108
 
95
109
  When OpenTelemetry is not present, there is zero overhead - the tracing code checks once at startup and bypasses all instrumentation logic entirely when the SDK is not loaded.
data/lib/dalli/client.rb CHANGED
@@ -650,11 +650,11 @@ module Dalli
650
650
  # a particular memcached instance becomes unreachable, or the
651
651
  # operation times out.
652
652
  ##
653
- def perform(*all_args)
653
+ # rubocop:disable Naming/MethodParameterName
654
+ def perform(op, key, *args)
655
+ # rubocop:enable Naming/MethodParameterName
654
656
  return yield if block_given?
655
657
 
656
- op, key, *args = all_args
657
-
658
658
  key = key.to_s
659
659
  key = @key_manager.validate_key(key)
660
660
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dalli
4
+ module Flags
5
+ # https://www.hjp.at/zettel/m/memcached_flags.rxml
6
+ # Looks like most clients use bit 0 to indicate native language serialization
7
+ SERIALIZED = 0x1
8
+
9
+ # https://www.hjp.at/zettel/m/memcached_flags.rxml
10
+ # Looks like most clients use bit 1 to indicate gzip compression.
11
+ COMPRESSED = 0x2
12
+
13
+ UTF8 = 0x4
14
+ end
15
+ end
@@ -61,13 +61,14 @@ module Dalli
61
61
  # Uses the library name 'dalli' and current Dalli::VERSION.
62
62
  #
63
63
  # @return [OpenTelemetry::Trace::Tracer, nil] the tracer or nil if OTel unavailable
64
- # rubocop:disable ThreadSafety/ClassInstanceVariable
64
+ # rubocop:disable ThreadSafety/ClassInstanceVariable, ThreadSafety/ClassAndModuleAttributes
65
65
  def tracer
66
66
  return @tracer if defined?(@tracer)
67
67
 
68
68
  @tracer = (OpenTelemetry.tracer_provider.tracer('dalli', Dalli::VERSION) if defined?(OpenTelemetry))
69
69
  end
70
- # rubocop:enable ThreadSafety/ClassInstanceVariable
70
+
71
+ attr_writer :tracer
71
72
 
72
73
  # Returns true if instrumentation is enabled (OpenTelemetry SDK is available).
73
74
  #
@@ -76,6 +77,15 @@ module Dalli
76
77
  !tracer.nil?
77
78
  end
78
79
 
80
+ # Disable instrumentation.
81
+ #
82
+ # @return [nil]
83
+ def disable!
84
+ @tracer = nil
85
+ end
86
+
87
+ # rubocop:enable ThreadSafety/ClassInstanceVariable, ThreadSafety/ClassAndModuleAttributes
88
+
79
89
  # Wraps a block with a span if instrumentation is enabled.
80
90
  #
81
91
  # Creates a client span with the given name and attributes merged with
@@ -20,10 +20,6 @@ module Dalli
20
20
 
21
21
  OPTIONS = DEFAULTS.keys.freeze
22
22
 
23
- # https://www.hjp.at/zettel/m/memcached_flags.rxml
24
- # Looks like most clients use bit 1 to indicate gzip compression.
25
- FLAG_COMPRESSED = 0x2
26
-
27
23
  def initialize(client_options)
28
24
  @compression_options =
29
25
  DEFAULTS.merge(client_options.slice(*OPTIONS))
@@ -32,13 +28,13 @@ module Dalli
32
28
  def store(value, req_options, bitflags)
33
29
  do_compress = compress_value?(value, req_options)
34
30
  store_value = do_compress ? compressor.compress(value) : value
35
- bitflags |= FLAG_COMPRESSED if do_compress
31
+ bitflags |= Flags::COMPRESSED if do_compress
36
32
 
37
33
  [store_value, bitflags]
38
34
  end
39
35
 
40
36
  def retrieve(value, bitflags)
41
- compressed = bitflags.anybits?(FLAG_COMPRESSED)
37
+ compressed = bitflags.anybits?(Flags::COMPRESSED)
42
38
  compressed ? compressor.decompress(value) : value
43
39
 
44
40
  # TODO: We likely want to move this rescue into the Dalli::Compressor / Dalli::GzipCompressor
@@ -10,16 +10,12 @@ module Dalli
10
10
  ##
11
11
  class ValueSerializer
12
12
  DEFAULTS = {
13
- serializer: Marshal
13
+ serializer: Marshal,
14
+ string_fastpath: false
14
15
  }.freeze
15
16
 
16
17
  OPTIONS = DEFAULTS.keys.freeze
17
18
 
18
- # https://www.hjp.at/zettel/m/memcached_flags.rxml
19
- # Looks like most clients use bit 0 to indicate native language serialization
20
- FLAG_SERIALIZED = 0x1
21
- FLAG_UTF8 = 0x2
22
-
23
19
  # Class variable to track whether the Marshal warning has been logged
24
20
  @@marshal_warning_logged = false # rubocop:disable Style/ClassVars
25
21
 
@@ -35,18 +31,18 @@ module Dalli
35
31
  return store_raw(value, bitflags) if req_options&.dig(:raw)
36
32
  return store_string_fastpath(value, bitflags) if use_string_fastpath?(value, req_options)
37
33
 
38
- [serialize_value(value), bitflags | FLAG_SERIALIZED]
34
+ [serialize_value(value), bitflags | Flags::SERIALIZED]
39
35
  end
40
36
 
41
37
  def retrieve(value, bitflags)
42
- serialized = bitflags.anybits?(FLAG_SERIALIZED)
38
+ serialized = bitflags.anybits?(Flags::SERIALIZED)
43
39
  if serialized
44
40
  begin
45
41
  serializer.load(value)
46
42
  rescue StandardError
47
43
  raise UnmarshalError, 'Unable to unmarshal value'
48
44
  end
49
- elsif bitflags.anybits?(FLAG_UTF8)
45
+ elsif bitflags.anybits?(Flags::UTF8)
50
46
  value.force_encoding(Encoding::UTF_8)
51
47
  else
52
48
  value
@@ -86,13 +82,15 @@ module Dalli
86
82
  def store_string_fastpath(value, bitflags)
87
83
  case value.encoding
88
84
  when Encoding::BINARY then [value, bitflags]
89
- when Encoding::UTF_8 then [value, bitflags | FLAG_UTF8]
90
- else [serialize_value(value), bitflags | FLAG_SERIALIZED]
85
+ when Encoding::UTF_8 then [value, bitflags | Flags::UTF8]
86
+ else [serialize_value(value), bitflags | Flags::SERIALIZED]
91
87
  end
92
88
  end
93
89
 
94
90
  def use_string_fastpath?(value, req_options)
95
- req_options&.dig(:string_fastpath) && value.instance_of?(String)
91
+ fastpath = req_options&.dig(:string_fastpath)
92
+ fastpath = @serialization_options[:string_fastpath] if fastpath.nil?
93
+ fastpath && value.instance_of?(String)
96
94
  end
97
95
 
98
96
  def warn_if_marshal_default(protocol_options)
data/lib/dalli/socket.rb CHANGED
@@ -106,14 +106,19 @@ module Dalli
106
106
  end
107
107
 
108
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.
109
+ # Returns true for an unmodified TCPSocket on Ruby 3.0+, or for resolv-replace >= 0.2.0
110
+ # which forwards keyword arguments through its patch.
111
+ # Returns false when monkey-patched by gems like socksify or resolv-replace < 0.2.0.
111
112
  # rubocop:disable ThreadSafety/ClassInstanceVariable
112
113
  def self.supports_connect_timeout?
113
114
  return @supports_connect_timeout if defined?(@supports_connect_timeout)
114
115
 
115
- @supports_connect_timeout = RUBY_VERSION >= '3.0' &&
116
- ::TCPSocket.instance_method(:initialize).parameters == TCPSOCKET_NATIVE_PARAMETERS
116
+ @supports_connect_timeout = RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '3.0' &&
117
+ ::TCPSocket.instance_method(:initialize).parameters.then do |params|
118
+ params == TCPSOCKET_NATIVE_PARAMETERS || params.any? do |type, _|
119
+ type == :keyrest
120
+ end
121
+ end
117
122
  end
118
123
  # rubocop:enable ThreadSafety/ClassInstanceVariable
119
124
 
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '5.0.2'
4
+ VERSION = '5.0.4'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.6'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -61,6 +61,7 @@ end
61
61
  require_relative 'dalli/version'
62
62
  require_relative 'dalli/instrumentation'
63
63
 
64
+ require_relative 'dalli/flags'
64
65
  require_relative 'dalli/compressor'
65
66
  require_relative 'dalli/client'
66
67
  require_relative 'dalli/key_manager'
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: 5.0.2
4
+ version: 5.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -40,6 +40,7 @@ files:
40
40
  - lib/dalli/cas/client.rb
41
41
  - lib/dalli/client.rb
42
42
  - lib/dalli/compressor.rb
43
+ - lib/dalli/flags.rb
43
44
  - lib/dalli/instrumentation.rb
44
45
  - lib/dalli/key_manager.rb
45
46
  - lib/dalli/options.rb
@@ -87,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
88
  - !ruby/object:Gem::Version
88
89
  version: '0'
89
90
  requirements: []
90
- rubygems_version: 4.0.6
91
+ rubygems_version: 4.0.10
91
92
  specification_version: 4
92
93
  summary: High performance memcached client for Ruby
93
94
  test_files: []