dalli 5.0.10 → 5.1.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 +4 -4
- data/CHANGELOG.md +47 -80
- data/Gemfile +1 -1
- data/README.md +7 -6
- data/lib/dalli/client.rb +272 -96
- data/lib/dalli/compressor.rb +5 -53
- data/lib/dalli/key_manager.rb +2 -23
- data/lib/dalli/pipelined_deleter.rb +29 -12
- data/lib/dalli/pipelined_getter.rb +57 -8
- data/lib/dalli/pipelined_setter.rb +15 -3
- data/lib/dalli/protocol/base.rb +55 -28
- data/lib/dalli/protocol/connection_manager.rb +9 -90
- data/lib/dalli/protocol/key_regularizer.rb +18 -1
- data/lib/dalli/protocol/meta.rb +89 -37
- data/lib/dalli/protocol/request_formatter.rb +110 -32
- data/lib/dalli/protocol/response_processor.rb +38 -55
- data/lib/dalli/protocol/value_compressor.rb +3 -30
- data/lib/dalli/protocol/value_serializer.rb +1 -1
- data/lib/dalli/socket.rb +4 -2
- data/lib/dalli/version.rb +2 -2
- data/lib/dalli.rb +0 -1
- data/lib/rack/session/dalli.rb +2 -1
- metadata +2 -3
- data/lib/dalli/json_serializer.rb +0 -28
|
@@ -35,10 +35,7 @@ module Dalli
|
|
|
35
35
|
@request_in_progress = false
|
|
36
36
|
@sock = nil
|
|
37
37
|
@pid = nil
|
|
38
|
-
@write_buffer = []
|
|
39
|
-
@write_buffer_bytes = 0
|
|
40
38
|
|
|
41
|
-
@fail_count = 0
|
|
42
39
|
reset_down_info
|
|
43
40
|
end
|
|
44
41
|
|
|
@@ -54,8 +51,7 @@ module Dalli
|
|
|
54
51
|
Dalli.logger.debug { "Dalli::Server#connect #{name}" }
|
|
55
52
|
|
|
56
53
|
@sock = memcached_socket
|
|
57
|
-
#
|
|
58
|
-
@sock.sync = true
|
|
54
|
+
@sock.sync = false # Enable buffered I/O for better performance
|
|
59
55
|
@pid = Process.pid
|
|
60
56
|
@request_in_progress = false
|
|
61
57
|
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError, SocketError => e
|
|
@@ -87,8 +83,6 @@ module Dalli
|
|
|
87
83
|
def down!
|
|
88
84
|
close
|
|
89
85
|
log_down_detected
|
|
90
|
-
# Once down_retry_delay passes, the server gets a full set of attempts
|
|
91
|
-
@fail_count = 0
|
|
92
86
|
|
|
93
87
|
@error = $ERROR_INFO&.class&.name
|
|
94
88
|
@msg ||= $ERROR_INFO&.message
|
|
@@ -118,7 +112,7 @@ module Dalli
|
|
|
118
112
|
return unless @sock
|
|
119
113
|
|
|
120
114
|
begin
|
|
121
|
-
|
|
115
|
+
@sock.close
|
|
122
116
|
rescue StandardError
|
|
123
117
|
nil
|
|
124
118
|
ensure
|
|
@@ -129,18 +123,10 @@ module Dalli
|
|
|
129
123
|
# @request_in_progress == true.
|
|
130
124
|
@sock = nil
|
|
131
125
|
@pid = nil
|
|
132
|
-
discard_write_buffer
|
|
133
126
|
abort_request!
|
|
134
127
|
end
|
|
135
128
|
end
|
|
136
129
|
|
|
137
|
-
# A forked child shares the parent's connection, so it closes only its
|
|
138
|
-
# own file descriptor. Closing the TLS socket itself would send
|
|
139
|
-
# close_notify and end the parent's TLS session.
|
|
140
|
-
def close_socket
|
|
141
|
-
fork_detected? ? @sock.to_io.close : @sock.close
|
|
142
|
-
end
|
|
143
|
-
|
|
144
130
|
def connected?
|
|
145
131
|
!@sock.nil?
|
|
146
132
|
end
|
|
@@ -159,11 +145,6 @@ module Dalli
|
|
|
159
145
|
raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless @request_in_progress
|
|
160
146
|
|
|
161
147
|
@request_in_progress = false
|
|
162
|
-
# A completed request is what proves the server healthy again, so the
|
|
163
|
-
# failure count resets here rather than on reconnect: a server that
|
|
164
|
-
# accepts connections but never answers would otherwise reset it on
|
|
165
|
-
# every retry and never reach socket_max_failures.
|
|
166
|
-
@fail_count = 0
|
|
167
148
|
end
|
|
168
149
|
|
|
169
150
|
def abort_request!
|
|
@@ -171,7 +152,6 @@ module Dalli
|
|
|
171
152
|
end
|
|
172
153
|
|
|
173
154
|
def read_line
|
|
174
|
-
flush_write_buffer
|
|
175
155
|
data = @sock.gets("\r\n")
|
|
176
156
|
error_on_request!('EOF in read_line') if data.nil?
|
|
177
157
|
data
|
|
@@ -181,29 +161,14 @@ module Dalli
|
|
|
181
161
|
|
|
182
162
|
# JRuby doesn't support IO#timeout=, so use custom readfull implementation
|
|
183
163
|
# CRuby 3.3+ has IO#timeout= which makes IO#read work with timeouts
|
|
184
|
-
# memcached can't store an item larger than 1 GiB (its -I maximum), so a
|
|
185
|
-
# reply claiming more (or a negative size) is malformed or hostile.
|
|
186
|
-
# IO#read allocates the full count up front, so check before reading.
|
|
187
|
-
MAX_READ_BYTES = (1024 * 1024 * 1024) + 2 # plus the value's trailing "\r\n"
|
|
188
|
-
|
|
189
|
-
def check_read_size!(count)
|
|
190
|
-
return if count.between?(0, MAX_READ_BYTES)
|
|
191
|
-
|
|
192
|
-
raise Dalli::DalliError, "Reply size #{count} from #{name} is out of range"
|
|
193
|
-
end
|
|
194
|
-
|
|
195
164
|
if RUBY_ENGINE == 'jruby'
|
|
196
165
|
def read(count)
|
|
197
|
-
check_read_size!(count)
|
|
198
|
-
flush_write_buffer
|
|
199
166
|
@sock.readfull(count)
|
|
200
167
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
|
|
201
168
|
error_on_request!(e)
|
|
202
169
|
end
|
|
203
170
|
else
|
|
204
171
|
def read(count)
|
|
205
|
-
check_read_size!(count)
|
|
206
|
-
flush_write_buffer
|
|
207
172
|
read_bytes(count)
|
|
208
173
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
|
|
209
174
|
error_on_request!(e)
|
|
@@ -214,34 +179,22 @@ module Dalli
|
|
|
214
179
|
# at the call site.
|
|
215
180
|
alias read_exact read
|
|
216
181
|
|
|
217
|
-
# Requests are buffered here rather than in the socket's own IO buffer
|
|
218
|
-
# (the socket is sync). Ruby flushes an IO's write buffer when any process
|
|
219
|
-
# holding it closes or finalizes the IO, so after a fork a child could
|
|
220
|
-
# send bytes the parent had buffered but not sent yet: a quiet write would
|
|
221
|
-
# run twice, or the parent's replies would shift onto the wrong requests.
|
|
222
|
-
# A forked child discards this buffer instead. Like IO's own buffer, it
|
|
223
|
-
# is sent once it grows past this size, or when a reply is read.
|
|
224
|
-
WRITE_BUFFER_FLUSH_BYTES = 64 * 1024
|
|
225
|
-
|
|
226
182
|
def write(bytes)
|
|
227
|
-
@
|
|
228
|
-
@write_buffer_bytes += bytes.bytesize
|
|
229
|
-
flush_write_buffer if @write_buffer_bytes >= WRITE_BUFFER_FLUSH_BYTES
|
|
230
|
-
bytes.bytesize
|
|
183
|
+
@sock.write(bytes)
|
|
231
184
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
|
|
232
185
|
error_on_request!(e)
|
|
233
186
|
end
|
|
234
187
|
|
|
235
188
|
def flush
|
|
236
|
-
|
|
189
|
+
@sock.flush
|
|
237
190
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
|
|
238
191
|
error_on_request!(e)
|
|
239
192
|
end
|
|
240
193
|
|
|
241
194
|
def flushed_write(bytes)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
195
|
+
written = @sock.write(bytes)
|
|
196
|
+
@sock.flush
|
|
197
|
+
written
|
|
245
198
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
|
|
246
199
|
error_on_request!(e)
|
|
247
200
|
end
|
|
@@ -249,7 +202,6 @@ module Dalli
|
|
|
249
202
|
# Non-blocking read. Here to support the operation
|
|
250
203
|
# of the get_multi operation
|
|
251
204
|
def read_available(...)
|
|
252
|
-
flush_write_buffer
|
|
253
205
|
@sock.read_available(...)
|
|
254
206
|
end
|
|
255
207
|
|
|
@@ -276,9 +228,8 @@ module Dalli
|
|
|
276
228
|
raise Dalli::RetryableNetworkError, message
|
|
277
229
|
end
|
|
278
230
|
|
|
279
|
-
# Called on connect. Deliberately leaves @fail_count alone; see
|
|
280
|
-
# finish_request!.
|
|
281
231
|
def reset_down_info
|
|
232
|
+
@fail_count = 0
|
|
282
233
|
@down_at = nil
|
|
283
234
|
@last_down_at = nil
|
|
284
235
|
@msg = nil
|
|
@@ -303,8 +254,7 @@ module Dalli
|
|
|
303
254
|
def reconnect_on_fork
|
|
304
255
|
message = 'Fork detected, re-connecting child process...'
|
|
305
256
|
Dalli.logger.info { message }
|
|
306
|
-
#
|
|
307
|
-
# without touching the parent's connection, and reconnects immediately
|
|
257
|
+
# Close socket on a fork and reconnect immediately
|
|
308
258
|
close
|
|
309
259
|
establish_connection
|
|
310
260
|
end
|
|
@@ -334,37 +284,6 @@ module Dalli
|
|
|
334
284
|
|
|
335
285
|
private
|
|
336
286
|
|
|
337
|
-
# One write per flush. A TLS socket sends each argument of
|
|
338
|
-
# write(*parts) as its own record and system call, so the parts are
|
|
339
|
-
# joined first.
|
|
340
|
-
def flush_write_buffer
|
|
341
|
-
return if @write_buffer.empty?
|
|
342
|
-
|
|
343
|
-
@sock.write(@write_buffer.size == 1 ? @write_buffer.first : joined_write_buffer)
|
|
344
|
-
discard_write_buffer
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
# Joined as bytes: requests can mix UTF-8 and binary strings, which
|
|
348
|
-
# String#<< refuses to combine when both hold non-ASCII bytes
|
|
349
|
-
if String.method_defined?(:append_as_bytes)
|
|
350
|
-
def joined_write_buffer
|
|
351
|
-
out = String.new(capacity: @write_buffer_bytes, encoding: Encoding::BINARY)
|
|
352
|
-
@write_buffer.each { |part| out.append_as_bytes(part) }
|
|
353
|
-
out
|
|
354
|
-
end
|
|
355
|
-
else
|
|
356
|
-
def joined_write_buffer
|
|
357
|
-
out = String.new(capacity: @write_buffer_bytes, encoding: Encoding::BINARY)
|
|
358
|
-
@write_buffer.each { |part| out << part.b }
|
|
359
|
-
out
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
def discard_write_buffer
|
|
364
|
-
@write_buffer.clear
|
|
365
|
-
@write_buffer_bytes = 0
|
|
366
|
-
end
|
|
367
|
-
|
|
368
287
|
# Reads exactly `count` bytes. IO#read(count) on a blocking socket blocks
|
|
369
288
|
# until it has `count` bytes, accumulating across TCP chunks internally,
|
|
370
289
|
# and only hands back a shorter (or nil) buffer when the stream hits EOF.
|
|
@@ -12,8 +12,25 @@ module Dalli
|
|
|
12
12
|
module KeyRegularizer
|
|
13
13
|
module_function
|
|
14
14
|
|
|
15
|
+
# protocol.txt requires that a key "must not include control
|
|
16
|
+
# characters or whitespace" -- \p{Cntrl} is C0 (0x00-0x1F) plus DEL
|
|
17
|
+
# (0x7F). \s alone misses NUL and the rest of that range: a key
|
|
18
|
+
# containing one of those bytes but no whitespace is ASCII-only, so
|
|
19
|
+
# it would otherwise be written to the wire unencoded. Not a
|
|
20
|
+
# protocol-injection risk (the text protocol splits on CRLF, not
|
|
21
|
+
# other control bytes), but a downstream consumer that treats the key
|
|
22
|
+
# specially at one of those bytes (a C string terminating at NUL, a
|
|
23
|
+
# terminal or log line interpreting an escape byte) could silently
|
|
24
|
+
# act on a different key than Dalli believes it sent.
|
|
25
|
+
#
|
|
26
|
+
# Written as \p{Cntrl} rather than the POSIX [:cntrl:] bracket class:
|
|
27
|
+
# \s and [:cntrl:] overlap (tab, newline, CR are in both), and Ruby
|
|
28
|
+
# warns "character class has duplicated range" when they're combined
|
|
29
|
+
# in one -- fatal here, since this suite's -w run treats warnings as
|
|
30
|
+
# errors (see test_strict_warnings.rb). \p{Cntrl} matches the same
|
|
31
|
+
# bytes without the overlap warning.
|
|
15
32
|
def required?(key)
|
|
16
|
-
!key.ascii_only? ||
|
|
33
|
+
!key.ascii_only? || /[\p{Cntrl}\s]/.match?(key)
|
|
17
34
|
end
|
|
18
35
|
|
|
19
36
|
def encode(key)
|
data/lib/dalli/protocol/meta.rb
CHANGED
|
@@ -26,22 +26,23 @@ module Dalli
|
|
|
26
26
|
def get(key, options = nil)
|
|
27
27
|
# Skip bitflags in raw mode - saves 2 bytes per request and skips parsing
|
|
28
28
|
skip_flags = raw_mode? || (options && options[:raw])
|
|
29
|
-
req = RequestFormatter.meta_get(key: key, skip_flags: skip_flags)
|
|
29
|
+
req = RequestFormatter.meta_get(key: key, skip_flags: skip_flags, **routing_token_kwargs(options))
|
|
30
30
|
flushed_write(req)
|
|
31
|
-
response_processor.meta_get_with_value(cache_nils: cache_nils?(options)
|
|
31
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def quiet_get_request(key)
|
|
34
|
+
def quiet_get_request(key, options = nil)
|
|
35
35
|
# Skip bitflags in raw mode - saves 2 bytes per request and skips parsing
|
|
36
|
-
RequestFormatter.meta_get(key: key, return_cas: true, quiet: true, skip_flags: raw_mode
|
|
36
|
+
RequestFormatter.meta_get(key: key, return_cas: true, quiet: true, skip_flags: raw_mode?,
|
|
37
|
+
**routing_token_kwargs(options))
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def gat(key, ttl, options = nil)
|
|
40
41
|
ttl = TtlSanitizer.sanitize(ttl)
|
|
41
42
|
skip_flags = raw_mode? || (options && options[:raw])
|
|
42
|
-
req = RequestFormatter.meta_get(key: key, ttl: ttl, skip_flags: skip_flags)
|
|
43
|
+
req = RequestFormatter.meta_get(key: key, ttl: ttl, skip_flags: skip_flags, **routing_token_kwargs(options))
|
|
43
44
|
flushed_write(req)
|
|
44
|
-
response_processor.meta_get_with_value(cache_nils: cache_nils?(options)
|
|
45
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def touch(key, ttl)
|
|
@@ -54,9 +55,9 @@ module Dalli
|
|
|
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
57
|
def cas(key, options = nil)
|
|
57
|
-
req = RequestFormatter.meta_get(key: key, value: true, return_cas: true,
|
|
58
|
+
req = RequestFormatter.meta_get(key: key, value: true, return_cas: true, **routing_token_kwargs(options))
|
|
58
59
|
flushed_write(req)
|
|
59
|
-
response_processor.meta_get_with_value_and_cas
|
|
60
|
+
response_processor.meta_get_with_value_and_cas
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
# Comprehensive meta get with support for all metadata flags.
|
|
@@ -85,15 +86,19 @@ module Dalli
|
|
|
85
86
|
# - :last_access - seconds since last access (only if return_last_access: true)
|
|
86
87
|
def meta_get(key, options = {})
|
|
87
88
|
req = RequestFormatter.meta_get(
|
|
88
|
-
key: key, value: true, return_cas: true,
|
|
89
|
+
key: key, value: true, return_cas: true,
|
|
89
90
|
vivify_ttl: options[:vivify_ttl], recache_ttl: options[:recache_ttl],
|
|
90
91
|
return_hit_status: options[:return_hit_status],
|
|
91
|
-
return_last_access: options[:return_last_access],
|
|
92
|
+
return_last_access: options[:return_last_access],
|
|
93
|
+
return_ttl_remaining: options[:return_ttl_remaining],
|
|
94
|
+
skip_lru_bump: options[:skip_lru_bump],
|
|
95
|
+
**routing_token_kwargs(options)
|
|
92
96
|
)
|
|
93
97
|
flushed_write(req)
|
|
94
98
|
response_processor.meta_get_with_metadata(
|
|
95
99
|
cache_nils: cache_nils?(options), return_hit_status: options[:return_hit_status],
|
|
96
|
-
return_last_access: options[:return_last_access],
|
|
100
|
+
return_last_access: options[:return_last_access],
|
|
101
|
+
return_ttl_remaining: options[:return_ttl_remaining]
|
|
97
102
|
)
|
|
98
103
|
end
|
|
99
104
|
|
|
@@ -137,34 +142,41 @@ module Dalli
|
|
|
137
142
|
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
|
138
143
|
req = RequestFormatter.meta_set(key: key, value: value,
|
|
139
144
|
bitflags: bitflags, cas: cas,
|
|
140
|
-
ttl: ttl, mode: mode, quiet: quiet
|
|
145
|
+
ttl: ttl, mode: mode, quiet: quiet,
|
|
146
|
+
**routing_token_kwargs(options))
|
|
141
147
|
write("#{req}#{value}#{TERMINATOR}")
|
|
142
148
|
@connection_manager.flush unless quiet
|
|
143
149
|
end
|
|
144
150
|
# rubocop:enable Metrics/ParameterLists
|
|
145
151
|
|
|
146
|
-
def append(key, value)
|
|
147
|
-
write_append_prepend_req(:append, key, value)
|
|
152
|
+
def append(key, value, options = nil)
|
|
153
|
+
write_append_prepend_req(:append, key, value, nil, nil, options)
|
|
148
154
|
response_processor.meta_set_append_prepend unless quiet?
|
|
149
155
|
end
|
|
150
156
|
|
|
151
|
-
def prepend(key, value)
|
|
152
|
-
write_append_prepend_req(:prepend, key, value)
|
|
157
|
+
def prepend(key, value, options = nil)
|
|
158
|
+
write_append_prepend_req(:prepend, key, value, nil, nil, options)
|
|
153
159
|
response_processor.meta_set_append_prepend unless quiet?
|
|
154
160
|
end
|
|
155
161
|
|
|
156
|
-
# rubocop:disable
|
|
157
|
-
def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil,
|
|
162
|
+
# rubocop:disable Metrics/ParameterLists
|
|
163
|
+
def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil, options = nil)
|
|
158
164
|
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
|
159
165
|
req = RequestFormatter.meta_set(key: key, value: value,
|
|
160
|
-
cas: cas, ttl: ttl, mode: mode, quiet: quiet
|
|
166
|
+
cas: cas, ttl: ttl, mode: mode, quiet: quiet?,
|
|
167
|
+
**routing_token_kwargs(options))
|
|
161
168
|
write("#{req}#{value}#{TERMINATOR}")
|
|
162
169
|
@connection_manager.flush unless quiet?
|
|
163
170
|
end
|
|
171
|
+
# rubocop:enable Metrics/ParameterLists
|
|
164
172
|
|
|
165
173
|
# Delete Commands
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
#
|
|
175
|
+
# `options` supports the meta-delete keys :invalidate, :tombstone_ttl and
|
|
176
|
+
# :drop_value, plus :p_token/:l_token; see Dalli::Client#delete.
|
|
177
|
+
def delete(key, cas, options = nil)
|
|
178
|
+
req = RequestFormatter.meta_delete(key: key, cas: cas, quiet: quiet?,
|
|
179
|
+
**tombstone_kwargs(options), **routing_token_kwargs(options))
|
|
168
180
|
write(req)
|
|
169
181
|
@connection_manager.flush unless quiet?
|
|
170
182
|
response_processor.meta_delete unless quiet?
|
|
@@ -172,8 +184,9 @@ module Dalli
|
|
|
172
184
|
|
|
173
185
|
# Pipelined delete - writes a quiet delete request without reading response.
|
|
174
186
|
# Used by PipelinedDeleter for bulk operations.
|
|
175
|
-
def pipelined_delete(key)
|
|
176
|
-
req = RequestFormatter.meta_delete(key: key, quiet: true
|
|
187
|
+
def pipelined_delete(key, req_options = nil)
|
|
188
|
+
req = RequestFormatter.meta_delete(key: key, quiet: true,
|
|
189
|
+
**tombstone_kwargs(req_options), **routing_token_kwargs(req_options))
|
|
177
190
|
write(req)
|
|
178
191
|
end
|
|
179
192
|
|
|
@@ -183,21 +196,23 @@ module Dalli
|
|
|
183
196
|
end
|
|
184
197
|
|
|
185
198
|
# Arithmetic Commands
|
|
186
|
-
def decr(key, count, ttl, initial)
|
|
187
|
-
decr_incr false, key, count, ttl, initial
|
|
199
|
+
def decr(key, count, ttl, initial, options = nil)
|
|
200
|
+
decr_incr false, key, count, ttl, initial, options
|
|
188
201
|
end
|
|
189
202
|
|
|
190
|
-
def incr(key, count, ttl, initial)
|
|
191
|
-
decr_incr true, key, count, ttl, initial
|
|
203
|
+
def incr(key, count, ttl, initial, options = nil)
|
|
204
|
+
decr_incr true, key, count, ttl, initial, options
|
|
192
205
|
end
|
|
193
206
|
|
|
194
|
-
|
|
207
|
+
# rubocop:disable Metrics/ParameterLists
|
|
208
|
+
def decr_incr(incr, key, delta, ttl, initial, options = nil)
|
|
195
209
|
ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
|
|
196
210
|
write(RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, incr: incr, ttl: ttl,
|
|
197
|
-
quiet: quiet
|
|
211
|
+
quiet: quiet?, **routing_token_kwargs(options)))
|
|
198
212
|
@connection_manager.flush unless quiet?
|
|
199
213
|
response_processor.decr_incr unless quiet?
|
|
200
214
|
end
|
|
215
|
+
# rubocop:enable Metrics/ParameterLists
|
|
201
216
|
|
|
202
217
|
# Other Commands
|
|
203
218
|
def flush(delay = 0)
|
|
@@ -235,14 +250,52 @@ module Dalli
|
|
|
235
250
|
# Single-server fast path for get_multi. Inlines request formatting and
|
|
236
251
|
# response parsing to minimize per-key overhead. Avoids the PipelinedGetter
|
|
237
252
|
# machinery (IO.select, response buffering, server grouping).
|
|
238
|
-
def read_multi_req(keys)
|
|
253
|
+
def read_multi_req(keys, options = nil)
|
|
239
254
|
is_raw = raw_mode?
|
|
240
|
-
buffer = RequestFormatter.multi_meta_get(keys, skip_flags: is_raw)
|
|
255
|
+
buffer = RequestFormatter.multi_meta_get(keys, skip_flags: is_raw, **routing_token_kwargs(options))
|
|
241
256
|
flushed_write(buffer)
|
|
242
257
|
buffer.clear
|
|
243
258
|
read_multi_get_responses(is_raw)
|
|
244
259
|
end
|
|
245
260
|
|
|
261
|
+
# Stale-aware bulk get. Returns { key => { value:, cas:, stale:, miss: } }
|
|
262
|
+
# for the keys the server returned. Keys that were not found are absent
|
|
263
|
+
# from the hash, matching read_multi_req and the get_multi family; a
|
|
264
|
+
# tombstoned item is present (it answers VA with the X flag) with
|
|
265
|
+
# stale: true, which is the distinction callers need.
|
|
266
|
+
#
|
|
267
|
+
# Shared by both the single-server fast path and PipelinedGetter's
|
|
268
|
+
# per-server-group request, so this one change covers both routes.
|
|
269
|
+
def read_multi_with_metadata_req(keys, options = nil)
|
|
270
|
+
is_raw = raw_mode?
|
|
271
|
+
buffer = RequestFormatter.multi_meta_get(keys, skip_flags: is_raw, return_cas: true,
|
|
272
|
+
**routing_token_kwargs(options))
|
|
273
|
+
flushed_write(buffer)
|
|
274
|
+
buffer.clear
|
|
275
|
+
read_multi_metadata_responses(is_raw)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Unlike read_multi_get_responses this locates tokens by flag rather than
|
|
279
|
+
# by position, because the c flag shifts the key's index.
|
|
280
|
+
def read_multi_metadata_responses(is_raw)
|
|
281
|
+
hash = {}
|
|
282
|
+
while (line = @connection_manager.read_line)
|
|
283
|
+
break if line.start_with?('MN')
|
|
284
|
+
next unless line.start_with?('VA ')
|
|
285
|
+
|
|
286
|
+
tokens = line.chomp!(TERMINATOR).split
|
|
287
|
+
value = @connection_manager.read(tokens[1].to_i + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
|
|
288
|
+
stale = response_processor.stale_from_tokens(tokens)
|
|
289
|
+
cas = response_processor.cas_from_tokens(tokens)
|
|
290
|
+
bitflags = is_raw ? 0 : response_processor.bitflags_from_tokens(tokens)
|
|
291
|
+
key = response_processor.key_from_tokens(tokens)
|
|
292
|
+
next if key.nil?
|
|
293
|
+
|
|
294
|
+
hash[key] = { value: @value_marshaller.retrieve(value, bitflags), cas: cas, stale: stale, miss: false }
|
|
295
|
+
end
|
|
296
|
+
hash
|
|
297
|
+
end
|
|
298
|
+
|
|
246
299
|
def read_multi_get_responses(is_raw)
|
|
247
300
|
hash = {}
|
|
248
301
|
key_index = is_raw ? 2 : 3
|
|
@@ -258,9 +311,7 @@ module Dalli
|
|
|
258
311
|
|
|
259
312
|
def parse_multi_get_value(line, key_index, is_raw)
|
|
260
313
|
tokens = line.chomp!(TERMINATOR).split
|
|
261
|
-
|
|
262
|
-
response_processor.check_value_size!(size)
|
|
263
|
-
value = @connection_manager.read(size + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
|
|
314
|
+
value = @connection_manager.read(tokens[1].to_i + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
|
|
264
315
|
raw_key = tokens[key_index]
|
|
265
316
|
return unless raw_key
|
|
266
317
|
|
|
@@ -278,7 +329,7 @@ module Dalli
|
|
|
278
329
|
[key, @value_marshaller.store(key, raw_value, req_options)]
|
|
279
330
|
end
|
|
280
331
|
|
|
281
|
-
buffer = RequestFormatter.multi_meta_set(entries, ttl: ttl)
|
|
332
|
+
buffer = RequestFormatter.multi_meta_set(entries, ttl: ttl, **routing_token_kwargs(req_options))
|
|
282
333
|
flushed_write(buffer)
|
|
283
334
|
buffer.clear
|
|
284
335
|
response_processor.consume_all_responses_until_mn
|
|
@@ -286,8 +337,9 @@ module Dalli
|
|
|
286
337
|
|
|
287
338
|
# Single-server fast path for delete_multi. Writes all quiet delete requests
|
|
288
339
|
# terminated by a noop, then consumes all responses.
|
|
289
|
-
def delete_multi_req(keys)
|
|
290
|
-
buffer = RequestFormatter.multi_meta_delete(keys)
|
|
340
|
+
def delete_multi_req(keys, req_options = nil)
|
|
341
|
+
buffer = RequestFormatter.multi_meta_delete(keys, **tombstone_kwargs(req_options),
|
|
342
|
+
**routing_token_kwargs(req_options))
|
|
291
343
|
flushed_write(buffer)
|
|
292
344
|
buffer.clear
|
|
293
345
|
keys.size - response_processor.pipelined_delete_non_deletions
|