dalli 4.3.6 → 4.3.7
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 +4 -4
- data/CHANGELOG.md +30 -0
- data/lib/dalli/client.rb +27 -14
- data/lib/dalli/compressor.rb +5 -0
- data/lib/dalli/key_manager.rb +6 -3
- data/lib/dalli/protocol/base.rb +7 -4
- data/lib/dalli/protocol/binary/response_processor.rb +3 -2
- data/lib/dalli/protocol/binary.rb +2 -2
- data/lib/dalli/protocol/connection_manager.rb +30 -4
- data/lib/dalli/protocol/meta/response_processor.rb +32 -9
- data/lib/dalli/protocol/meta.rb +7 -6
- data/lib/dalli/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b1a68fbdf9ea89024d6e9bc5803f94d3342c2f7b8bd7cfea3a97f7a873f7b76
|
|
4
|
+
data.tar.gz: 93d1c2fcb71fa739e6a00808cddb11567ad9fed3caa4d0751c924576bc04bfe0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc42cde8b6f61ba60768bf43a9ebf8b1088dc8b2efbb4e32b5ed1abedd3c3be81c66aa02dc1a2558fd319debcc8074e61471996ad8825afed32d4fd741f8a312
|
|
7
|
+
data.tar.gz: 3a8320685a39edef196feaa387ef954caea41650b540c7f37d32d4821b5804f41631e5346e14b21912de0963f147c7d55e8c1ad08e24d2f7d25073975ba64d72
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,36 @@ Dalli Changelog
|
|
|
4
4
|
Unreleased
|
|
5
5
|
==========
|
|
6
6
|
|
|
7
|
+
4.3.7
|
|
8
|
+
==========
|
|
9
|
+
|
|
10
|
+
Security:
|
|
11
|
+
|
|
12
|
+
- Keep the caller's key when retrying a request (GHSA-m252-9cgf-vx2w)
|
|
13
|
+
- With a `namespace`, a request retried after a transient network error (a timeout, or a connection closed by memcached or a proxy) applied the namespace a second time, so a retried read could return a different key's value and a retried write could overwrite a different key
|
|
14
|
+
- Affects `get_with_metadata` and `fetch_with_lock` since 4.1.0 (single-key operations on 4.x aren't affected); fixed in 5.2.2, 5.1.4, 5.0.10 and 4.3.7. Clients without a namespace aren't affected
|
|
15
|
+
- Complete the fix for per-request `raw: true` on reads (GHSA-wr87-m4jw-29x5)
|
|
16
|
+
- `cas` and `cas!` (both protocols) and `fetch_with_lock` (meta protocol) accepted `raw: true` but didn't pass it to the read, so the value was still deserialized. They now honor it
|
|
17
|
+
- A read made with `raw: true` doesn't ask for flags, but a reply carrying them anyway (from a proxy or a hostile server) still had its value deserialized. Meta protocol raw reads now ignore flags in the reply
|
|
18
|
+
- Affects 4.3.6 and earlier; fixed in 5.2.2, 5.1.4, 5.0.10, 4.3.7 and 3.2.13
|
|
19
|
+
- Complete the fork-safety fix (GHSA-w39f-xq2m-4g8x)
|
|
20
|
+
- Fork detection used a pid cache refreshed by a `Process._fork` hook. When another library's fork hook ran first in the child (connection_pool, loaded before Dalli as in Rails, closes its connections there), Dalli didn't know it was in a forked child and ended the parent's TLS session. It now checks `Process.pid`
|
|
21
|
+
- 4.3.6's write buffer kept a reference to the caller's value string with the meta protocol, so changing the string before the buffer was sent (for example, reusing one buffer inside a `quiet` block) changed the bytes sent, losing values or adding commands. The buffer now holds a copy
|
|
22
|
+
- Fixed in 4.3.7 and 3.2.13
|
|
23
|
+
|
|
24
|
+
Bug fixes:
|
|
25
|
+
|
|
26
|
+
- `get_with_metadata` and `fetch_with_lock` retried the final error raised when a server is marked down, so with `down_retry_delay: 0` they retried an unresponsive server forever. They now retry only retryable errors, like other operations
|
|
27
|
+
- Fix a regression in 4.3.6: a `get_multi` that didn't finish within `socket_timeout` counted toward `socket_max_failures`, so two slow `get_multi` calls in a row marked a healthy server down. It now just closes the connection
|
|
28
|
+
- Fix a regression in 4.3.6: over TLS, every request buffered in a `quiet` block was sent as its own TLS record and system call (2.5 times slower for a block of 2000 deletes). Each flush is one write again
|
|
29
|
+
- Handle malformed replies from a broken or hostile server or proxy: a negative value size, a hit with no key, or a hit with no `s` flag could leave the connection out of step or raise a non-Dalli error
|
|
30
|
+
- With `decompressed_max_bytes` in effect, data after the end of a compressed value's stream was returned as part of the value. It's now ignored, as without the limit
|
|
31
|
+
- With a `digest_class` whose digests aren't short hex strings, shortening a long key could loop forever. It now raises `ArgumentError`
|
|
32
|
+
|
|
33
|
+
Development:
|
|
34
|
+
|
|
35
|
+
- Run the Tests, RuboCop and Profiles workflows on pushes to `main` and the `*-stable` branches only, so a pull request's branch isn't tested twice (backport of #1198)
|
|
36
|
+
|
|
7
37
|
4.3.6
|
|
8
38
|
==========
|
|
9
39
|
|
data/lib/dalli/client.rb
CHANGED
|
@@ -134,14 +134,18 @@ module Dalli
|
|
|
134
134
|
def get_with_metadata(key, options = {})
|
|
135
135
|
raise_unless_meta_protocol!
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
# A retry re-runs this method with its original arguments, so `key` must
|
|
138
|
+
# stay as the caller gave it; reassigning it would add the namespace again.
|
|
139
|
+
validated_key = @key_manager.validate_key(key.to_s)
|
|
139
140
|
|
|
140
|
-
server = ring.server_for_key(
|
|
141
|
-
Instrumentation.trace('get_with_metadata', trace_attrs('get_with_metadata',
|
|
142
|
-
server.request(:meta_get,
|
|
141
|
+
server = ring.server_for_key(validated_key)
|
|
142
|
+
Instrumentation.trace('get_with_metadata', trace_attrs('get_with_metadata', validated_key, server)) do
|
|
143
|
+
server.request(:meta_get, validated_key, options)
|
|
143
144
|
end
|
|
144
|
-
|
|
145
|
+
# Only a retryable error is retried. The NetworkError raised when a server
|
|
146
|
+
# is marked down is final, as in #perform; retrying it looped forever once
|
|
147
|
+
# down_retry_delay let the same server be tried again.
|
|
148
|
+
rescue RetryableNetworkError => e
|
|
145
149
|
Dalli.logger.debug { e.inspect }
|
|
146
150
|
Dalli.logger.debug { 'retrying get_with_metadata with new server' }
|
|
147
151
|
retry
|
|
@@ -238,14 +242,17 @@ module Dalli
|
|
|
238
242
|
validate_integer!(:lock_ttl, lock_ttl)
|
|
239
243
|
validate_integer!(:recache_threshold, recache_threshold)
|
|
240
244
|
|
|
241
|
-
|
|
242
|
-
|
|
245
|
+
# Left unchanged for the retry below; see get_with_metadata
|
|
246
|
+
validated_key = @key_manager.validate_key(key.to_s)
|
|
243
247
|
|
|
244
|
-
server = ring.server_for_key(
|
|
245
|
-
Instrumentation.trace('fetch_with_lock', trace_attrs('fetch_with_lock',
|
|
246
|
-
fetch_with_lock_request(
|
|
248
|
+
server = ring.server_for_key(validated_key)
|
|
249
|
+
Instrumentation.trace('fetch_with_lock', trace_attrs('fetch_with_lock', validated_key, server)) do
|
|
250
|
+
fetch_with_lock_request(validated_key, ttl, lock_ttl, recache_threshold, req_options, &block)
|
|
247
251
|
end
|
|
248
|
-
|
|
252
|
+
# Only a retryable error is retried. The NetworkError raised when a server
|
|
253
|
+
# is marked down is final, as in #perform; retrying it looped forever once
|
|
254
|
+
# down_retry_delay let the same server be tried again.
|
|
255
|
+
rescue RetryableNetworkError => e
|
|
249
256
|
Dalli.logger.debug { e.inspect }
|
|
250
257
|
Dalli.logger.debug { 'retrying fetch_with_lock with new server' }
|
|
251
258
|
retry
|
|
@@ -592,7 +599,8 @@ module Dalli
|
|
|
592
599
|
end
|
|
593
600
|
|
|
594
601
|
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
|
595
|
-
|
|
602
|
+
# req_options reaches the read too, so raw: true returns the stored bytes
|
|
603
|
+
(value, cas) = perform(:cas, key, req_options)
|
|
596
604
|
return if value.nil? && !always_set
|
|
597
605
|
|
|
598
606
|
newvalue = yield(value)
|
|
@@ -601,7 +609,12 @@ module Dalli
|
|
|
601
609
|
|
|
602
610
|
def fetch_with_lock_request(key, ttl, lock_ttl, recache_threshold, req_options)
|
|
603
611
|
server = ring.server_for_key(key)
|
|
604
|
-
|
|
612
|
+
# req_options (e.g. raw: true) reaches the read too. It's the base, not
|
|
613
|
+
# the override: fetch_with_lock's own lock_ttl/recache_threshold win.
|
|
614
|
+
meta_options = req_options.is_a?(Hash) ? req_options.dup : {}
|
|
615
|
+
meta_options[:vivify_ttl] = lock_ttl
|
|
616
|
+
meta_options[:recache_ttl] = recache_threshold
|
|
617
|
+
result = server.request(:meta_get, key, meta_options)
|
|
605
618
|
|
|
606
619
|
return result[:value] unless result[:won_recache]
|
|
607
620
|
|
data/lib/dalli/compressor.rb
CHANGED
|
@@ -39,7 +39,12 @@ module Dalli
|
|
|
39
39
|
out = String.new(encoding: Encoding::BINARY)
|
|
40
40
|
0.step(data.bytesize - 1, INFLATE_SLICE_BYTES) do |pos|
|
|
41
41
|
inflate_slice(inflater, data.byteslice(pos, INFLATE_SLICE_BYTES), out, max_bytes)
|
|
42
|
+
# Anything after the end of the stream isn't part of the value, as
|
|
43
|
+
# with Zlib::Inflate.inflate; finish would return it as output
|
|
44
|
+
break if inflater.finished?
|
|
42
45
|
end
|
|
46
|
+
return out if inflater.finished?
|
|
47
|
+
|
|
43
48
|
append_within_limit(out, inflater.finish, max_bytes)
|
|
44
49
|
ensure
|
|
45
50
|
inflater.close
|
data/lib/dalli/key_manager.rb
CHANGED
|
@@ -125,15 +125,18 @@ module Dalli
|
|
|
125
125
|
##
|
|
126
126
|
def truncated_key(key)
|
|
127
127
|
digest = digest_class.hexdigest(key)
|
|
128
|
-
prefix = key[0, prefix_length(digest)]
|
|
128
|
+
prefix = key[0, [prefix_length(digest), 0].max]
|
|
129
129
|
truncated = "#{prefix}#{TRUNCATED_KEY_SEPARATOR}#{digest}"
|
|
130
130
|
# A prefix with whitespace or non-ASCII characters sends the whole key
|
|
131
131
|
# base64-encoded, which can still be too long on the wire; shorten it.
|
|
132
|
-
while wire_length(truncated) > MAX_KEY_LENGTH
|
|
132
|
+
while wire_length(truncated) > MAX_KEY_LENGTH && !prefix.empty?
|
|
133
133
|
prefix = prefix[0...-1]
|
|
134
134
|
truncated = "#{prefix}#{TRUNCATED_KEY_SEPARATOR}#{digest}"
|
|
135
135
|
end
|
|
136
|
-
truncated
|
|
136
|
+
return truncated if wire_length(truncated) <= MAX_KEY_LENGTH
|
|
137
|
+
|
|
138
|
+
# Only possible with a digest_class whose digests aren't short hex strings
|
|
139
|
+
raise ArgumentError, "#{digest_class} digests are too long to fit in a truncated key"
|
|
137
140
|
end
|
|
138
141
|
|
|
139
142
|
# memcached's key limit applies to the key as sent: in bytes, and, with
|
data/lib/dalli/protocol/base.rb
CHANGED
|
@@ -152,10 +152,13 @@ module Dalli
|
|
|
152
152
|
@connection_manager.abort_request!
|
|
153
153
|
return true unless connected?
|
|
154
154
|
|
|
155
|
-
# Closes the connection, which ensures that our connection
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
# Closes the connection, which ensures that our connection is in a
|
|
156
|
+
# clean state for future requests. The get_multi ran out of time
|
|
157
|
+
# overall rather than hitting a socket error, so it doesn't count
|
|
158
|
+
# toward socket_max_failures: two slow get_multis in a row would
|
|
159
|
+
# otherwise mark a healthy server down.
|
|
160
|
+
Dalli.logger.warn { "#{name} didn't finish a get_multi within socket_timeout; closing the connection" }
|
|
161
|
+
@connection_manager.close
|
|
159
162
|
true
|
|
160
163
|
end
|
|
161
164
|
|
|
@@ -102,7 +102,8 @@ module Dalli
|
|
|
102
102
|
true
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
# raw: true returns the value as stored; see #get
|
|
106
|
+
def data_cas_response(raw: false)
|
|
106
107
|
resp_header, body = read_response
|
|
107
108
|
return [nil, resp_header.cas] if resp_header.not_found?
|
|
108
109
|
return [nil, false] if resp_header.not_stored?
|
|
@@ -110,7 +111,7 @@ module Dalli
|
|
|
110
111
|
raise_on_not_ok!(resp_header)
|
|
111
112
|
return [nil, resp_header.cas] unless body
|
|
112
113
|
|
|
113
|
-
[unpack_response_body(resp_header, body,
|
|
114
|
+
[unpack_response_body(resp_header, body, !raw).last, resp_header.cas]
|
|
114
115
|
end
|
|
115
116
|
|
|
116
117
|
# Returns the new value for the key, if found and updated
|
|
@@ -47,11 +47,11 @@ module Dalli
|
|
|
47
47
|
|
|
48
48
|
# TODO: This is confusing, as there's a cas command in memcached
|
|
49
49
|
# and this isn't it. Maybe rename? Maybe eliminate?
|
|
50
|
-
def cas(key)
|
|
50
|
+
def cas(key, options = nil)
|
|
51
51
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
|
52
52
|
write(req)
|
|
53
53
|
@connection_manager.flush
|
|
54
|
-
response_processor.data_cas_response
|
|
54
|
+
response_processor.data_cas_response(raw: raw_request?(options))
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# Storage Commands
|
|
@@ -58,7 +58,7 @@ module Dalli
|
|
|
58
58
|
@sock = memcached_socket
|
|
59
59
|
# Writes are buffered in @write_buffer instead; see WRITE_BUFFER_FLUSH_BYTES
|
|
60
60
|
@sock.sync = true
|
|
61
|
-
@pid =
|
|
61
|
+
@pid = Process.pid
|
|
62
62
|
@request_in_progress = false
|
|
63
63
|
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError, SocketError => e
|
|
64
64
|
# SocketError = DNS resolution failure
|
|
@@ -204,7 +204,10 @@ module Dalli
|
|
|
204
204
|
WRITE_BUFFER_FLUSH_BYTES = 64 * 1024
|
|
205
205
|
|
|
206
206
|
def write(bytes)
|
|
207
|
-
|
|
207
|
+
# A copy: the caller may change its string before the buffer is sent
|
|
208
|
+
# (the meta protocol writes a value string as its own part), as Ruby's
|
|
209
|
+
# IO buffer copied on write
|
|
210
|
+
@write_buffer << (bytes.frozen? ? bytes : bytes.dup)
|
|
208
211
|
@write_buffer_bytes += bytes.bytesize
|
|
209
212
|
flush_write_buffer if @write_buffer_bytes >= WRITE_BUFFER_FLUSH_BYTES
|
|
210
213
|
bytes.bytesize
|
|
@@ -282,7 +285,11 @@ module Dalli
|
|
|
282
285
|
end
|
|
283
286
|
|
|
284
287
|
def fork_detected?
|
|
285
|
-
|
|
288
|
+
# Process.pid rather than PIDCache: PIDCache is refreshed by a
|
|
289
|
+
# Process._fork hook, and another library's fork hook (such as
|
|
290
|
+
# connection_pool closing its connections) can run in the child before
|
|
291
|
+
# it, which would close the parent's TLS session as if it were ours.
|
|
292
|
+
@pid && @pid != Process.pid
|
|
286
293
|
end
|
|
287
294
|
|
|
288
295
|
def log_down_detected
|
|
@@ -306,13 +313,32 @@ module Dalli
|
|
|
306
313
|
|
|
307
314
|
private
|
|
308
315
|
|
|
316
|
+
# One write per flush. A TLS socket sends each argument of
|
|
317
|
+
# write(*parts) as its own record and system call, so the parts are
|
|
318
|
+
# joined first.
|
|
309
319
|
def flush_write_buffer
|
|
310
320
|
return if @write_buffer.empty?
|
|
311
321
|
|
|
312
|
-
@sock.write(
|
|
322
|
+
@sock.write(@write_buffer.size == 1 ? @write_buffer.first : joined_write_buffer)
|
|
313
323
|
discard_write_buffer
|
|
314
324
|
end
|
|
315
325
|
|
|
326
|
+
# Joined as bytes: requests can mix UTF-8 and binary strings, which
|
|
327
|
+
# String#<< refuses to combine when both hold non-ASCII bytes
|
|
328
|
+
if String.method_defined?(:append_as_bytes)
|
|
329
|
+
def joined_write_buffer
|
|
330
|
+
out = String.new(capacity: @write_buffer_bytes, encoding: Encoding::BINARY)
|
|
331
|
+
@write_buffer.each { |part| out.append_as_bytes(part) }
|
|
332
|
+
out
|
|
333
|
+
end
|
|
334
|
+
else
|
|
335
|
+
def joined_write_buffer
|
|
336
|
+
out = String.new(capacity: @write_buffer_bytes, encoding: Encoding::BINARY)
|
|
337
|
+
@write_buffer.each { |part| out << part.b }
|
|
338
|
+
out
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
316
342
|
def discard_write_buffer
|
|
317
343
|
@write_buffer.clear
|
|
318
344
|
@write_buffer_bytes = 0
|
|
@@ -28,22 +28,25 @@ module Dalli
|
|
|
28
28
|
@value_marshaller = value_marshaller
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
# raw: true (a request made with raw: true) returns the value as stored,
|
|
32
|
+
# ignoring any flags in the reply. The request didn't ask for flags, so
|
|
33
|
+
# a reply carrying them anyway must not get the value deserialized.
|
|
34
|
+
def meta_get_with_value(cache_nils: false, raw: false)
|
|
32
35
|
tokens = error_on_unexpected!([VA, EN, HD])
|
|
33
36
|
return cache_nils ? ::Dalli::NOT_FOUND : nil if tokens.first == EN
|
|
34
37
|
return true unless tokens.first == VA
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
retrieve(read_data(tokens[1].to_i), raw ? 0 : bitflags_from_tokens(tokens), raw)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
|
-
def meta_get_with_value_and_cas
|
|
42
|
+
def meta_get_with_value_and_cas(raw: false)
|
|
40
43
|
tokens = error_on_unexpected!([VA, EN, HD])
|
|
41
44
|
return [nil, 0] if tokens.first == EN
|
|
42
45
|
|
|
43
46
|
cas = cas_from_tokens(tokens)
|
|
44
47
|
return [nil, cas] unless tokens.first == VA
|
|
45
48
|
|
|
46
|
-
[
|
|
49
|
+
[retrieve(read_data(tokens[1].to_i), raw ? 0 : bitflags_from_tokens(tokens), raw), cas]
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def meta_get_without_value
|
|
@@ -62,12 +65,13 @@ module Dalli
|
|
|
62
65
|
#
|
|
63
66
|
# Used by meta_get for comprehensive metadata retrieval.
|
|
64
67
|
# Supports thundering herd protection (N/R flags) and metadata flags (h/l/u).
|
|
65
|
-
def meta_get_with_metadata(cache_nils: false, return_hit_status: false, return_last_access: false
|
|
68
|
+
def meta_get_with_metadata(cache_nils: false, return_hit_status: false, return_last_access: false,
|
|
69
|
+
raw: false)
|
|
66
70
|
tokens = error_on_unexpected!([VA, EN, HD])
|
|
67
71
|
result = build_metadata_result(tokens)
|
|
68
72
|
result[:hit_before] = hit_status_from_tokens(tokens) if return_hit_status
|
|
69
73
|
result[:last_access] = last_access_from_tokens(tokens) if return_last_access
|
|
70
|
-
result[:value] = parse_value_from_tokens(tokens, cache_nils)
|
|
74
|
+
result[:value] = parse_value_from_tokens(tokens, cache_nils, raw)
|
|
71
75
|
result
|
|
72
76
|
end
|
|
73
77
|
|
|
@@ -79,11 +83,16 @@ module Dalli
|
|
|
79
83
|
}
|
|
80
84
|
end
|
|
81
85
|
|
|
82
|
-
def parse_value_from_tokens(tokens, cache_nils)
|
|
86
|
+
def parse_value_from_tokens(tokens, cache_nils, raw = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
83
87
|
return cache_nils ? ::Dalli::NOT_FOUND : nil if tokens.first == EN
|
|
84
88
|
return unless tokens.first == VA
|
|
85
89
|
|
|
86
|
-
|
|
90
|
+
retrieve(read_data(tokens[1].to_i), raw ? 0 : bitflags_from_tokens(tokens), raw)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Values from a raw request are returned as stored
|
|
94
|
+
def retrieve(value, bitflags, raw)
|
|
95
|
+
raw ? value : @value_marshaller.retrieve(value, bitflags)
|
|
87
96
|
end
|
|
88
97
|
|
|
89
98
|
def meta_set_with_cas
|
|
@@ -148,6 +157,10 @@ module Dalli
|
|
|
148
157
|
end
|
|
149
158
|
|
|
150
159
|
def full_response_from_buffer(tokens, body, resp_size)
|
|
160
|
+
# A hit with no key can't be matched to a request: skip it (false
|
|
161
|
+
# status) rather than yield it, or take it for the end of the pipeline
|
|
162
|
+
return [resp_size, false, nil, nil, nil] unless tokens.any? { |t| t.start_with?('k') }
|
|
163
|
+
|
|
151
164
|
value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
|
|
152
165
|
[resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
|
|
153
166
|
end
|
|
@@ -197,6 +210,11 @@ module Dalli
|
|
|
197
210
|
# buffer wait for (and accumulate) that many bytes.
|
|
198
211
|
MAX_VALUE_BYTES = 1024 * 1024 * 1024
|
|
199
212
|
|
|
213
|
+
# The size given right after "VA", or 0 when it isn't a number
|
|
214
|
+
def positional_size(tokens)
|
|
215
|
+
tokens.first == VA && tokens[1]&.match?(/\A\d+\z/) ? tokens[1].to_i : 0
|
|
216
|
+
end
|
|
217
|
+
|
|
200
218
|
def check_value_size!(size)
|
|
201
219
|
return if size.between?(0, MAX_VALUE_BYTES)
|
|
202
220
|
|
|
@@ -248,7 +266,9 @@ module Dalli
|
|
|
248
266
|
end
|
|
249
267
|
|
|
250
268
|
def body_len_from_tokens(tokens)
|
|
251
|
-
size
|
|
269
|
+
# memcached also gives the size positionally ("VA <size> ..."); use it
|
|
270
|
+
# when there's no s flag rather than reading the value as replies
|
|
271
|
+
size = tokens.any? { |t| t.start_with?('s') } ? value_from_tokens(tokens, 's')&.to_i : positional_size(tokens)
|
|
252
272
|
check_value_size!(size) if size
|
|
253
273
|
size
|
|
254
274
|
end
|
|
@@ -270,6 +290,9 @@ module Dalli
|
|
|
270
290
|
end
|
|
271
291
|
|
|
272
292
|
def read_data(data_size)
|
|
293
|
+
# Checked before the terminator is added: -1 or -2 would otherwise
|
|
294
|
+
# read 1 or 0 bytes and leave the connection out of step
|
|
295
|
+
check_value_size!(data_size)
|
|
273
296
|
@io_source.read(data_size + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
|
|
274
297
|
end
|
|
275
298
|
end
|
data/lib/dalli/protocol/meta.rb
CHANGED
|
@@ -30,7 +30,7 @@ module Dalli
|
|
|
30
30
|
req = RequestFormatter.meta_get(key: encoded_key, base64: base64, skip_flags: skip_flags)
|
|
31
31
|
write(req)
|
|
32
32
|
@connection_manager.flush
|
|
33
|
-
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
|
33
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options), raw: raw_request?(options))
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def quiet_get_request(key)
|
|
@@ -47,7 +47,7 @@ module Dalli
|
|
|
47
47
|
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, base64: base64, skip_flags: skip_flags)
|
|
48
48
|
write(req)
|
|
49
49
|
@connection_manager.flush
|
|
50
|
-
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
|
50
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options), raw: raw_request?(options))
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def touch(key, ttl)
|
|
@@ -61,12 +61,13 @@ module Dalli
|
|
|
61
61
|
|
|
62
62
|
# TODO: This is confusing, as there's a cas command in memcached
|
|
63
63
|
# and this isn't it. Maybe rename? Maybe eliminate?
|
|
64
|
-
def cas(key)
|
|
64
|
+
def cas(key, options = nil)
|
|
65
65
|
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
66
|
-
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64
|
|
66
|
+
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64,
|
|
67
|
+
skip_flags: raw_request?(options))
|
|
67
68
|
write(req)
|
|
68
69
|
@connection_manager.flush
|
|
69
|
-
response_processor.meta_get_with_value_and_cas
|
|
70
|
+
response_processor.meta_get_with_value_and_cas(raw: raw_request?(options))
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
# Comprehensive meta get with support for all metadata flags.
|
|
@@ -105,7 +106,7 @@ module Dalli
|
|
|
105
106
|
@connection_manager.flush
|
|
106
107
|
response_processor.meta_get_with_metadata(
|
|
107
108
|
cache_nils: cache_nils?(options), return_hit_status: options[:return_hit_status],
|
|
108
|
-
return_last_access: options[:return_last_access]
|
|
109
|
+
return_last_access: options[:return_last_access], raw: raw_request?(options)
|
|
109
110
|
)
|
|
110
111
|
end
|
|
111
112
|
|
data/lib/dalli/version.rb
CHANGED