dalli 3.2.12 → 3.2.14
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 +26 -0
- data/README.md +4 -0
- data/lib/dalli/client.rb +7 -1
- data/lib/dalli/compressor.rb +5 -0
- data/lib/dalli/key_manager.rb +6 -3
- data/lib/dalli/protocol/base.rb +58 -8
- 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 +34 -2
- data/lib/dalli/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b52d92d26a7277b6f65b402c082be2f99b98ca9cb7596af709ac70848167a53
|
|
4
|
+
data.tar.gz: 2cc59e69e536f07ec79ec6e37ffcae997c1e47231aaa213e32fdce02b3b3e123
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8db03f1265942f604b68bdded36f70aa00e859269950367df91c62ef1b2d1011ba6166269125f4ab377c4b4acd4acfd763c3f17a867d2e357479b259c0592ffc
|
|
7
|
+
data.tar.gz: 7f15a910062c45a776e3bc3964a3555b4a3c0c0a5897877dc170ee5a80d16a313bb4eb0da64821ca14d930ffbea1445855e2f1ca7fe2a068a33765aabc064636
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ Dalli Changelog
|
|
|
4
4
|
Unreleased
|
|
5
5
|
==========
|
|
6
6
|
|
|
7
|
+
3.2.14
|
|
8
|
+
==========
|
|
9
|
+
|
|
10
|
+
- Don't rely on memcached's auto-create for keys that need base64 encoding (GHSA-r47x-6p6r-mvcp)
|
|
11
|
+
- memcached stores an item made by auto-create (`ma` with `N`/`J`, or `mg` with `N`) for a base64-encoded key without its binary-key flag, and later replies name the item by its raw key bytes. A key containing spaces or CR/LF could then make a multi-get read the replies after it as different ones
|
|
12
|
+
- `incr` and `decr` with an initial value now create such counters with an add instead. Return values are unchanged
|
|
13
|
+
- The new `verify_reply_keys` option makes `get_multi` ignore replies for keys they didn't request, which guards against items created by older Dalli versions or other clients until memcached is upgraded. It's off by default, since it costs a multi-get about 15%
|
|
14
|
+
- Affects `protocol: :meta` since 3.2.0; fixed in 5.2.3, 5.1.5, 5.0.11, 4.3.8 and 3.2.14. memcached 1.6.45 and earlier still name these items by their raw key bytes
|
|
15
|
+
|
|
16
|
+
3.2.13
|
|
17
|
+
==========
|
|
18
|
+
|
|
19
|
+
- Complete the fix for per-request `raw: true` on reads (GHSA-wr87-m4jw-29x5)
|
|
20
|
+
- `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
|
|
21
|
+
- Affects 3.2.12 and earlier; fixed in 5.2.2, 5.1.4, 5.0.10, 4.3.7 and 3.2.13
|
|
22
|
+
- Complete the fork-safety fix (GHSA-w39f-xq2m-4g8x)
|
|
23
|
+
- 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`
|
|
24
|
+
- Fixed in 4.3.7 and 3.2.13
|
|
25
|
+
|
|
26
|
+
- 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
|
|
27
|
+
- 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
|
|
28
|
+
- 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
|
|
29
|
+
- With a `digest_class` whose digests aren't short hex strings, shortening a long key could loop forever. It now raises `ArgumentError`
|
|
30
|
+
|
|
31
|
+
- 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)
|
|
32
|
+
|
|
7
33
|
3.2.12
|
|
8
34
|
==========
|
|
9
35
|
|
data/README.md
CHANGED
|
@@ -37,6 +37,10 @@ Dalli::Client.new('localhost:11211', serializer: Dalli::JSONSerializer)
|
|
|
37
37
|
|
|
38
38
|
Per-request `raw: true` returns the stored bytes without deserializing or decompressing them, on every read method that accepts it (`get` and `fetch`).
|
|
39
39
|
|
|
40
|
+
### Counters and verify_reply_keys
|
|
41
|
+
|
|
42
|
+
memcached names an item made by auto-create for a base64-encoded key (one with whitespace, control or non-ASCII characters) by its raw key bytes in later replies (with `protocol: :meta`). Since 3.2.14, Dalli creates counters for such keys with an add rather than auto-create. Items created before then, or by other clients, can still be named that way until memcached releases a fix (1.6.45 and earlier behave this way). If keys passed to `incr` or `decr` with an initial value can contain untrusted input, set `verify_reply_keys: true` until then: multi-gets then ignore replies for keys they didn't request, at a cost of about 15% per multi-get.
|
|
43
|
+
|
|
40
44
|
## Development
|
|
41
45
|
|
|
42
46
|
After checking out the repo, run `bin/setup` to install dependencies. You can run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/dalli/client.rb
CHANGED
|
@@ -45,6 +45,11 @@ module Dalli
|
|
|
45
45
|
# useful for injecting a FIPS compliant hash object.
|
|
46
46
|
# - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
|
|
47
47
|
# to communicate with memcached.
|
|
48
|
+
# - :verify_reply_keys - defaults to false. When true, get_multi only accepts replies for the keys it requested;
|
|
49
|
+
# after a reply naming another key, the rest of that server's replies are treated as misses.
|
|
50
|
+
# With the meta protocol, memcached can name an item by its raw key bytes when the item was
|
|
51
|
+
# created by auto-create from a base64-encoded key, which Dalli no longer does. Costs a
|
|
52
|
+
# multi-get about 15%.
|
|
48
53
|
#
|
|
49
54
|
def initialize(servers = nil, options = {})
|
|
50
55
|
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
|
@@ -388,7 +393,8 @@ module Dalli
|
|
|
388
393
|
end
|
|
389
394
|
|
|
390
395
|
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
|
391
|
-
|
|
396
|
+
# req_options reaches the read too, so raw: true returns the stored bytes
|
|
397
|
+
(value, cas) = perform(:cas, key, req_options)
|
|
392
398
|
return if value.nil? && !always_set
|
|
393
399
|
|
|
394
400
|
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'forwardable'
|
|
4
|
+
require 'set'
|
|
4
5
|
require 'socket'
|
|
5
6
|
require 'timeout'
|
|
6
7
|
|
|
@@ -23,6 +24,7 @@ module Dalli
|
|
|
23
24
|
def initialize(attribs, client_options = {})
|
|
24
25
|
hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
|
|
25
26
|
@options = client_options.merge(user_creds)
|
|
27
|
+
@verify_reply_keys = client_options[:verify_reply_keys] ? true : false
|
|
26
28
|
@value_marshaller = ValueMarshaller.new(@options)
|
|
27
29
|
@connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
|
|
28
30
|
end
|
|
@@ -105,7 +107,7 @@ module Dalli
|
|
|
105
107
|
|
|
106
108
|
# If the status is ok and the key is not nil, then this is a
|
|
107
109
|
# getkq response with a value that we want to set in the response hash
|
|
108
|
-
values
|
|
110
|
+
break unless keep_reading?(values, key, value, cas)
|
|
109
111
|
|
|
110
112
|
# Get the next response from the buffer
|
|
111
113
|
status, cas, key, value = response_buffer.process_single_getk_response
|
|
@@ -124,13 +126,16 @@ module Dalli
|
|
|
124
126
|
def pipeline_abort
|
|
125
127
|
response_buffer.clear
|
|
126
128
|
@connection_manager.abort_request!
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
# Closes the connection, which ensures that our connection
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
return unless connected?
|
|
130
|
+
|
|
131
|
+
# Closes the connection, which ensures that our connection is in a
|
|
132
|
+
# clean state for future requests. The get_multi ran out of time
|
|
133
|
+
# overall rather than hitting a socket error, so it doesn't count
|
|
134
|
+
# toward socket_max_failures: two slow get_multis in a row would
|
|
135
|
+
# otherwise mark a healthy server down.
|
|
136
|
+
Dalli.logger.warn { "#{name} didn't finish a get_multi within socket_timeout; closing the connection" }
|
|
137
|
+
@connection_manager.close
|
|
138
|
+
nil
|
|
134
139
|
end
|
|
135
140
|
|
|
136
141
|
# Did the last call to #pipeline_response_setup complete successfully?
|
|
@@ -217,6 +222,7 @@ module Dalli
|
|
|
217
222
|
end
|
|
218
223
|
|
|
219
224
|
def pipelined_get(keys)
|
|
225
|
+
expect_reply_keys(keys)
|
|
220
226
|
req = +''
|
|
221
227
|
keys.each do |key|
|
|
222
228
|
req << quiet_get_request(key)
|
|
@@ -229,6 +235,50 @@ module Dalli
|
|
|
229
235
|
@response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor)
|
|
230
236
|
end
|
|
231
237
|
|
|
238
|
+
# A multi-get only accepts replies for the keys it asked for. With the
|
|
239
|
+
# meta protocol, memcached names an item created by auto-create (ma or mg
|
|
240
|
+
# with N) for a base64-encoded key by its raw bytes, without the b flag;
|
|
241
|
+
# such a key can contain spaces or CR/LF, which split the reply header and
|
|
242
|
+
# make the replies after it parse as different ones. A key that wasn't
|
|
243
|
+
# requested is how that shows up. Only with the verify_reply_keys option,
|
|
244
|
+
# since building the set costs a multi-get about 15%.
|
|
245
|
+
def expect_reply_keys(keys)
|
|
246
|
+
@expected_reply_keys = (keys.to_set { |k| reply_key_form(k) } if @verify_reply_keys)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Stores a getkq reply's value and returns true, unless its key wasn't
|
|
250
|
+
# requested: then the replies are out of step, so what was read so far is
|
|
251
|
+
# kept, the rest dropped, and false returned to stop reading
|
|
252
|
+
def keep_reading?(values, key, value, cas)
|
|
253
|
+
return true if key.nil?
|
|
254
|
+
|
|
255
|
+
if unexpected_reply_key?(key)
|
|
256
|
+
abandon_pipeline
|
|
257
|
+
return false
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
values[key] = [value, cas]
|
|
261
|
+
true
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def unexpected_reply_key?(key)
|
|
265
|
+
!key.nil? && @expected_reply_keys && !@expected_reply_keys.include?(reply_key_form(key))
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Keys compared as bytes; ASCII keys compare equal in any encoding as is
|
|
269
|
+
def reply_key_form(key)
|
|
270
|
+
key.ascii_only? ? key : key.b
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Drops the rest of this server's replies and closes the connection so
|
|
274
|
+
# nothing unread is left on it. Not a socket failure, so it doesn't count
|
|
275
|
+
# toward socket_max_failures; the keys not read are simply missing.
|
|
276
|
+
def abandon_pipeline
|
|
277
|
+
Dalli.logger.warn { "#{name} replied to a get_multi with a key that wasn't requested; closing the connection" }
|
|
278
|
+
response_buffer.clear
|
|
279
|
+
@connection_manager.close
|
|
280
|
+
end
|
|
281
|
+
|
|
232
282
|
# Called after the noop response is received at the end of a set
|
|
233
283
|
# of pipelined gets
|
|
234
284
|
def finish_pipeline
|
|
@@ -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
|
@@ -13,6 +13,7 @@ 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
|
|
16
17
|
|
|
17
18
|
def response_processor
|
|
18
19
|
@response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
|
|
@@ -53,11 +54,11 @@ module Dalli
|
|
|
53
54
|
|
|
54
55
|
# TODO: This is confusing, as there's a cas command in memcached
|
|
55
56
|
# and this isn't it. Maybe rename? Maybe eliminate?
|
|
56
|
-
def cas(key)
|
|
57
|
+
def cas(key, options = nil)
|
|
57
58
|
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
58
59
|
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
|
|
59
60
|
write(req)
|
|
60
|
-
response_processor.meta_get_with_value_and_cas
|
|
61
|
+
response_processor.meta_get_with_value_and_cas(raw: raw_request?(options))
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
# Storage Commands
|
|
@@ -129,11 +130,42 @@ module Dalli
|
|
|
129
130
|
def decr_incr(incr, key, delta, ttl, initial)
|
|
130
131
|
ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
|
|
131
132
|
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
|
+
|
|
132
138
|
write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: initial, incr: incr, ttl: ttl,
|
|
133
139
|
quiet: quiet?, base64: base64))
|
|
134
140
|
response_processor.decr_incr unless quiet?
|
|
135
141
|
end
|
|
136
142
|
|
|
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
|
+
|
|
137
169
|
# Other Commands
|
|
138
170
|
def flush(delay = 0)
|
|
139
171
|
write(RequestFormatter.flush(delay: delay))
|
data/lib/dalli/version.rb
CHANGED
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: 3.2.
|
|
4
|
+
version: 3.2.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter M. Goldstein
|
|
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
90
|
- !ruby/object:Gem::Version
|
|
91
91
|
version: '0'
|
|
92
92
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
93
|
+
rubygems_version: 4.0.23
|
|
94
94
|
specification_version: 4
|
|
95
95
|
summary: High performance memcached client for Ruby
|
|
96
96
|
test_files: []
|