dalli 4.3.3 → 5.0.0

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: 57b78e30ee409a2d742fc47ee57ccdd482fe9c75f369366ae315b7ef8b649934
4
- data.tar.gz: b8cad66f3cba53bbcb84b18f406eed60f22209c10dd4a312063a1e13189185ca
3
+ metadata.gz: 16d7d5950137f4a51ae41392eaa576732595ac4020ae7be972d6fb4b33b9861d
4
+ data.tar.gz: ebb799522259a9a32a86e1dbfd13e810427f787f8110415509288655a5a5460b
5
5
  SHA512:
6
- metadata.gz: 18b26e3c4aa30e5f8b4195d95307afca6c9240dd1154a36966d40d52047e638758850ec06e2a40fd1bd8e69fe150dee7409b1f4a565f0a80e397aaf115315463
7
- data.tar.gz: b6db69ecbc1e6d34587d8ec29b861f6a71c863b36229d3c642e3d0e646ba6234e3bac04225d64ff174d12f35b728663e82e1d2b5f7e93dc9ac3e1ff33fd58db3
6
+ metadata.gz: 74e32eace9001bd50be0617f10234f1440f86e506facc7247cf6041cf81fa85e966a5721e2a7ff364eb89da19bd46cc92b2631d99d4cbb21aee8240a475a76b5
7
+ data.tar.gz: 8e2bc294a1fa08a04f0bdfa300394751e63a7c1a21cac6aa1080a37e9491fe33091150047f664607ae42596d0bb2ac60f05c06ad97aee83165de754b9cceeed2
data/CHANGELOG.md CHANGED
@@ -1,28 +1,34 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
- 4.3.3
4
+ 5.0.0
5
5
  ==========
6
6
 
7
- Performance:
7
+ **Breaking Changes:**
8
8
 
9
- - Reduce object allocations in pipelined get response processing (#1072)
10
- - Offset-based `ResponseBuffer`: track a read offset instead of slicing a new string after every parsed response; compact only when the consumed portion exceeds 4KB and more than half the buffer
11
- - Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing in both binary and meta protocols
12
- - Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
13
- - `PipelinedGetter`: replace Hash-based socket-to-server mapping with linear scan (faster for typical 1-5 server counts); use `Process.clock_gettime(CLOCK_MONOTONIC)` instead of `Time.now`
14
- - Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
9
+ - **Removed binary protocol** - The meta protocol is now the only supported protocol
10
+ - The `:protocol` option is no longer used
11
+ - Requires memcached 1.6+ (for meta protocol support)
12
+ - Users on older memcached versions must upgrade or stay on Dalli 4.x
15
13
 
16
- Bug Fixes:
14
+ - **Removed SASL authentication** - The meta protocol does not support authentication
15
+ - Use network-level security (firewall rules, VPN) or memcached's TLS support instead
16
+ - Users requiring SASL authentication must stay on Dalli 4.x with binary protocol
17
17
 
18
- - Skip OTel integration tests when meta protocol is unavailable (#1072)
18
+ - **Ruby 3.3+ required** - Dropped support for Ruby 3.1 and 3.2
19
+ - Ruby 3.2 reached end-of-life in March 2026
20
+ - JRuby remains supported
19
21
 
20
- 4.3.2
21
- ==========
22
+ Performance:
23
+
24
+ - **~7% read performance improvement** (CRuby only)
25
+ - Use native `IO#read` instead of custom `readfull` implementation
26
+ - Enabled by Ruby 3.3's `IO#timeout=` support
27
+ - JRuby continues to use `readfull` for compatibility
22
28
 
23
29
  OpenTelemetry:
24
30
 
25
- - Migrate to stable OTel semantic conventions
31
+ - Migrate to stable OTel semantic conventions (#1070)
26
32
  - `db.system` renamed to `db.system.name`
27
33
  - `db.operation` renamed to `db.operation.name`
28
34
  - `server.address` now contains hostname only; `server.port` is a separate integer attribute
@@ -32,6 +38,12 @@ OpenTelemetry:
32
38
  - Add `peer.service` span attribute
33
39
  - `:otel_peer_service` option for logical service naming
34
40
 
41
+ Internal:
42
+
43
+ - Simplified protocol directory structure: moved `lib/dalli/protocol/meta/*` to `lib/dalli/protocol/`
44
+ - Removed deprecated binary protocol files and SASL authentication code
45
+ - Removed `require 'set'` (autoloaded in Ruby 3.3+)
46
+
35
47
  4.3.1
36
48
  ==========
37
49
 
data/README.md CHANGED
@@ -10,26 +10,14 @@ Dalli supports:
10
10
  * Fine-grained control of data serialization and compression
11
11
  * Thread-safe operation (either through use of a connection pool, or by using the Dalli client in threadsafe mode)
12
12
  * SSL/TLS connections to memcached
13
- * SASL authentication
14
13
  * OpenTelemetry distributed tracing (automatic when SDK is present)
15
14
 
16
15
  The name is a variant of Salvador Dali for his famous painting [The Persistence of Memory](http://en.wikipedia.org/wiki/The_Persistence_of_Memory).
17
16
 
18
17
  ## Requirements
19
18
 
20
- * Ruby 3.1 or later
21
- * memcached 1.4 or later (1.6+ recommended for meta protocol support)
22
-
23
- ## Protocol Options
24
-
25
- Dalli supports two protocols for communicating with memcached:
26
-
27
- * `:binary` (default) - Works with all memcached versions, supports SASL authentication
28
- * `:meta` - Requires memcached 1.6+, better performance for some operations, no authentication support
29
-
30
- ```ruby
31
- Dalli::Client.new('localhost:11211', protocol: :meta)
32
- ```
19
+ * Ruby 3.3 or later (JRuby also supported)
20
+ * memcached 1.6 or later
33
21
 
34
22
  ## Configuration Options
35
23
 
@@ -64,7 +52,7 @@ By default, Dalli uses Ruby's Marshal for serialization. Deserializing untrusted
64
52
  Dalli::Client.new('localhost:11211', serializer: JSON)
65
53
  ```
66
54
 
67
- See the [4.0-Upgrade.md](4.0-Upgrade.md) guide for more information.
55
+ See the [5.0-Upgrade.md](5.0-Upgrade.md) guide for upgrade information.
68
56
 
69
57
  ## OpenTelemetry Tracing
70
58
 
data/lib/dalli/client.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'digest/md5'
4
- require 'set'
5
4
 
6
5
  # encoding: ascii
7
6
  module Dalli
@@ -48,8 +47,6 @@ module Dalli
48
47
  # serialization pipeline.
49
48
  # - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
50
49
  # useful for injecting a FIPS compliant hash object.
51
- # - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
52
- # to communicate with memcached.
53
50
  # - :otel_db_statement - controls the +db.query.text+ span attribute when OpenTelemetry is loaded.
54
51
  # +:include+ logs the full operation and key(s), +:obfuscate+ replaces keys with "?",
55
52
  # +nil+ (default) omits the attribute entirely.
@@ -58,9 +55,9 @@ module Dalli
58
55
  def initialize(servers = nil, options = {})
59
56
  @normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
60
57
  @options = normalize_options(options)
58
+ warn_removed_options(@options)
61
59
  @key_manager = ::Dalli::KeyManager.new(@options)
62
60
  @ring = nil
63
- emit_deprecation_warnings
64
61
  end
65
62
 
66
63
  #
@@ -102,10 +99,7 @@ module Dalli
102
99
  end
103
100
 
104
101
  ##
105
- # Get value with extended metadata using the meta protocol.
106
- #
107
- # IMPORTANT: This method requires memcached 1.6+ and the meta protocol (protocol: :meta).
108
- # It will raise an error if used with the binary protocol.
102
+ # Get value with extended metadata.
109
103
  #
110
104
  # @param key [String] the cache key
111
105
  # @param options [Hash] options controlling what metadata to return
@@ -133,8 +127,6 @@ module Dalli
133
127
  # # => { value: "data", cas: 123, hit_before: true, last_access: 42 }
134
128
  #
135
129
  def get_with_metadata(key, options = {})
136
- raise_unless_meta_protocol!
137
-
138
130
  key = key.to_s
139
131
  key = @key_manager.validate_key(key)
140
132
 
@@ -208,9 +200,6 @@ module Dalli
208
200
  # cache entry (the "thundering herd" problem). Only one client wins the right to
209
201
  # regenerate; other clients receive the stale value (if available) or wait.
210
202
  #
211
- # IMPORTANT: This method requires memcached 1.6+ and the meta protocol (protocol: :meta).
212
- # It will raise an error if used with the binary protocol.
213
- #
214
203
  # @param key [String] the cache key
215
204
  # @param ttl [Integer] time-to-live for the cached value in seconds
216
205
  # @param lock_ttl [Integer] how long the lock/stub lives (default: 30 seconds)
@@ -236,8 +225,6 @@ module Dalli
236
225
  def fetch_with_lock(key, ttl: nil, lock_ttl: 30, recache_threshold: nil, req_options: nil, &block)
237
226
  raise ArgumentError, 'Block is required for fetch_with_lock' unless block_given?
238
227
 
239
- raise_unless_meta_protocol!
240
-
241
228
  key = key.to_s
242
229
  key = @key_manager.validate_key(key)
243
230
 
@@ -514,10 +501,6 @@ module Dalli
514
501
 
515
502
  private
516
503
 
517
- # Records hit/miss metrics on a span for cache observability.
518
- # @param span [OpenTelemetry::Trace::Span, nil] the span to record on
519
- # @param key_count [Integer] total keys requested
520
- # @param hit_count [Integer] keys found in cache
521
504
  def record_hit_miss_metrics(span, key_count, hit_count)
522
505
  return unless span
523
506
 
@@ -607,16 +590,7 @@ module Dalli
607
590
  end
608
591
 
609
592
  def ring
610
- @ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
611
- end
612
-
613
- def protocol_implementation
614
- @protocol_implementation ||= case @options[:protocol]&.to_s
615
- when 'meta'
616
- Dalli::Protocol::Meta
617
- else
618
- Dalli::Protocol::Binary
619
- end
593
+ @ring ||= Dalli::Ring.new(@normalized_servers, @options)
620
594
  end
621
595
 
622
596
  ##
@@ -627,7 +601,7 @@ module Dalli
627
601
  #
628
602
  # This method also forces retries on network errors - when
629
603
  # a particular memcached instance becomes unreachable, or the
630
- # operational times out.
604
+ # operation times out.
631
605
  ##
632
606
  def perform(*all_args)
633
607
  return yield if block_given?
@@ -654,6 +628,21 @@ module Dalli
654
628
  raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
655
629
  end
656
630
 
631
+ REMOVED_OPTIONS = {
632
+ protocol: 'Dalli 5.0 only supports the meta protocol. The :protocol option has been removed.',
633
+ username: 'Dalli 5.0 removed SASL authentication support. The :username option is ignored.',
634
+ password: 'Dalli 5.0 removed SASL authentication support. The :password option is ignored.'
635
+ }.freeze
636
+ private_constant :REMOVED_OPTIONS
637
+
638
+ def warn_removed_options(opts)
639
+ REMOVED_OPTIONS.each do |key, message|
640
+ next unless opts.key?(key)
641
+
642
+ Dalli.logger.warn(message)
643
+ end
644
+ end
645
+
657
646
  def pipelined_getter
658
647
  PipelinedGetter.new(ring, @key_manager)
659
648
  end
@@ -665,15 +654,5 @@ module Dalli
665
654
  def pipelined_deleter
666
655
  PipelinedDeleter.new(ring, @key_manager)
667
656
  end
668
-
669
- def raise_unless_meta_protocol!
670
- return if protocol_implementation == Dalli::Protocol::Meta
671
-
672
- raise Dalli::DalliError,
673
- 'This operation requires the meta protocol (memcached 1.6+). ' \
674
- 'Use protocol: :meta when creating the client.'
675
- end
676
-
677
- include ProtocolDeprecations
678
657
  end
679
658
  end
@@ -8,7 +8,7 @@ module Dalli
8
8
  # When OpenTelemetry is loaded, Dalli automatically creates spans for cache operations.
9
9
  # When OpenTelemetry is not available, all tracing methods are no-ops with zero overhead.
10
10
  #
11
- # Dalli 4.3.2 uses the stable OTel semantic conventions for database spans.
11
+ # Dalli 5.0 uses the stable OTel semantic conventions for database spans.
12
12
  #
13
13
  # == Span Attributes
14
14
  #
data/lib/dalli/options.rb CHANGED
@@ -6,7 +6,7 @@ module Dalli
6
6
  # Make Dalli threadsafe by using a lock around all
7
7
  # public server methods.
8
8
  #
9
- # Dalli::Protocol::Binary.extend(Dalli::Threadsafe)
9
+ # Dalli::Protocol::Meta.extend(Dalli::Threadsafe)
10
10
  #
11
11
  module Threadsafe
12
12
  def self.extended(obj)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
-
5
3
  module Dalli
6
4
  ##
7
5
  # Contains logic for the pipelined gets implemented by the client.
@@ -29,7 +27,7 @@ module Dalli
29
27
  # Stores partial results collected during interleaved send phase
30
28
  @partial_results = {}
31
29
  servers = setup_requests(keys)
32
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
30
+ start_time = Time.now
33
31
 
34
32
  # First yield any partial results collected during interleaved send
35
33
  yield_partial_results(&block)
@@ -149,7 +147,7 @@ module Dalli
149
147
  end
150
148
 
151
149
  def remaining_time(start, timeout)
152
- elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
150
+ elapsed = Time.now - start
153
151
  return 0 if elapsed > timeout
154
152
 
155
153
  timeout - elapsed
@@ -168,8 +166,8 @@ module Dalli
168
166
  # Processes responses from a server. Returns true if there are no
169
167
  # additional responses from this server.
170
168
  def process_server(server)
171
- server.pipeline_next_responses do |key, value, cas|
172
- yield @key_manager.key_without_namespace(key), [value, cas]
169
+ server.pipeline_next_responses.each_pair do |key, value_list|
170
+ yield @key_manager.key_without_namespace(key), value_list
173
171
  end
174
172
 
175
173
  server.pipeline_complete?
@@ -178,13 +176,18 @@ module Dalli
178
176
  def servers_with_response(servers, timeout)
179
177
  return [] if servers.empty?
180
178
 
181
- sockets = servers.map(&:sock)
182
- readable, = IO.select(sockets, nil, nil, timeout)
179
+ # TODO: - This is a bit challenging. Essentially the PipelinedGetter
180
+ # is a reactor, but without the benefit of a Fiber or separate thread.
181
+ # My suspicion is that we may want to try and push this down into the
182
+ # individual servers, but I'm not sure. For now, we keep the
183
+ # mapping between the alerted object (the socket) and the
184
+ # corrresponding server here.
185
+ server_map = servers.each_with_object({}) { |s, h| h[s.sock] = s }
186
+
187
+ readable, = IO.select(server_map.keys, nil, nil, timeout)
183
188
  return [] if readable.nil?
184
189
 
185
- # For typical server counts (1-5), linear scan is faster than
186
- # building and looking up a hash map
187
- readable.filter_map { |sock| servers.find { |s| s.sock == sock } }
190
+ readable.map { |sock| server_map[sock] }
188
191
  end
189
192
 
190
193
  def groups_for_keys(*keys)
@@ -22,6 +22,7 @@ module Dalli
22
22
 
23
23
  def initialize(attribs, client_options = {})
24
24
  hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
25
+ warn_uri_credentials(user_creds)
25
26
  @options = client_options.merge(user_creds)
26
27
  @raw_mode = client_options[:raw]
27
28
  @value_marshaller = @raw_mode ? StringMarshaller.new(@options) : ValueMarshaller.new(@options)
@@ -91,13 +92,10 @@ module Dalli
91
92
  # repeatedly whenever this server's socket is readable until
92
93
  # #pipeline_complete?.
93
94
  #
94
- # When a block is given, yields (key, value, cas) for each response,
95
- # avoiding intermediate Hash allocation. Returns nil.
96
- # Without a block, returns a Hash of { key => [value, cas] }.
97
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
98
- def pipeline_next_responses(&block)
95
+ # Returns a Hash of kv pairs received.
96
+ def pipeline_next_responses
99
97
  reconnect_on_pipeline_complete!
100
- values = nil
98
+ values = {}
101
99
 
102
100
  response_buffer.read
103
101
 
@@ -111,24 +109,16 @@ module Dalli
111
109
 
112
110
  # If the status is ok and the key is not nil, then this is a
113
111
  # getkq response with a value that we want to set in the response hash
114
- unless key.nil?
115
- if block
116
- yield key, value, cas
117
- else
118
- values ||= {}
119
- values[key] = [value, cas]
120
- end
121
- end
112
+ values[key] = [value, cas] unless key.nil?
122
113
 
123
114
  # Get the next response from the buffer
124
115
  status, cas, key, value = response_buffer.process_single_getk_response
125
116
  end
126
117
 
127
- values || {}
118
+ values
128
119
  rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
129
120
  @connection_manager.error_on_request!(e)
130
121
  end
131
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
132
122
 
133
123
  # Abort current pipelined get. Generally used to signal an external
134
124
  # timeout during pipelined get. The underlying socket is
@@ -152,18 +142,6 @@ module Dalli
152
142
  !response_buffer.in_progress?
153
143
  end
154
144
 
155
- def username
156
- @options[:username] || ENV.fetch('MEMCACHE_USERNAME', nil)
157
- end
158
-
159
- def password
160
- @options[:password] || ENV.fetch('MEMCACHE_PASSWORD', nil)
161
- end
162
-
163
- def require_auth?
164
- !username.nil?
165
- end
166
-
167
145
  def quiet?
168
146
  Thread.current[::Dalli::QUIET]
169
147
  end
@@ -173,6 +151,16 @@ module Dalli
173
151
 
174
152
  private
175
153
 
154
+ URI_CREDENTIAL_WARNING = 'Dalli 5.0 removed SASL authentication. ' \
155
+ 'Credentials in memcached:// URIs are ignored.'
156
+ private_constant :URI_CREDENTIAL_WARNING
157
+
158
+ def warn_uri_credentials(user_creds)
159
+ return if user_creds[:username].nil? && user_creds[:password].nil?
160
+
161
+ Dalli.logger.warn(URI_CREDENTIAL_WARNING)
162
+ end
163
+
176
164
  ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
177
165
  private_constant :ALLOWED_QUIET_OPS
178
166
 
@@ -227,8 +215,7 @@ module Dalli
227
215
 
228
216
  def connect
229
217
  @connection_manager.establish_connection
230
- authenticate_connection if require_auth?
231
- @version = version # Connect socket if not authed
218
+ @version = version
232
219
  up!
233
220
  end
234
221
 
@@ -156,7 +156,13 @@ module Dalli
156
156
  end
157
157
 
158
158
  def read(count)
159
- @sock.readfull(count)
159
+ # JRuby doesn't support IO#timeout=, so use custom readfull implementation
160
+ # CRuby 3.3+ has IO#timeout= which makes IO#read work with timeouts
161
+ if RUBY_ENGINE == 'jruby'
162
+ @sock.readfull(count)
163
+ else
164
+ @sock.read(count)
165
+ end
160
166
  rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
161
167
  error_on_request!(e)
162
168
  end
@@ -257,13 +257,9 @@ module Dalli
257
257
  @connection_manager.flush
258
258
  end
259
259
 
260
- def authenticate_connection
261
- raise Dalli::DalliError, 'Authentication not supported for the meta protocol.'
262
- end
263
-
264
- require_relative 'meta/key_regularizer'
265
- require_relative 'meta/request_formatter'
266
- require_relative 'meta/response_processor'
260
+ require_relative 'key_regularizer'
261
+ require_relative 'request_formatter'
262
+ require_relative 'response_processor'
267
263
  end
268
264
  end
269
265
  end
@@ -7,39 +7,38 @@ module Dalli
7
7
  module Protocol
8
8
  ##
9
9
  # Manages the buffer for responses from memcached.
10
- # Uses an offset-based approach to avoid string allocations
11
- # when advancing through parsed responses.
12
10
  ##
13
11
  class ResponseBuffer
14
- # Compact the buffer when the consumed portion exceeds this
15
- # threshold and represents more than half the buffer
16
- COMPACT_THRESHOLD = 4096
17
-
18
12
  def initialize(io_source, response_processor)
19
13
  @io_source = io_source
20
14
  @response_processor = response_processor
21
15
  @buffer = nil
22
- @offset = 0
23
16
  end
24
17
 
25
18
  def read
26
19
  @buffer << @io_source.read_nonblock
27
20
  end
28
21
 
29
- # Attempts to process a single response from the buffer,
30
- # advancing the offset past the consumed bytes.
22
+ # Attempts to process a single response from the buffer. Starts
23
+ # by advancing the buffer to the specified start position
31
24
  def process_single_getk_response
32
- bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer, @offset)
33
- @offset += bytes
34
- compact_if_needed
25
+ bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer)
26
+ advance(bytes)
35
27
  [status, cas, key, value]
36
28
  end
37
29
 
30
+ # Advances the internal response buffer by bytes_to_advance
31
+ # bytes. The
32
+ def advance(bytes_to_advance)
33
+ return unless bytes_to_advance.positive?
34
+
35
+ @buffer = @buffer.byteslice(bytes_to_advance..-1)
36
+ end
37
+
38
38
  # Resets the internal buffer to an empty state,
39
39
  # so that we're ready to read pipelined responses
40
40
  def reset
41
41
  @buffer = ''.b
42
- @offset = 0
43
42
  end
44
43
 
45
44
  # Ensures the buffer is initialized for reading without discarding
@@ -49,30 +48,16 @@ module Dalli
49
48
  return if in_progress?
50
49
 
51
50
  @buffer = ''.b
52
- @offset = 0
53
51
  end
54
52
 
55
53
  # Clear the internal response buffer
56
54
  def clear
57
55
  @buffer = nil
58
- @offset = 0
59
56
  end
60
57
 
61
58
  def in_progress?
62
59
  !@buffer.nil?
63
60
  end
64
-
65
- private
66
-
67
- # Only compact when we've consumed a significant portion of the buffer.
68
- # This avoids per-response string allocation while preventing unbounded
69
- # memory growth for large pipelines.
70
- def compact_if_needed
71
- return unless @offset > COMPACT_THRESHOLD && @offset > @buffer.bytesize / 2
72
-
73
- @buffer = @buffer.byteslice(@offset..)
74
- @offset = 0
75
- end
76
61
  end
77
62
  end
78
63
  end
@@ -147,6 +147,14 @@ module Dalli
147
147
  true
148
148
  end
149
149
 
150
+ def tokens_from_header_buffer(buf)
151
+ header = header_from_buffer(buf)
152
+ tokens = header.split
153
+ header_len = header.bytesize + TERMINATOR.length
154
+ body_len = body_len_from_tokens(tokens)
155
+ [tokens, header_len, body_len]
156
+ end
157
+
150
158
  def full_response_from_buffer(tokens, body, resp_size)
151
159
  value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
152
160
  [resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
@@ -162,15 +170,11 @@ module Dalli
162
170
  # The remaining three values in the array are the ResponseHeader,
163
171
  # key, and value.
164
172
  ##
165
- def getk_response_from_buffer(buf, offset = 0)
166
- # Find the header terminator starting from offset
167
- term_idx = buf.index(TERMINATOR, offset)
168
- return [0, nil, nil, nil, nil] unless term_idx
173
+ def getk_response_from_buffer(buf)
174
+ # There's no header in the buffer, so don't advance
175
+ return [0, nil, nil, nil, nil] unless contains_header?(buf)
169
176
 
170
- header = buf.byteslice(offset, term_idx - offset)
171
- tokens = header.split
172
- header_len = header.bytesize + TERMINATOR.length
173
- body_len = body_len_from_tokens(tokens)
177
+ tokens, header_len, body_len = tokens_from_header_buffer(buf)
174
178
 
175
179
  # We have a complete response that has no body.
176
180
  # This is either the response to the terminating
@@ -181,14 +185,22 @@ module Dalli
181
185
  resp_size = header_len + body_len + TERMINATOR.length
182
186
  # The header is in the buffer, but the body is not. As we don't have
183
187
  # a complete response, don't advance the buffer
184
- return [0, nil, nil, nil, nil] unless buf.bytesize >= offset + resp_size
188
+ return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
185
189
 
186
190
  # The full response is in our buffer, so parse it and return
187
191
  # the values
188
- body = buf.byteslice(offset + header_len, body_len)
192
+ body = buf.slice(header_len, body_len)
189
193
  full_response_from_buffer(tokens, body, resp_size)
190
194
  end
191
195
 
196
+ def contains_header?(buf)
197
+ buf.include?(TERMINATOR)
198
+ end
199
+
200
+ def header_from_buffer(buf)
201
+ buf.split(TERMINATOR, 2).first
202
+ end
203
+
192
204
  def error_on_unexpected!(expected_codes)
193
205
  tokens = next_line_to_tokens
194
206
 
@@ -6,7 +6,7 @@ module Dalli
6
6
  module Protocol
7
7
  ##
8
8
  # Dalli::Protocol::ServerConfigParser parses a server string passed to
9
- # a Dalli::Protocol::Binary instance into the hostname, port, weight, and
9
+ # a Dalli::Protocol::Meta instance into the hostname, port, weight, and
10
10
  # socket_type.
11
11
  ##
12
12
  class ServerConfigParser
data/lib/dalli/ring.rb CHANGED
@@ -23,9 +23,9 @@ module Dalli
23
23
 
24
24
  attr_accessor :servers, :continuum
25
25
 
26
- def initialize(servers_arg, protocol_implementation, options)
26
+ def initialize(servers_arg, options)
27
27
  @servers = servers_arg.map do |s|
28
- protocol_implementation.new(s, options)
28
+ Dalli::Protocol::Meta.new(s, options)
29
29
  end
30
30
  @continuum = nil
31
31
  @continuum = build_continuum(servers) if servers.size > 1
@@ -16,7 +16,7 @@ module Dalli
16
16
  # weight are optional (e.g. 'localhost', 'abc.com:12345', 'example.org:22222:3')
17
17
  # * A colon separated string of (UNIX socket, weight) where the weight is optional
18
18
  # (e.g. '/var/run/memcached/socket', '/tmp/xyz:3') (not supported on Windows)
19
- # * A URI with a 'memcached' protocol, which will typically include a username/password
19
+ # * A URI with a 'memcached' protocol (e.g. 'memcached://localhost:11211')
20
20
  #
21
21
  # The methods in this module do not validate the format of individual server strings, but
22
22
  # rather normalize the argument into a compact array, wherein each array entry corresponds
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.3'
4
+ VERSION = '5.0.0'
5
5
 
6
- MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
6
+ MIN_SUPPORTED_MEMCACHED_VERSION = '1.6'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -62,7 +62,6 @@ require_relative 'dalli/version'
62
62
  require_relative 'dalli/instrumentation'
63
63
 
64
64
  require_relative 'dalli/compressor'
65
- require_relative 'dalli/protocol_deprecations'
66
65
  require_relative 'dalli/client'
67
66
  require_relative 'dalli/key_manager'
68
67
  require_relative 'dalli/pipelined_getter'
@@ -71,7 +70,6 @@ require_relative 'dalli/pipelined_deleter'
71
70
  require_relative 'dalli/ring'
72
71
  require_relative 'dalli/protocol'
73
72
  require_relative 'dalli/protocol/base'
74
- require_relative 'dalli/protocol/binary'
75
73
  require_relative 'dalli/protocol/connection_manager'
76
74
  require_relative 'dalli/protocol/meta'
77
75
  require_relative 'dalli/protocol/response_buffer'