dalli 5.0.4 → 5.0.6
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 +137 -0
- data/lib/dalli/client.rb +23 -7
- data/lib/dalli/options.rb +1 -1
- data/lib/dalli/pipelined_deleter.rb +17 -10
- data/lib/dalli/protocol/base.rb +15 -2
- data/lib/dalli/protocol/connection_manager.rb +53 -17
- data/lib/dalli/protocol/key_regularizer.rb +8 -9
- data/lib/dalli/protocol/meta.rb +46 -87
- data/lib/dalli/protocol/request_formatter.rb +80 -32
- data/lib/dalli/protocol/response_buffer.rb +21 -22
- data/lib/dalli/protocol/response_processor.rb +63 -27
- data/lib/dalli/socket.rb +47 -32
- data/lib/dalli/version.rb +1 -1
- metadata +2 -3
- data/lib/dalli/pid_cache.rb +0 -40
data/lib/dalli/protocol/meta.rb
CHANGED
|
@@ -24,48 +24,38 @@ module Dalli
|
|
|
24
24
|
|
|
25
25
|
# Retrieval Commands
|
|
26
26
|
def get(key, options = nil)
|
|
27
|
-
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
28
27
|
# Skip bitflags in raw mode - saves 2 bytes per request and skips parsing
|
|
29
28
|
skip_flags = raw_mode? || (options && options[:raw])
|
|
30
|
-
req = RequestFormatter.meta_get(key:
|
|
31
|
-
|
|
32
|
-
@connection_manager.flush
|
|
29
|
+
req = RequestFormatter.meta_get(key: key, skip_flags: skip_flags)
|
|
30
|
+
flushed_write(req)
|
|
33
31
|
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
def quiet_get_request(key)
|
|
37
|
-
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
38
35
|
# Skip bitflags in raw mode - saves 2 bytes per request and skips parsing
|
|
39
|
-
RequestFormatter.meta_get(key:
|
|
40
|
-
skip_flags: raw_mode?)
|
|
36
|
+
RequestFormatter.meta_get(key: key, return_cas: true, quiet: true, skip_flags: raw_mode?)
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
def gat(key, ttl, options = nil)
|
|
44
40
|
ttl = TtlSanitizer.sanitize(ttl)
|
|
45
|
-
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
46
41
|
skip_flags = raw_mode? || (options && options[:raw])
|
|
47
|
-
req = RequestFormatter.meta_get(key:
|
|
48
|
-
|
|
49
|
-
@connection_manager.flush
|
|
42
|
+
req = RequestFormatter.meta_get(key: key, ttl: ttl, skip_flags: skip_flags)
|
|
43
|
+
flushed_write(req)
|
|
50
44
|
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
|
51
45
|
end
|
|
52
46
|
|
|
53
47
|
def touch(key, ttl)
|
|
54
48
|
ttl = TtlSanitizer.sanitize(ttl)
|
|
55
|
-
|
|
56
|
-
req
|
|
57
|
-
write(req)
|
|
58
|
-
@connection_manager.flush
|
|
49
|
+
req = RequestFormatter.meta_get(key: key, ttl: ttl, value: false)
|
|
50
|
+
flushed_write(req)
|
|
59
51
|
response_processor.meta_get_without_value
|
|
60
52
|
end
|
|
61
53
|
|
|
62
54
|
# TODO: This is confusing, as there's a cas command in memcached
|
|
63
55
|
# and this isn't it. Maybe rename? Maybe eliminate?
|
|
64
56
|
def cas(key)
|
|
65
|
-
|
|
66
|
-
req
|
|
67
|
-
write(req)
|
|
68
|
-
@connection_manager.flush
|
|
57
|
+
req = RequestFormatter.meta_get(key: key, value: true, return_cas: true)
|
|
58
|
+
flushed_write(req)
|
|
69
59
|
response_processor.meta_get_with_value_and_cas
|
|
70
60
|
end
|
|
71
61
|
|
|
@@ -94,15 +84,13 @@ module Dalli
|
|
|
94
84
|
# - :hit_before - true/false if previously accessed (only if return_hit_status: true)
|
|
95
85
|
# - :last_access - seconds since last access (only if return_last_access: true)
|
|
96
86
|
def meta_get(key, options = {})
|
|
97
|
-
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
98
87
|
req = RequestFormatter.meta_get(
|
|
99
|
-
key:
|
|
88
|
+
key: key, value: true, return_cas: true,
|
|
100
89
|
vivify_ttl: options[:vivify_ttl], recache_ttl: options[:recache_ttl],
|
|
101
90
|
return_hit_status: options[:return_hit_status],
|
|
102
91
|
return_last_access: options[:return_last_access], skip_lru_bump: options[:skip_lru_bump]
|
|
103
92
|
)
|
|
104
|
-
|
|
105
|
-
@connection_manager.flush
|
|
93
|
+
flushed_write(req)
|
|
106
94
|
response_processor.meta_get_with_metadata(
|
|
107
95
|
cache_nils: cache_nils?(options), return_hit_status: options[:return_hit_status],
|
|
108
96
|
return_last_access: options[:return_last_access]
|
|
@@ -117,10 +105,8 @@ module Dalli
|
|
|
117
105
|
# @param cas [Integer] optional CAS value for compare-and-swap
|
|
118
106
|
# @return [Boolean] true if successful
|
|
119
107
|
def delete_stale(key, cas = nil)
|
|
120
|
-
|
|
121
|
-
req
|
|
122
|
-
write(req)
|
|
123
|
-
@connection_manager.flush
|
|
108
|
+
req = RequestFormatter.meta_delete(key: key, cas: cas, stale: true)
|
|
109
|
+
flushed_write(req)
|
|
124
110
|
response_processor.meta_delete
|
|
125
111
|
end
|
|
126
112
|
|
|
@@ -146,17 +132,13 @@ module Dalli
|
|
|
146
132
|
response_processor.meta_set_with_cas unless quiet?
|
|
147
133
|
end
|
|
148
134
|
|
|
149
|
-
# rubocop:disable Metrics/ParameterLists
|
|
150
135
|
def write_storage_req(mode, key, raw_value, ttl = nil, cas = nil, options = {}, quiet: quiet?)
|
|
151
136
|
(value, bitflags) = @value_marshaller.store(key, raw_value, options)
|
|
152
137
|
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
|
153
|
-
|
|
154
|
-
req = RequestFormatter.meta_set(key: encoded_key, value: value,
|
|
138
|
+
req = RequestFormatter.meta_set(key: key, value: value,
|
|
155
139
|
bitflags: bitflags, cas: cas,
|
|
156
|
-
ttl: ttl, mode: mode, quiet: quiet
|
|
157
|
-
write(req)
|
|
158
|
-
write(value)
|
|
159
|
-
write(TERMINATOR)
|
|
140
|
+
ttl: ttl, mode: mode, quiet: quiet)
|
|
141
|
+
write("#{req}#{value}#{TERMINATOR}")
|
|
160
142
|
@connection_manager.flush unless quiet
|
|
161
143
|
end
|
|
162
144
|
# rubocop:enable Metrics/ParameterLists
|
|
@@ -174,21 +156,16 @@ module Dalli
|
|
|
174
156
|
# rubocop:disable Metrics/ParameterLists
|
|
175
157
|
def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil, _options = {})
|
|
176
158
|
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
|
177
|
-
|
|
178
|
-
req = RequestFormatter.meta_set(key: encoded_key, value: value, base64: base64,
|
|
159
|
+
req = RequestFormatter.meta_set(key: key, value: value,
|
|
179
160
|
cas: cas, ttl: ttl, mode: mode, quiet: quiet?)
|
|
180
|
-
write(req)
|
|
181
|
-
write(value)
|
|
182
|
-
write(TERMINATOR)
|
|
161
|
+
write("#{req}#{value}#{TERMINATOR}")
|
|
183
162
|
@connection_manager.flush unless quiet?
|
|
184
163
|
end
|
|
185
164
|
# rubocop:enable Metrics/ParameterLists
|
|
186
165
|
|
|
187
166
|
# Delete Commands
|
|
188
167
|
def delete(key, cas)
|
|
189
|
-
|
|
190
|
-
req = RequestFormatter.meta_delete(key: encoded_key, cas: cas,
|
|
191
|
-
base64: base64, quiet: quiet?)
|
|
168
|
+
req = RequestFormatter.meta_delete(key: key, cas: cas, quiet: quiet?)
|
|
192
169
|
write(req)
|
|
193
170
|
@connection_manager.flush unless quiet?
|
|
194
171
|
response_processor.meta_delete unless quiet?
|
|
@@ -197,11 +174,15 @@ module Dalli
|
|
|
197
174
|
# Pipelined delete - writes a quiet delete request without reading response.
|
|
198
175
|
# Used by PipelinedDeleter for bulk operations.
|
|
199
176
|
def pipelined_delete(key)
|
|
200
|
-
|
|
201
|
-
req = RequestFormatter.meta_delete(key: encoded_key, base64: base64, quiet: true)
|
|
177
|
+
req = RequestFormatter.meta_delete(key: key, quiet: true)
|
|
202
178
|
write(req)
|
|
203
179
|
end
|
|
204
180
|
|
|
181
|
+
def finish_pipelined_delete(sent)
|
|
182
|
+
write_noop
|
|
183
|
+
sent - response_processor.pipelined_delete_non_deletions
|
|
184
|
+
end
|
|
185
|
+
|
|
205
186
|
# Arithmetic Commands
|
|
206
187
|
def decr(key, count, ttl, initial)
|
|
207
188
|
decr_incr false, key, count, ttl, initial
|
|
@@ -213,9 +194,8 @@ module Dalli
|
|
|
213
194
|
|
|
214
195
|
def decr_incr(incr, key, delta, ttl, initial)
|
|
215
196
|
ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
quiet: quiet?, base64: base64))
|
|
197
|
+
write(RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, incr: incr, ttl: ttl,
|
|
198
|
+
quiet: quiet?))
|
|
219
199
|
@connection_manager.flush unless quiet?
|
|
220
200
|
response_processor.decr_incr unless quiet?
|
|
221
201
|
end
|
|
@@ -235,26 +215,22 @@ module Dalli
|
|
|
235
215
|
end
|
|
236
216
|
|
|
237
217
|
def stats(info = nil)
|
|
238
|
-
|
|
239
|
-
@connection_manager.flush
|
|
218
|
+
flushed_write(RequestFormatter.stats(info))
|
|
240
219
|
response_processor.stats
|
|
241
220
|
end
|
|
242
221
|
|
|
243
222
|
def reset_stats
|
|
244
|
-
|
|
245
|
-
@connection_manager.flush
|
|
223
|
+
flushed_write(RequestFormatter.stats('reset'))
|
|
246
224
|
response_processor.reset
|
|
247
225
|
end
|
|
248
226
|
|
|
249
227
|
def version
|
|
250
|
-
|
|
251
|
-
@connection_manager.flush
|
|
228
|
+
flushed_write(RequestFormatter.version)
|
|
252
229
|
response_processor.version
|
|
253
230
|
end
|
|
254
231
|
|
|
255
232
|
def write_noop
|
|
256
|
-
|
|
257
|
-
@connection_manager.flush
|
|
233
|
+
flushed_write(RequestFormatter.meta_noop)
|
|
258
234
|
end
|
|
259
235
|
|
|
260
236
|
# Single-server fast path for get_multi. Inlines request formatting and
|
|
@@ -262,17 +238,9 @@ module Dalli
|
|
|
262
238
|
# machinery (IO.select, response buffering, server grouping).
|
|
263
239
|
def read_multi_req(keys)
|
|
264
240
|
is_raw = raw_mode?
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
post_get = is_raw ? " v k q s\r\n" : " v f k q s\r\n"
|
|
269
|
-
keys.each do |key|
|
|
270
|
-
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
271
|
-
write(base64 ? "mg #{encoded_key} b#{post_get}" : "mg #{encoded_key}#{post_get}")
|
|
272
|
-
end
|
|
273
|
-
write("mn\r\n")
|
|
274
|
-
@connection_manager.flush
|
|
275
|
-
|
|
241
|
+
buffer = RequestFormatter.multi_meta_get(keys, skip_flags: is_raw)
|
|
242
|
+
flushed_write(buffer)
|
|
243
|
+
buffer.clear
|
|
276
244
|
read_multi_get_responses(is_raw)
|
|
277
245
|
end
|
|
278
246
|
|
|
@@ -295,7 +263,8 @@ module Dalli
|
|
|
295
263
|
raw_key = tokens[key_index]
|
|
296
264
|
return unless raw_key
|
|
297
265
|
|
|
298
|
-
key =
|
|
266
|
+
key = raw_key[1..]
|
|
267
|
+
key = KeyRegularizer.decode(key) if tokens.include?('b')
|
|
299
268
|
bitflags = is_raw ? 0 : response_processor.bitflags_from_tokens(tokens)
|
|
300
269
|
[key, @value_marshaller.retrieve(value, bitflags)]
|
|
301
270
|
end
|
|
@@ -304,33 +273,23 @@ module Dalli
|
|
|
304
273
|
# minimize per-key overhead. Avoids PipelinedSetter server grouping.
|
|
305
274
|
def write_multi_req(pairs, ttl, req_options)
|
|
306
275
|
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
|
307
|
-
pairs.
|
|
308
|
-
|
|
309
|
-
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
310
|
-
# Inline format: "ms <key> <size> c [b] F<flags> T<ttl> MS q\r\n"
|
|
311
|
-
cmd = "ms #{encoded_key} #{value.bytesize} c"
|
|
312
|
-
cmd << ' b' if base64
|
|
313
|
-
cmd << " F#{bitflags}" if bitflags
|
|
314
|
-
cmd << " T#{ttl}" if ttl
|
|
315
|
-
cmd << " MS q\r\n"
|
|
316
|
-
write(cmd)
|
|
317
|
-
write(value)
|
|
318
|
-
write(TERMINATOR)
|
|
276
|
+
entries = pairs.map do |key, raw_value|
|
|
277
|
+
[key, @value_marshaller.store(key, raw_value, req_options)]
|
|
319
278
|
end
|
|
320
|
-
|
|
279
|
+
|
|
280
|
+
buffer = RequestFormatter.multi_meta_set(entries, ttl: ttl)
|
|
281
|
+
flushed_write(buffer)
|
|
282
|
+
buffer.clear
|
|
321
283
|
response_processor.consume_all_responses_until_mn
|
|
322
284
|
end
|
|
323
285
|
|
|
324
286
|
# Single-server fast path for delete_multi. Writes all quiet delete requests
|
|
325
287
|
# terminated by a noop, then consumes all responses.
|
|
326
288
|
def delete_multi_req(keys)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
end
|
|
332
|
-
write_noop
|
|
333
|
-
response_processor.consume_all_responses_until_mn
|
|
289
|
+
buffer = RequestFormatter.multi_meta_delete(keys)
|
|
290
|
+
flushed_write(buffer)
|
|
291
|
+
buffer.clear
|
|
292
|
+
keys.size - response_processor.pipelined_delete_non_deletions
|
|
334
293
|
end
|
|
335
294
|
|
|
336
295
|
require_relative 'key_regularizer'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# frozen_string_literal:
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Dalli
|
|
4
4
|
module Protocol
|
|
@@ -7,14 +7,13 @@ module Dalli
|
|
|
7
7
|
# Class that encapsulates logic for formatting meta protocol requests
|
|
8
8
|
# to memcached.
|
|
9
9
|
##
|
|
10
|
-
|
|
10
|
+
module RequestFormatter
|
|
11
|
+
extend self
|
|
12
|
+
|
|
11
13
|
# Since these are string construction methods, we're going to disable these
|
|
12
14
|
# Rubocop directives. We really can't make this construction much simpler,
|
|
13
15
|
# and introducing an intermediate object seems like overkill.
|
|
14
16
|
#
|
|
15
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
16
|
-
# rubocop:disable Metrics/ParameterLists
|
|
17
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
18
17
|
#
|
|
19
18
|
# Meta get flags:
|
|
20
19
|
#
|
|
@@ -35,16 +34,15 @@ module Dalli
|
|
|
35
34
|
# - Z: Client lost the recache race (another client is already regenerating)
|
|
36
35
|
# - h0/h1: Hit status (0 = first access, 1 = previously accessed)
|
|
37
36
|
# - l<N>: Seconds since last access
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cmd = "mg #{key}"
|
|
37
|
+
def meta_get(key:, value: true, return_cas: false, ttl: nil, quiet: false,
|
|
38
|
+
vivify_ttl: nil, recache_ttl: nil,
|
|
39
|
+
return_hit_status: false, return_last_access: false, skip_lru_bump: false,
|
|
40
|
+
skip_flags: false)
|
|
41
|
+
cmd = "mg #{encoded_key(key)}"
|
|
43
42
|
# In raw mode (skip_flags: true), we don't request bitflags since they're not used.
|
|
44
43
|
# This saves 2 bytes per request and skips parsing on response.
|
|
45
44
|
cmd << (skip_flags ? ' v' : ' v f') if value
|
|
46
45
|
cmd << ' c' if return_cas
|
|
47
|
-
cmd << ' b' if base64
|
|
48
46
|
cmd << " T#{ttl}" if ttl
|
|
49
47
|
cmd << ' k q s' if quiet # Return the key in the response if quiet
|
|
50
48
|
cmd << " N#{vivify_ttl}" if vivify_ttl # Thundering herd: vivify on miss
|
|
@@ -52,12 +50,27 @@ module Dalli
|
|
|
52
50
|
cmd << ' h' if return_hit_status # Return hit status (0 or 1)
|
|
53
51
|
cmd << ' l' if return_last_access # Return seconds since last access
|
|
54
52
|
cmd << ' u' if skip_lru_bump # Don't bump LRU or update access stats
|
|
55
|
-
cmd
|
|
53
|
+
cmd << TERMINATOR
|
|
56
54
|
end
|
|
57
55
|
|
|
58
|
-
def
|
|
56
|
+
def multi_meta_get(keys, skip_flags: false)
|
|
57
|
+
# In raw mode: "mg <key> v k q s\r\n" (no f flag, key at index 2)
|
|
58
|
+
# Normal mode: "mg <key> v f k q s\r\n" (key at index 3)
|
|
59
|
+
post_get = skip_flags ? " v k q s\r\n" : " v f k q s\r\n"
|
|
60
|
+
buffer = ''.b
|
|
61
|
+
keys.each do |key|
|
|
62
|
+
buffer << 'mg ' << encoded_key(key) << post_get
|
|
63
|
+
end
|
|
64
|
+
buffer << 'mn' << TERMINATOR
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, quiet: false)
|
|
68
|
+
base64 = KeyRegularizer.required?(key)
|
|
69
|
+
key = KeyRegularizer.encode(key) if base64
|
|
59
70
|
cmd = "ms #{key} #{value.bytesize}"
|
|
60
|
-
|
|
71
|
+
# Skip the cas-return flag in quiet mode: the response is suppressed,
|
|
72
|
+
# so requesting it only adds bytes to the request.
|
|
73
|
+
cmd << ' c' if !quiet && !%i[append prepend].include?(mode)
|
|
61
74
|
cmd << ' b' if base64
|
|
62
75
|
cmd << " F#{bitflags}" if bitflags
|
|
63
76
|
cmd << cas_string(cas)
|
|
@@ -67,22 +80,46 @@ module Dalli
|
|
|
67
80
|
cmd << TERMINATOR
|
|
68
81
|
end
|
|
69
82
|
|
|
83
|
+
def multi_meta_set(entries, ttl: nil)
|
|
84
|
+
buffer = ''.b
|
|
85
|
+
entries.each do |key, pair|
|
|
86
|
+
value, bitflags = pair
|
|
87
|
+
|
|
88
|
+
base64 = KeyRegularizer.required?(key)
|
|
89
|
+
key = KeyRegularizer.encode(key) if base64
|
|
90
|
+
|
|
91
|
+
# Inline format: "ms <key> <size> c [b] F<flags> T<ttl> MS q\r\n"
|
|
92
|
+
buffer << "ms #{key} #{value.bytesize} c"
|
|
93
|
+
buffer << ' b' if base64
|
|
94
|
+
buffer << " F#{bitflags}" if bitflags
|
|
95
|
+
buffer << " T#{ttl}" if ttl
|
|
96
|
+
buffer << ' MS q' << TERMINATOR << value << TERMINATOR
|
|
97
|
+
end
|
|
98
|
+
buffer << META_NOOP
|
|
99
|
+
end
|
|
100
|
+
|
|
70
101
|
# Thundering herd protection flag:
|
|
71
102
|
# - stale (I flag): Instead of deleting the item, mark it as stale. Other clients
|
|
72
103
|
# using N/R flags will see the X flag and know the item is being regenerated.
|
|
73
|
-
def
|
|
74
|
-
cmd = "md #{key}"
|
|
75
|
-
cmd << ' b' if base64
|
|
104
|
+
def meta_delete(key:, cas: nil, ttl: nil, quiet: false, stale: false)
|
|
105
|
+
cmd = "md #{encoded_key(key)}"
|
|
76
106
|
cmd << cas_string(cas)
|
|
77
107
|
cmd << " T#{ttl}" if ttl
|
|
78
108
|
cmd << ' I' if stale # Mark stale instead of deleting
|
|
79
109
|
cmd << ' q' if quiet
|
|
80
|
-
cmd
|
|
110
|
+
cmd << TERMINATOR
|
|
81
111
|
end
|
|
82
112
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
113
|
+
def multi_meta_delete(keys)
|
|
114
|
+
buffer = ''.b
|
|
115
|
+
keys.each do |key|
|
|
116
|
+
buffer << 'md ' << encoded_key(key) << ' q' << TERMINATOR
|
|
117
|
+
end
|
|
118
|
+
buffer << META_NOOP
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, quiet: false)
|
|
122
|
+
cmd = "ma #{encoded_key(key)} v"
|
|
86
123
|
cmd << " D#{delta}" if delta
|
|
87
124
|
cmd << " J#{initial}" if initial
|
|
88
125
|
# Always set a TTL if an initial value is specified
|
|
@@ -90,38 +127,49 @@ module Dalli
|
|
|
90
127
|
cmd << cas_string(cas)
|
|
91
128
|
cmd << ' q' if quiet
|
|
92
129
|
cmd << " M#{incr ? 'I' : 'D'}"
|
|
93
|
-
cmd
|
|
130
|
+
cmd << TERMINATOR
|
|
94
131
|
end
|
|
95
132
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
96
133
|
# rubocop:enable Metrics/ParameterLists
|
|
97
134
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
98
135
|
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
META_NOOP = "mn#{TERMINATOR}".freeze
|
|
137
|
+
def meta_noop
|
|
138
|
+
META_NOOP
|
|
101
139
|
end
|
|
102
140
|
|
|
103
|
-
def
|
|
141
|
+
def version
|
|
104
142
|
"version#{TERMINATOR}"
|
|
105
143
|
end
|
|
106
144
|
|
|
107
|
-
def
|
|
145
|
+
def flush(delay: nil, quiet: false)
|
|
108
146
|
cmd = +'flush_all'
|
|
109
147
|
cmd << " #{parse_to_64_bit_int(delay, 0)}" if delay
|
|
110
148
|
cmd << ' noreply' if quiet
|
|
111
|
-
cmd
|
|
149
|
+
cmd << TERMINATOR
|
|
112
150
|
end
|
|
113
151
|
|
|
114
152
|
ALLOWED_STATS_ARGS = [nil, '', 'items', 'slabs', 'settings', 'reset'].freeze
|
|
115
153
|
|
|
116
|
-
def
|
|
154
|
+
def stats(arg = nil)
|
|
117
155
|
raise ArgumentError, "Invalid stats argument: #{arg.inspect}" unless ALLOWED_STATS_ARGS.include?(arg)
|
|
118
156
|
|
|
119
157
|
cmd = +'stats'
|
|
120
158
|
cmd << " #{arg}" if arg && !arg.empty?
|
|
121
|
-
cmd
|
|
159
|
+
cmd << TERMINATOR
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def encoded_key(key)
|
|
163
|
+
if KeyRegularizer.required?(key)
|
|
164
|
+
KeyRegularizer.encode(key) << ' b'
|
|
165
|
+
else
|
|
166
|
+
key
|
|
167
|
+
end
|
|
122
168
|
end
|
|
123
169
|
|
|
124
|
-
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def mode_to_token(mode)
|
|
125
173
|
case mode
|
|
126
174
|
when :add
|
|
127
175
|
'E'
|
|
@@ -136,12 +184,12 @@ module Dalli
|
|
|
136
184
|
end
|
|
137
185
|
end
|
|
138
186
|
|
|
139
|
-
def
|
|
187
|
+
def cas_string(cas)
|
|
140
188
|
cas = parse_to_64_bit_int(cas, nil)
|
|
141
189
|
cas.nil? || cas.zero? ? '' : " C#{cas}"
|
|
142
190
|
end
|
|
143
191
|
|
|
144
|
-
def
|
|
192
|
+
def parse_to_64_bit_int(val, default)
|
|
145
193
|
val.nil? ? nil : Integer(val)
|
|
146
194
|
rescue ArgumentError
|
|
147
195
|
# Sanitize to default if it isn't parsable as an integer
|
|
@@ -5,16 +5,16 @@ require 'timeout'
|
|
|
5
5
|
|
|
6
6
|
module Dalli
|
|
7
7
|
module Protocol
|
|
8
|
+
# Compact the buffer when the consumed portion exceeds this
|
|
9
|
+
# threshold and represents more than half the buffer
|
|
10
|
+
COMPACT_THRESHOLD = 4096
|
|
11
|
+
|
|
8
12
|
##
|
|
9
13
|
# Manages the buffer for responses from memcached.
|
|
10
14
|
# Uses an offset-based approach to avoid string allocations
|
|
11
15
|
# when advancing through parsed responses.
|
|
12
16
|
##
|
|
13
17
|
class ResponseBuffer
|
|
14
|
-
# Compact the buffer when the consumed portion exceeds this
|
|
15
|
-
# threshold and represents more than half the buffer
|
|
16
|
-
COMPACT_THRESHOLD = 4096
|
|
17
|
-
|
|
18
18
|
def initialize(io_source, response_processor)
|
|
19
19
|
@io_source = io_source
|
|
20
20
|
@response_processor = response_processor
|
|
@@ -23,22 +23,32 @@ module Dalli
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def read
|
|
26
|
-
@buffer
|
|
26
|
+
remaining_bytes = @buffer.bytesize - @offset
|
|
27
|
+
if remaining_bytes.zero?
|
|
28
|
+
@offset = 0
|
|
29
|
+
@buffer = @io_source.read_available(@buffer)
|
|
30
|
+
else
|
|
31
|
+
if @offset > COMPACT_THRESHOLD && @offset > (@buffer.bytesize / 2)
|
|
32
|
+
@buffer.bytesplice(0, @offset, '')
|
|
33
|
+
@offset = 0
|
|
34
|
+
end
|
|
35
|
+
@buffer << @io_source.read_available
|
|
36
|
+
end
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
# Attempts to process a single response from the buffer,
|
|
30
40
|
# advancing the offset past the consumed bytes.
|
|
31
41
|
def process_single_getk_response
|
|
32
|
-
|
|
33
|
-
@offset +=
|
|
34
|
-
|
|
35
|
-
[status, cas, key, value]
|
|
42
|
+
response = @response_processor.getk_response_from_buffer(@buffer, @offset)
|
|
43
|
+
@offset += response.pop
|
|
44
|
+
response
|
|
36
45
|
end
|
|
37
46
|
|
|
38
47
|
# Resets the internal buffer to an empty state,
|
|
39
48
|
# so that we're ready to read pipelined responses
|
|
40
49
|
def reset
|
|
41
|
-
@buffer
|
|
50
|
+
@buffer&.clear
|
|
51
|
+
@buffer ||= ''.b
|
|
42
52
|
@offset = 0
|
|
43
53
|
end
|
|
44
54
|
|
|
@@ -54,6 +64,7 @@ module Dalli
|
|
|
54
64
|
|
|
55
65
|
# Clear the internal response buffer
|
|
56
66
|
def clear
|
|
67
|
+
@buffer&.clear
|
|
57
68
|
@buffer = nil
|
|
58
69
|
@offset = 0
|
|
59
70
|
end
|
|
@@ -61,18 +72,6 @@ module Dalli
|
|
|
61
72
|
def in_progress?
|
|
62
73
|
!@buffer.nil?
|
|
63
74
|
end
|
|
64
|
-
|
|
65
|
-
private
|
|
66
|
-
|
|
67
|
-
# Only compact when we've consumed a significant portion of the buffer.
|
|
68
|
-
# This avoids per-response string allocation while preventing unbounded
|
|
69
|
-
# memory growth for large pipelines.
|
|
70
|
-
def compact_if_needed
|
|
71
|
-
return unless @offset > COMPACT_THRESHOLD && @offset > @buffer.bytesize / 2
|
|
72
|
-
|
|
73
|
-
@buffer = @buffer.byteslice(@offset..)
|
|
74
|
-
@offset = 0
|
|
75
|
-
end
|
|
76
75
|
end
|
|
77
76
|
end
|
|
78
77
|
end
|