dalli 3.2.12 → 3.2.13
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 +17 -0
- data/lib/dalli/client.rb +2 -1
- data/lib/dalli/compressor.rb +5 -0
- data/lib/dalli/key_manager.rb +6 -3
- data/lib/dalli/protocol/base.rb +10 -7
- 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 +6 -2
- data/lib/dalli/protocol/meta/response_processor.rb +16 -3
- data/lib/dalli/protocol/meta.rb +2 -2
- 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: 7f7bcbe37e631c3c733d1487e519501497702b873e81e6588d6dfad1ab96a65b
|
|
4
|
+
data.tar.gz: d7a953d2c0052334d8edb7fa45306724ffa25cec64436dcceccc9847d057e8bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d7b0ed94102d333f611736c48d944b0f1010760ae646003db60cd02c90b1a047a5fd10f7a5ef077527a88594f8844cbe08f9647259749e78ade74d09d2e851b
|
|
7
|
+
data.tar.gz: bf86e4604fc18eaf2bdf57d6abad82258745238112a9866e5da9e05c0f45b44d62f82ebe8b5519ecc83d00ec6ee94e2d11bb931aa14171081a0b731745633e6d
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ Dalli Changelog
|
|
|
4
4
|
Unreleased
|
|
5
5
|
==========
|
|
6
6
|
|
|
7
|
+
3.2.13
|
|
8
|
+
==========
|
|
9
|
+
|
|
10
|
+
- Complete the fix for per-request `raw: true` on reads (GHSA-wr87-m4jw-29x5)
|
|
11
|
+
- `cas` and `cas!` accepted `raw: true` but didn't pass it to the read, so the value was still deserialized. They now honor it, in both protocols
|
|
12
|
+
- Affects 3.2.12 and earlier; fixed in 5.2.2, 5.1.4, 5.0.10, 4.3.7 and 3.2.13
|
|
13
|
+
- Complete the fork-safety fix (GHSA-w39f-xq2m-4g8x)
|
|
14
|
+
- 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`
|
|
15
|
+
- Fixed in 4.3.7 and 3.2.13
|
|
16
|
+
|
|
17
|
+
- Fix a regression in 3.2.12: 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
|
|
18
|
+
- Handle malformed pipelined replies from a broken or hostile server or proxy: a hit with no key, or with no `s` flag, could leave the connection out of step or raise an error
|
|
19
|
+
- 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
|
|
20
|
+
- With a `digest_class` whose digests aren't short hex strings, shortening a long key could loop forever. It now raises `ArgumentError`
|
|
21
|
+
|
|
22
|
+
- Run the Tests and RuboCop workflows on pushes to `main` and the `*-stable` branches only, so a pull request's branch isn't tested twice (backport of #1198)
|
|
23
|
+
|
|
7
24
|
3.2.12
|
|
8
25
|
==========
|
|
9
26
|
|
data/lib/dalli/client.rb
CHANGED
|
@@ -388,7 +388,8 @@ module Dalli
|
|
|
388
388
|
end
|
|
389
389
|
|
|
390
390
|
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
|
391
|
-
|
|
391
|
+
# req_options reaches the read too, so raw: true returns the stored bytes
|
|
392
|
+
(value, cas) = perform(:cas, key, req_options)
|
|
392
393
|
return if value.nil? && !always_set
|
|
393
394
|
|
|
394
395
|
newvalue = yield(value)
|
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
|
@@ -110,15 +110,18 @@ module Dalli
|
|
|
110
110
|
##
|
|
111
111
|
def truncated_key(key)
|
|
112
112
|
digest = digest_class.hexdigest(key)
|
|
113
|
-
prefix = key[0, prefix_length(digest)]
|
|
113
|
+
prefix = key[0, [prefix_length(digest), 0].max]
|
|
114
114
|
truncated = "#{prefix}#{TRUNCATED_KEY_SEPARATOR}#{digest}"
|
|
115
115
|
# A prefix with whitespace or non-ASCII characters sends the whole key
|
|
116
116
|
# base64-encoded, which can still be too long on the wire; shorten it.
|
|
117
|
-
while wire_length(truncated) > MAX_KEY_LENGTH
|
|
117
|
+
while wire_length(truncated) > MAX_KEY_LENGTH && !prefix.empty?
|
|
118
118
|
prefix = prefix[0...-1]
|
|
119
119
|
truncated = "#{prefix}#{TRUNCATED_KEY_SEPARATOR}#{digest}"
|
|
120
120
|
end
|
|
121
|
-
truncated
|
|
121
|
+
return truncated if wire_length(truncated) <= MAX_KEY_LENGTH
|
|
122
|
+
|
|
123
|
+
# Only possible with a digest_class whose digests aren't short hex strings
|
|
124
|
+
raise ArgumentError, "#{digest_class} digests are too long to fit in a truncated key"
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
# memcached's key limit applies to the key as sent: in bytes, and, with
|
data/lib/dalli/protocol/base.rb
CHANGED
|
@@ -124,13 +124,16 @@ module Dalli
|
|
|
124
124
|
def pipeline_abort
|
|
125
125
|
response_buffer.clear
|
|
126
126
|
@connection_manager.abort_request!
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
# Closes the connection, which ensures that our connection
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
return unless connected?
|
|
128
|
+
|
|
129
|
+
# Closes the connection, which ensures that our connection is in a
|
|
130
|
+
# clean state for future requests. The get_multi ran out of time
|
|
131
|
+
# overall rather than hitting a socket error, so it doesn't count
|
|
132
|
+
# toward socket_max_failures: two slow get_multis in a row would
|
|
133
|
+
# otherwise mark a healthy server down.
|
|
134
|
+
Dalli.logger.warn { "#{name} didn't finish a get_multi within socket_timeout; closing the connection" }
|
|
135
|
+
@connection_manager.close
|
|
136
|
+
nil
|
|
134
137
|
end
|
|
135
138
|
|
|
136
139
|
# Did the last call to #pipeline_response_setup complete successfully?
|
|
@@ -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
|
|
@@ -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)
|
|
47
|
+
def cas(key, options = nil)
|
|
48
48
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
|
49
49
|
write(req)
|
|
50
|
-
response_processor.data_cas_response
|
|
50
|
+
response_processor.data_cas_response(raw: raw_request?(options))
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# Storage Commands
|
|
@@ -54,7 +54,7 @@ module Dalli
|
|
|
54
54
|
Dalli.logger.debug { "Dalli::Server#connect #{name}" }
|
|
55
55
|
|
|
56
56
|
@sock = memcached_socket
|
|
57
|
-
@pid =
|
|
57
|
+
@pid = Process.pid
|
|
58
58
|
@request_in_progress = false
|
|
59
59
|
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError, SocketError => e
|
|
60
60
|
# SocketError = DNS resolution failure
|
|
@@ -257,7 +257,11 @@ module Dalli
|
|
|
257
257
|
end
|
|
258
258
|
|
|
259
259
|
def fork_detected?
|
|
260
|
-
|
|
260
|
+
# Process.pid rather than PIDCache: PIDCache is refreshed by a
|
|
261
|
+
# Process._fork hook, and another library's fork hook (such as
|
|
262
|
+
# connection_pool closing its connections) can run in the child before
|
|
263
|
+
# it, which would close the parent's TLS session as if it were ours.
|
|
264
|
+
@pid && @pid != Process.pid
|
|
261
265
|
end
|
|
262
266
|
|
|
263
267
|
def log_down_detected
|
|
@@ -38,14 +38,16 @@ module Dalli
|
|
|
38
38
|
raw ? value : @value_marshaller.retrieve(value, bitflags_from_tokens(tokens))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
# raw: true returns the value as stored; see #meta_get_with_value
|
|
42
|
+
def meta_get_with_value_and_cas(raw: false)
|
|
42
43
|
tokens = error_on_unexpected!([VA, EN, HD])
|
|
43
44
|
return [nil, 0] if tokens.first == EN
|
|
44
45
|
|
|
45
46
|
cas = cas_from_tokens(tokens)
|
|
46
47
|
return [nil, cas] unless tokens.first == VA
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
value = read_line
|
|
50
|
+
[raw ? value : @value_marshaller.retrieve(value, bitflags_from_tokens(tokens)), cas]
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def meta_get_without_value
|
|
@@ -123,6 +125,10 @@ module Dalli
|
|
|
123
125
|
end
|
|
124
126
|
|
|
125
127
|
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
|
+
|
|
126
132
|
value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
|
|
127
133
|
[resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
|
|
128
134
|
end
|
|
@@ -168,6 +174,11 @@ module Dalli
|
|
|
168
174
|
# buffer wait for (and accumulate) that many bytes.
|
|
169
175
|
MAX_VALUE_BYTES = 1024 * 1024 * 1024
|
|
170
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
|
+
|
|
171
182
|
def check_value_size!(size)
|
|
172
183
|
return if size.between?(0, MAX_VALUE_BYTES)
|
|
173
184
|
|
|
@@ -209,7 +220,9 @@ module Dalli
|
|
|
209
220
|
end
|
|
210
221
|
|
|
211
222
|
def body_len_from_tokens(tokens)
|
|
212
|
-
size
|
|
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)
|
|
213
226
|
check_value_size!(size) if size
|
|
214
227
|
size
|
|
215
228
|
end
|
data/lib/dalli/protocol/meta.rb
CHANGED
|
@@ -53,11 +53,11 @@ module Dalli
|
|
|
53
53
|
|
|
54
54
|
# TODO: This is confusing, as there's a cas command in memcached
|
|
55
55
|
# and this isn't it. Maybe rename? Maybe eliminate?
|
|
56
|
-
def cas(key)
|
|
56
|
+
def cas(key, options = nil)
|
|
57
57
|
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
58
58
|
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
|
|
59
59
|
write(req)
|
|
60
|
-
response_processor.meta_get_with_value_and_cas
|
|
60
|
+
response_processor.meta_get_with_value_and_cas(raw: raw_request?(options))
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Storage Commands
|
data/lib/dalli/version.rb
CHANGED