dalli 3.2.15 → 4.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.
@@ -22,7 +22,7 @@ module Dalli
22
22
  def get(key, options = nil)
23
23
  req = RequestFormatter.standard_request(opkey: :get, key: key)
24
24
  write(req)
25
- response_processor.get(cache_nils: cache_nils?(options), raw: raw_request?(options))
25
+ response_processor.get(cache_nils: cache_nils?(options))
26
26
  end
27
27
 
28
28
  def quiet_get_request(key)
@@ -33,7 +33,7 @@ module Dalli
33
33
  ttl = TtlSanitizer.sanitize(ttl)
34
34
  req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
35
35
  write(req)
36
- response_processor.get(cache_nils: cache_nils?(options), raw: raw_request?(options))
36
+ response_processor.get(cache_nils: cache_nils?(options))
37
37
  end
38
38
 
39
39
  def touch(key, ttl)
@@ -44,10 +44,10 @@ module Dalli
44
44
 
45
45
  # TODO: This is confusing, as there's a cas command in memcached
46
46
  # and this isn't it. Maybe rename? Maybe eliminate?
47
- def cas(key, options = nil)
47
+ def cas(key)
48
48
  req = RequestFormatter.standard_request(opkey: :get, key: key)
49
49
  write(req)
50
- response_processor.data_cas_response(raw: raw_request?(options))
50
+ response_processor.data_cas_response
51
51
  end
52
52
 
53
53
  # Storage Commands
@@ -119,6 +119,7 @@ module Dalli
119
119
  # if the key doesn't already exist, rather than
120
120
  # setting the initial value
121
121
  NOT_FOUND_EXPIRY = 0xFFFFFFFF
122
+ private_constant :NOT_FOUND_EXPIRY
122
123
 
123
124
  def decr_incr(opkey, key, count, ttl, initial)
124
125
  expiry = initial ? TtlSanitizer.sanitize(ttl) : NOT_FOUND_EXPIRY
@@ -139,10 +140,7 @@ module Dalli
139
140
  # We need to read all the responses at once.
140
141
  def noop
141
142
  write_noop
142
- result = response_processor.consume_all_responses_until_noop
143
- # Everything up to and including the noop reply has been read
144
- @connection_manager.quiet_responses_pending = false
145
- result
143
+ response_processor.consume_all_responses_until_noop
146
144
  end
147
145
 
148
146
  def stats(info = '')
@@ -28,7 +28,6 @@ module Dalli
28
28
 
29
29
  attr_accessor :hostname, :port, :socket_type, :options
30
30
  attr_reader :sock
31
- attr_writer :quiet_responses_pending
32
31
 
33
32
  def initialize(hostname, port, socket_type, client_options)
34
33
  @hostname = hostname
@@ -36,11 +35,9 @@ module Dalli
36
35
  @socket_type = socket_type
37
36
  @options = DEFAULTS.merge(client_options)
38
37
  @request_in_progress = false
39
- @quiet_responses_pending = false
40
38
  @sock = nil
41
39
  @pid = nil
42
40
 
43
- @fail_count = 0
44
41
  reset_down_info
45
42
  end
46
43
 
@@ -56,11 +53,10 @@ module Dalli
56
53
  Dalli.logger.debug { "Dalli::Server#connect #{name}" }
57
54
 
58
55
  @sock = memcached_socket
59
- @pid = Process.pid
56
+ @pid = PIDCache.pid
60
57
  @request_in_progress = false
61
- @quiet_responses_pending = false
62
- rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError, SocketError => e
63
- # SocketError = DNS resolution failure; SSL_ERRORS = failed TLS handshake
58
+ rescue SystemCallError, *TIMEOUT_ERRORS, EOFError, SocketError => e
59
+ # SocketError = DNS resolution failure
64
60
  error_on_request!(e)
65
61
  end
66
62
 
@@ -88,8 +84,6 @@ module Dalli
88
84
  def down!
89
85
  close
90
86
  log_down_detected
91
- # Once down_retry_delay passes, the server gets a full set of attempts
92
- @fail_count = 0
93
87
 
94
88
  @error = $ERROR_INFO&.class&.name
95
89
  @msg ||= $ERROR_INFO&.message
@@ -106,47 +100,32 @@ module Dalli
106
100
 
107
101
  def confirm_ready!
108
102
  close if request_in_progress?
109
- close_on_fork if fork_detected?
103
+ reconnect_on_fork if fork_detected?
110
104
  end
111
105
 
112
106
  def confirm_in_progress!
113
107
  raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless request_in_progress?
114
108
 
115
- close_on_fork if fork_detected?
109
+ reconnect_on_fork if fork_detected?
116
110
  end
117
111
 
118
112
  def close
119
113
  return unless @sock
120
114
 
121
115
  begin
122
- close_socket
116
+ @sock.close
123
117
  rescue StandardError
124
118
  nil
125
119
  end
126
120
  @sock = nil
127
121
  @pid = nil
128
- @quiet_responses_pending = false
129
122
  abort_request!
130
123
  end
131
124
 
132
- # A forked child shares the parent's connection, so it closes only its
133
- # own file descriptor. Closing the TLS socket itself would send
134
- # close_notify and end the parent's TLS session.
135
- def close_socket
136
- fork_detected? ? @sock.to_io.close : @sock.close
137
- end
138
-
139
125
  def connected?
140
126
  !@sock.nil?
141
127
  end
142
128
 
143
- # True when a quiet request was written on this connection and its
144
- # replies (memcached only suppresses some of them) haven't been
145
- # consumed by a noop yet.
146
- def quiet_responses_pending?
147
- @quiet_responses_pending
148
- end
149
-
150
129
  def request_in_progress?
151
130
  @request_in_progress
152
131
  end
@@ -161,50 +140,21 @@ module Dalli
161
140
  raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless @request_in_progress
162
141
 
163
142
  @request_in_progress = false
164
- # A completed request is what proves the server healthy again, so the
165
- # failure count resets here rather than on reconnect: a server that
166
- # accepts connections but never answers would otherwise reset it on
167
- # every retry and never reach socket_max_failures.
168
- @fail_count = 0
169
143
  end
170
144
 
171
145
  def abort_request!
172
146
  @request_in_progress = false
173
147
  end
174
148
 
175
- # The longest reply line Dalli reads, in bytes, CRLF included. A meta
176
- # reply header is at most about 500 bytes: "VA <size>" plus its flags,
177
- # the largest being a 250-byte key base64-encoded to 336 bytes, a
178
- # 20-digit CAS, a 32-byte opaque and a few numeric fields. The longest
179
- # stats line (stats settings) carries a file path such as ext_path, up
180
- # to PATH_MAX (4096). 8 KiB leaves room for both, while a server that
181
- # never sends CRLF can't grow the read buffer without bound.
182
- MAX_LINE_BYTES = 8 * 1024
183
-
184
149
  def read_line
185
- data = @sock.gets("\r\n", MAX_LINE_BYTES)
150
+ data = @sock.gets("\r\n")
186
151
  error_on_request!('EOF in read_line') if data.nil?
187
- # Without its CRLF the line either ran past MAX_LINE_BYTES or was cut
188
- # short by EOF; either way the connection is out of step
189
- error_on_request!("Reply line without CRLF in the first #{MAX_LINE_BYTES} bytes") unless data.end_with?("\r\n")
190
152
  data
191
153
  rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
192
154
  error_on_request!(e)
193
155
  end
194
156
 
195
- # memcached can't store an item larger than 1 GiB (its -I maximum), so a
196
- # reply claiming more (or a negative size) is malformed or hostile.
197
- # Reading allocates the full count up front, so check before reading.
198
- MAX_READ_BYTES = (1024 * 1024 * 1024) + 2 # plus the value's trailing "\r\n"
199
-
200
- def check_read_size!(count)
201
- return if count.between?(0, MAX_READ_BYTES)
202
-
203
- raise Dalli::DalliError, "Reply size #{count} from #{name} is out of range"
204
- end
205
-
206
157
  def read(count)
207
- check_read_size!(count)
208
158
  @sock.readfull(count)
209
159
  rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
210
160
  error_on_request!(e)
@@ -245,9 +195,8 @@ module Dalli
245
195
  raise Dalli::NetworkError, message
246
196
  end
247
197
 
248
- # Called on connect. Deliberately leaves @fail_count alone; see
249
- # finish_request!.
250
198
  def reset_down_info
199
+ @fail_count = 0
251
200
  @down_at = nil
252
201
  @last_down_at = nil
253
202
  @msg = nil
@@ -263,28 +212,22 @@ module Dalli
263
212
  end
264
213
 
265
214
  def log_warn_message(err_or_string)
266
- detail = err_or_string.is_a?(String) ? err_or_string : "#{err_or_string.class}: #{err_or_string.message}"
267
215
  Dalli.logger.warn do
268
216
  detail = err_or_string.is_a?(String) ? err_or_string : "#{err_or_string.class}: #{err_or_string.message}"
269
217
  "#{name} failed (count: #{@fail_count}) #{detail}"
270
218
  end
271
219
  end
272
220
 
273
- def close_on_fork
221
+ def reconnect_on_fork
274
222
  message = 'Fork detected, re-connecting child process...'
275
223
  Dalli.logger.info { message }
276
- # Closes the inherited socket without touching the parent's
277
- # connection, setting us up for reconnect on next request.
224
+ # Close socket on a fork and reconnect immediately
278
225
  close
279
- raise Dalli::NetworkError, message
226
+ establish_connection
280
227
  end
281
228
 
282
229
  def fork_detected?
283
- # Process.pid rather than PIDCache: PIDCache is refreshed by a
284
- # Process._fork hook, and another library's fork hook (such as
285
- # connection_pool closing its connections) can run in the child before
286
- # it, which would close the parent's TLS session as if it were ours.
287
- @pid && @pid != Process.pid
230
+ @pid && @pid != PIDCache.pid
288
231
  end
289
232
 
290
233
  def log_down_detected
@@ -10,7 +10,7 @@ module Dalli
10
10
  # memcached supports the use of base64 hashes for keys containing
11
11
  # whitespace or non-ASCII characters, provided the 'b' flag is included in the request.
12
12
  class KeyRegularizer
13
- WHITESPACE = /\s/.freeze
13
+ WHITESPACE = /\s/
14
14
 
15
15
  def self.encode(key)
16
16
  return [key, false] if key.ascii_only? && !WHITESPACE.match(key)
@@ -13,7 +13,6 @@ module Dalli
13
13
  # and introducing an intermediate object seems like overkill.
14
14
  #
15
15
  # rubocop:disable Metrics/CyclomaticComplexity
16
- # rubocop:disable Metrics/MethodLength
17
16
  # rubocop:disable Metrics/ParameterLists
18
17
  # rubocop:disable Metrics/PerceivedComplexity
19
18
  def self.meta_get(key:, value: true, return_cas: false, ttl: nil, base64: false, quiet: false)
@@ -21,7 +20,7 @@ module Dalli
21
20
  cmd << ' v f' if value
22
21
  cmd << ' c' if return_cas
23
22
  cmd << ' b' if base64
24
- cmd << " T#{integer_flag(:ttl, ttl)}" if ttl
23
+ cmd << " T#{ttl}" if ttl
25
24
  cmd << ' k q s' if quiet # Return the key in the response if quiet
26
25
  cmd + TERMINATOR
27
26
  end
@@ -32,19 +31,17 @@ module Dalli
32
31
  cmd << ' b' if base64
33
32
  cmd << " F#{bitflags}" if bitflags
34
33
  cmd << cas_string(cas)
35
- cmd << " T#{integer_flag(:ttl, ttl)}" if ttl
34
+ cmd << " T#{ttl}" if ttl
36
35
  cmd << " M#{mode_to_token(mode)}"
37
36
  cmd << ' q' if quiet
38
37
  cmd << TERMINATOR
39
- cmd << value
40
- cmd + TERMINATOR
41
38
  end
42
39
 
43
40
  def self.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false)
44
41
  cmd = "md #{key}"
45
42
  cmd << ' b' if base64
46
43
  cmd << cas_string(cas)
47
- cmd << " T#{integer_flag(:ttl, ttl)}" if ttl
44
+ cmd << " T#{ttl}" if ttl
48
45
  cmd << ' q' if quiet
49
46
  cmd + TERMINATOR
50
47
  end
@@ -52,17 +49,16 @@ module Dalli
52
49
  def self.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, base64: false, quiet: false)
53
50
  cmd = "ma #{key} v"
54
51
  cmd << ' b' if base64
55
- cmd << " D#{integer_flag(:delta, delta)}" if delta
56
- cmd << " J#{integer_flag(:initial, initial)}" if initial
52
+ cmd << " D#{delta}" if delta
53
+ cmd << " J#{initial}" if initial
57
54
  # Always set a TTL if an initial value is specified
58
- cmd << " N#{ttl ? integer_flag(:ttl, ttl) : 0}" if ttl || initial
55
+ cmd << " N#{ttl || 0}" if ttl || initial
59
56
  cmd << cas_string(cas)
60
57
  cmd << ' q' if quiet
61
58
  cmd << " M#{incr ? 'I' : 'D'}"
62
59
  cmd + TERMINATOR
63
60
  end
64
61
  # rubocop:enable Metrics/CyclomaticComplexity
65
- # rubocop:enable Metrics/MethodLength
66
62
  # rubocop:enable Metrics/ParameterLists
67
63
  # rubocop:enable Metrics/PerceivedComplexity
68
64
 
@@ -81,13 +77,16 @@ module Dalli
81
77
  cmd + TERMINATOR
82
78
  end
83
79
 
80
+ ALLOWED_STATS_ARGS = [nil, '', 'items', 'slabs', 'settings', 'reset'].freeze
81
+
84
82
  def self.stats(arg = nil)
83
+ raise ArgumentError, "Invalid stats argument: #{arg.inspect}" unless ALLOWED_STATS_ARGS.include?(arg)
84
+
85
85
  cmd = +'stats'
86
- cmd << " #{arg}" if arg
86
+ cmd << " #{arg}" if arg && !arg.empty?
87
87
  cmd + TERMINATOR
88
88
  end
89
89
 
90
- # rubocop:disable Metrics/MethodLength
91
90
  def self.mode_to_token(mode)
92
91
  case mode
93
92
  when :add
@@ -102,19 +101,6 @@ module Dalli
102
101
  'S'
103
102
  end
104
103
  end
105
- # rubocop:enable Metrics/MethodLength
106
-
107
- # Numeric flag values are written straight into the command line, so a
108
- # value that isn't an integer -- e.g. a String carrying CRLF -- would let
109
- # the caller inject further memcached commands (GHSA-6wmv-xq9m-fmp7).
110
- # Converting to Integer means only digits ever reach the wire. Strings
111
- # are parsed as base 10 so "010" means 10, as memcached would read it,
112
- # rather than octal 8.
113
- def self.integer_flag(name, value)
114
- value.is_a?(String) ? Integer(value, 10) : Integer(value)
115
- rescue ArgumentError, TypeError, FloatDomainError
116
- raise ArgumentError, "#{name} must be an Integer, got #{value.inspect}"
117
- end
118
104
 
119
105
  def self.cas_string(cas)
120
106
  cas = parse_to_64_bit_int(cas, nil)
@@ -21,33 +21,29 @@ module Dalli
21
21
  STAT = 'STAT'
22
22
  VA = 'VA'
23
23
  VERSION = 'VERSION'
24
+ SERVER_ERROR = 'SERVER_ERROR'
24
25
 
25
26
  def initialize(io_source, value_marshaller)
26
27
  @io_source = io_source
27
28
  @value_marshaller = value_marshaller
28
29
  end
29
30
 
30
- # raw: true returns the value as stored, without deserializing or
31
- # decompressing it, whatever flags it was stored with
32
- def meta_get_with_value(cache_nils: false, raw: false)
31
+ def meta_get_with_value(cache_nils: false)
33
32
  tokens = error_on_unexpected!([VA, EN, HD])
34
33
  return cache_nils ? ::Dalli::NOT_FOUND : nil if tokens.first == EN
35
34
  return true unless tokens.first == VA
36
35
 
37
- value = read_data(tokens[1].to_i)
38
- raw ? value : @value_marshaller.retrieve(value, bitflags_from_tokens(tokens))
36
+ @value_marshaller.retrieve(read_data(tokens[1].to_i), bitflags_from_tokens(tokens))
39
37
  end
40
38
 
41
- # raw: true returns the value as stored; see #meta_get_with_value
42
- def meta_get_with_value_and_cas(raw: false)
39
+ def meta_get_with_value_and_cas
43
40
  tokens = error_on_unexpected!([VA, EN, HD])
44
41
  return [nil, 0] if tokens.first == EN
45
42
 
46
43
  cas = cas_from_tokens(tokens)
47
44
  return [nil, cas] unless tokens.first == VA
48
45
 
49
- value = read_data(tokens[1].to_i)
50
- [raw ? value : @value_marshaller.retrieve(value, bitflags_from_tokens(tokens)), cas]
46
+ [@value_marshaller.retrieve(read_data(tokens[1].to_i), bitflags_from_tokens(tokens)), cas]
51
47
  end
52
48
 
53
49
  def meta_get_without_value
@@ -125,10 +121,6 @@ module Dalli
125
121
  end
126
122
 
127
123
  def full_response_from_buffer(tokens, body, resp_size)
128
- # A hit with no key can't be matched to a request: skip it (false
129
- # status) rather than yield it, or take it for the end of the pipeline
130
- return [resp_size, false, nil, nil, nil] unless tokens.any? { |t| t.start_with?('k') }
131
-
132
124
  value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
133
125
  [resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
134
126
  end
@@ -149,14 +141,11 @@ module Dalli
149
141
 
150
142
  tokens, header_len, body_len = tokens_from_header_buffer(buf)
151
143
 
152
- # A complete response with no body. Only the terminating noop's MN
153
- # ends the pipeline. Any other bodyless reply (CLIENT_ERROR,
154
- # SERVER_ERROR, ...) answers one key's request: report it with a
155
- # false status so the caller skips it and keeps reading. Treating it
156
- # as the end would leave the remaining replies on the connection for
157
- # later commands to read as their own. A hit on an empty value (VA 0)
158
- # still has a body -- just its terminator -- so it's parsed below.
159
- return [header_len, tokens.first == MN, nil, nil, nil] if no_body?(tokens, body_len)
144
+ # We have a complete response that has no body.
145
+ # This is either the response to the terminating
146
+ # noop or, if the status is not MN, an intermediate
147
+ # error response that needs to be discarded.
148
+ return [header_len, true, nil, nil, nil] if body_len.zero?
160
149
 
161
150
  resp_size = header_len + body_len + TERMINATOR.length
162
151
  # The header is in the buffer, but the body is not. As we don't have
@@ -169,27 +158,6 @@ module Dalli
169
158
  full_response_from_buffer(tokens, body, resp_size)
170
159
  end
171
160
 
172
- # memcached can't store an item larger than 1 GiB (its -I maximum). A
173
- # pipelined reply claiming an impossible size would otherwise have the
174
- # buffer wait for (and accumulate) that many bytes.
175
- MAX_VALUE_BYTES = 1024 * 1024 * 1024
176
-
177
- # The size given right after "VA", or 0 when it isn't a number
178
- def positional_size(tokens)
179
- tokens.first == VA && tokens[1]&.match?(/\A\d+\z/) ? tokens[1].to_i : 0
180
- end
181
-
182
- def check_value_size!(size)
183
- return if size.between?(0, MAX_VALUE_BYTES)
184
-
185
- raise Dalli::DalliError, "Reply value size #{size} is out of range"
186
- end
187
-
188
- # A zero-size reply has no body unless it's a VA (a hit on an empty value)
189
- def no_body?(tokens, body_len)
190
- body_len.zero? && tokens.first != VA
191
- end
192
-
193
161
  def contains_header?(buf)
194
162
  buf.include?(TERMINATOR)
195
163
  end
@@ -200,9 +168,12 @@ module Dalli
200
168
 
201
169
  def error_on_unexpected!(expected_codes)
202
170
  tokens = next_line_to_tokens
203
- raise Dalli::DalliError, "Response error: #{tokens.first}" unless expected_codes.include?(tokens.first)
204
171
 
205
- tokens
172
+ return tokens if expected_codes.include?(tokens.first)
173
+
174
+ raise Dalli::ServerError, tokens.join(' ').to_s if tokens.first == SERVER_ERROR
175
+
176
+ raise Dalli::DalliError, "Response error: #{tokens.first}"
206
177
  end
207
178
 
208
179
  def bitflags_from_tokens(tokens)
@@ -220,11 +191,7 @@ module Dalli
220
191
  end
221
192
 
222
193
  def body_len_from_tokens(tokens)
223
- # memcached also gives the size positionally ("VA <size> ..."); use it
224
- # when there's no s flag rather than reading the value as replies
225
- size = tokens.any? { |t| t.start_with?('s') } ? value_from_tokens(tokens, 's')&.to_i : positional_size(tokens)
226
- check_value_size!(size) if size
227
- size
194
+ value_from_tokens(tokens, 's')&.to_i
228
195
  end
229
196
 
230
197
  def value_from_tokens(tokens, flag)
@@ -243,12 +210,7 @@ module Dalli
243
210
  line&.split || []
244
211
  end
245
212
 
246
- # Values are read by the size in the VA line, not up to the next CRLF:
247
- # a value can contain CRLF and can be longer than MAX_LINE_BYTES
248
213
  def read_data(data_size)
249
- # Checked before the terminator is added: -1 or -2 would otherwise
250
- # read 1 or 0 bytes and leave the connection out of step
251
- check_value_size!(data_size)
252
214
  @io_source.read(data_size + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
253
215
  end
254
216
  end
@@ -13,7 +13,6 @@ module Dalli
13
13
  ##
14
14
  class Meta < Base
15
15
  TERMINATOR = "\r\n"
16
- COUNTER_ADD_OPTIONS = { raw: true }.freeze # counters are stored raw
17
16
 
18
17
  def response_processor
19
18
  @response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
@@ -28,7 +27,7 @@ module Dalli
28
27
  encoded_key, base64 = KeyRegularizer.encode(key)
29
28
  req = RequestFormatter.meta_get(key: encoded_key, base64: base64)
30
29
  write(req)
31
- response_processor.meta_get_with_value(cache_nils: cache_nils?(options), raw: raw_request?(options))
30
+ response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
32
31
  end
33
32
 
34
33
  def quiet_get_request(key)
@@ -41,7 +40,7 @@ module Dalli
41
40
  encoded_key, base64 = KeyRegularizer.encode(key)
42
41
  req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, base64: base64)
43
42
  write(req)
44
- response_processor.meta_get_with_value(cache_nils: cache_nils?(options), raw: raw_request?(options))
43
+ response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
45
44
  end
46
45
 
47
46
  def touch(key, ttl)
@@ -54,11 +53,11 @@ module Dalli
54
53
 
55
54
  # TODO: This is confusing, as there's a cas command in memcached
56
55
  # and this isn't it. Maybe rename? Maybe eliminate?
57
- def cas(key, options = nil)
56
+ def cas(key)
58
57
  encoded_key, base64 = KeyRegularizer.encode(key)
59
58
  req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
60
59
  write(req)
61
- response_processor.meta_get_with_value_and_cas(raw: raw_request?(options))
60
+ response_processor.meta_get_with_value_and_cas
62
61
  end
63
62
 
64
63
  # Storage Commands
@@ -86,6 +85,8 @@ module Dalli
86
85
  bitflags: bitflags, cas: cas,
87
86
  ttl: ttl, mode: mode, quiet: quiet?, base64: base64)
88
87
  write(req)
88
+ write(value)
89
+ write(TERMINATOR)
89
90
  end
90
91
  # rubocop:enable Metrics/ParameterLists
91
92
 
@@ -106,6 +107,8 @@ module Dalli
106
107
  req = RequestFormatter.meta_set(key: encoded_key, value: value, base64: base64,
107
108
  cas: cas, ttl: ttl, mode: mode, quiet: quiet?)
108
109
  write(req)
110
+ write(value)
111
+ write(TERMINATOR)
109
112
  end
110
113
  # rubocop:enable Metrics/ParameterLists
111
114
 
@@ -130,42 +133,11 @@ module Dalli
130
133
  def decr_incr(incr, key, delta, ttl, initial)
131
134
  ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
132
135
  encoded_key, base64 = KeyRegularizer.encode(key)
133
- # memcached stores an item created by ma's auto-create (N/J) for a
134
- # base64-encoded key without its binary-key flag, and later replies
135
- # name it with the raw key bytes. Create such counters with an add.
136
- return decr_incr_with_add(incr, key, delta, ttl, initial) if initial && base64
137
-
138
136
  write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: initial, incr: incr, ttl: ttl,
139
137
  quiet: quiet?, base64: base64))
140
138
  response_processor.decr_incr unless quiet?
141
139
  end
142
140
 
143
- # Like decr_incr with an initial value, without memcached's auto-create:
144
- # on a miss the counter is added with the initial value (which is
145
- # returned, as auto-create would), and the add losing a race to another
146
- # client means the counter now exists, so the incr is tried again. In a
147
- # quiet block both requests are sent and whichever applies takes effect.
148
- def decr_incr_with_add(incr, key, delta, ttl, initial)
149
- return quiet_decr_incr_with_add(incr, key, delta, ttl, initial) if quiet?
150
-
151
- 2.times do
152
- value = decr_incr(incr, key, delta, nil, nil)
153
- return value unless value.nil?
154
-
155
- write_storage_req(:add, key, initial.to_s, ttl, nil, COUNTER_ADD_OPTIONS)
156
- return initial if response_processor.meta_set_with_cas
157
- end
158
- decr_incr(incr, key, delta, nil, nil)
159
- end
160
-
161
- def quiet_decr_incr_with_add(incr, key, delta, ttl, initial)
162
- encoded_key, base64 = KeyRegularizer.encode(key)
163
- write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: nil, incr: incr, ttl: nil,
164
- quiet: true, base64: base64))
165
- # Sent with the rest of the quiet block, by its closing noop
166
- write_storage_req(:add, key, initial.to_s, ttl, nil, COUNTER_ADD_OPTIONS)
167
- end
168
-
169
141
  # Other Commands
170
142
  def flush(delay = 0)
171
143
  write(RequestFormatter.flush(delay: delay))
@@ -176,10 +148,7 @@ module Dalli
176
148
  # We need to read all the responses at once.
177
149
  def noop
178
150
  write_noop
179
- result = response_processor.consume_all_responses_until_mn
180
- # Everything up to and including the MN has been read
181
- @connection_manager.quiet_responses_pending = false
182
- result
151
+ response_processor.consume_all_responses_until_mn
183
152
  end
184
153
 
185
154
  def stats(info = nil)
@@ -16,7 +16,7 @@ module Dalli
16
16
  # can limit character set to LDH + '.'. Hex digit section
17
17
  # is there to support IPv6 addresses, which need to be specified with
18
18
  # a bounding []
19
- SERVER_CONFIG_REGEXP = /\A(\[([\h:]+)\]|[^:]+)(?::(\d+))?(?::(\d+))?\z/.freeze
19
+ SERVER_CONFIG_REGEXP = /\A(\[([\h:]+)\]|[^:]+)(?::(\d+))?(?::(\d+))?\z/
20
20
 
21
21
  DEFAULT_PORT = 11_211
22
22
  DEFAULT_WEIGHT = 1
@@ -31,7 +31,7 @@ module Dalli
31
31
  return ttl_as_i if ttl_as_i > now # Already a timestamp
32
32
 
33
33
  Dalli.logger.debug "Expiration interval (#{ttl_as_i}) too long for Memcached " \
34
- 'and too short to be a future timestamp,' \
34
+ 'and too short to be a future timestamp, ' \
35
35
  'converting to an expiration timestamp'
36
36
  now + ttl_as_i
37
37
  end