dalli 4.3.2 → 5.0.2
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 +96 -0
- data/Gemfile +1 -0
- data/README.md +4 -16
- data/lib/dalli/client.rb +72 -46
- data/lib/dalli/instrumentation.rb +3 -1
- data/lib/dalli/options.rb +1 -1
- data/lib/dalli/pid_cache.rb +1 -1
- data/lib/dalli/pipelined_getter.rb +9 -16
- data/lib/dalli/protocol/base.rb +28 -19
- data/lib/dalli/protocol/connection_manager.rb +9 -3
- data/lib/dalli/protocol/meta.rb +77 -5
- data/lib/dalli/protocol/response_buffer.rb +27 -12
- data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +10 -22
- data/lib/dalli/protocol/server_config_parser.rb +1 -1
- data/lib/dalli/ring.rb +2 -2
- data/lib/dalli/servers_arg_normalizer.rb +1 -1
- data/lib/dalli/socket.rb +4 -0
- data/lib/dalli/version.rb +2 -2
- data/lib/dalli.rb +2 -4
- metadata +5 -11
- data/lib/dalli/protocol/binary/request_formatter.rb +0 -117
- data/lib/dalli/protocol/binary/response_header.rb +0 -36
- data/lib/dalli/protocol/binary/response_processor.rb +0 -239
- data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
- data/lib/dalli/protocol/binary.rb +0 -200
- data/lib/dalli/protocol_deprecations.rb +0 -45
- /data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +0 -0
- /data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +0 -0
data/lib/dalli/protocol/meta.rb
CHANGED
|
@@ -257,13 +257,85 @@ module Dalli
|
|
|
257
257
|
@connection_manager.flush
|
|
258
258
|
end
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
# Single-server fast path for get_multi. Inlines request formatting and
|
|
261
|
+
# response parsing to minimize per-key overhead. Avoids the PipelinedGetter
|
|
262
|
+
# machinery (IO.select, response buffering, server grouping).
|
|
263
|
+
def read_multi_req(keys)
|
|
264
|
+
is_raw = raw_mode?
|
|
265
|
+
# Inline request formatting — avoids RequestFormatter.meta_get overhead per key.
|
|
266
|
+
# In raw mode: "mg <key> v k q s\r\n" (no f flag, key at index 2)
|
|
267
|
+
# Normal mode: "mg <key> v f k q s\r\n" (key at index 3)
|
|
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
|
+
|
|
276
|
+
read_multi_get_responses(is_raw)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def read_multi_get_responses(is_raw)
|
|
280
|
+
hash = {}
|
|
281
|
+
key_index = is_raw ? 2 : 3
|
|
282
|
+
while (line = @connection_manager.read_line)
|
|
283
|
+
break if line.start_with?('MN')
|
|
284
|
+
next unless line.start_with?('VA ')
|
|
285
|
+
|
|
286
|
+
key, value = parse_multi_get_value(line, key_index, is_raw)
|
|
287
|
+
hash[key] = value if key
|
|
288
|
+
end
|
|
289
|
+
hash
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def parse_multi_get_value(line, key_index, is_raw)
|
|
293
|
+
tokens = line.chomp!(TERMINATOR).split
|
|
294
|
+
value = @connection_manager.read(tokens[1].to_i + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
|
|
295
|
+
raw_key = tokens[key_index]
|
|
296
|
+
return unless raw_key
|
|
297
|
+
|
|
298
|
+
key = KeyRegularizer.decode(raw_key[1..], tokens.include?('b'))
|
|
299
|
+
bitflags = is_raw ? 0 : response_processor.bitflags_from_tokens(tokens)
|
|
300
|
+
[key, @value_marshaller.retrieve(value, bitflags)]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Single-server fast path for set_multi. Inlines request formatting to
|
|
304
|
+
# minimize per-key overhead. Avoids PipelinedSetter server grouping.
|
|
305
|
+
def write_multi_req(pairs, ttl, req_options)
|
|
306
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
|
307
|
+
pairs.each do |key, raw_value|
|
|
308
|
+
(value, bitflags) = @value_marshaller.store(key, raw_value, req_options)
|
|
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)
|
|
319
|
+
end
|
|
320
|
+
write_noop
|
|
321
|
+
response_processor.consume_all_responses_until_mn
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Single-server fast path for delete_multi. Writes all quiet delete requests
|
|
325
|
+
# terminated by a noop, then consumes all responses.
|
|
326
|
+
def delete_multi_req(keys)
|
|
327
|
+
keys.each do |key|
|
|
328
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
|
329
|
+
# Inline format: "md <key> [b] q\r\n"
|
|
330
|
+
write(base64 ? "md #{encoded_key} b q\r\n" : "md #{encoded_key} q\r\n")
|
|
331
|
+
end
|
|
332
|
+
write_noop
|
|
333
|
+
response_processor.consume_all_responses_until_mn
|
|
262
334
|
end
|
|
263
335
|
|
|
264
|
-
require_relative '
|
|
265
|
-
require_relative '
|
|
266
|
-
require_relative '
|
|
336
|
+
require_relative 'key_regularizer'
|
|
337
|
+
require_relative 'request_formatter'
|
|
338
|
+
require_relative 'response_processor'
|
|
267
339
|
end
|
|
268
340
|
end
|
|
269
341
|
end
|
|
@@ -7,38 +7,39 @@ module Dalli
|
|
|
7
7
|
module Protocol
|
|
8
8
|
##
|
|
9
9
|
# Manages the buffer for responses from memcached.
|
|
10
|
+
# Uses an offset-based approach to avoid string allocations
|
|
11
|
+
# when advancing through parsed responses.
|
|
10
12
|
##
|
|
11
13
|
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
|
+
|
|
12
18
|
def initialize(io_source, response_processor)
|
|
13
19
|
@io_source = io_source
|
|
14
20
|
@response_processor = response_processor
|
|
15
21
|
@buffer = nil
|
|
22
|
+
@offset = 0
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
def read
|
|
19
26
|
@buffer << @io_source.read_nonblock
|
|
20
27
|
end
|
|
21
28
|
|
|
22
|
-
# Attempts to process a single response from the buffer
|
|
23
|
-
#
|
|
29
|
+
# Attempts to process a single response from the buffer,
|
|
30
|
+
# advancing the offset past the consumed bytes.
|
|
24
31
|
def process_single_getk_response
|
|
25
|
-
bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer)
|
|
26
|
-
|
|
32
|
+
bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer, @offset)
|
|
33
|
+
@offset += bytes
|
|
34
|
+
compact_if_needed
|
|
27
35
|
[status, cas, key, value]
|
|
28
36
|
end
|
|
29
37
|
|
|
30
|
-
# Advances the internal response buffer by bytes_to_advance
|
|
31
|
-
# bytes. The
|
|
32
|
-
def advance(bytes_to_advance)
|
|
33
|
-
return unless bytes_to_advance.positive?
|
|
34
|
-
|
|
35
|
-
@buffer = @buffer.byteslice(bytes_to_advance..-1)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
38
|
# Resets the internal buffer to an empty state,
|
|
39
39
|
# so that we're ready to read pipelined responses
|
|
40
40
|
def reset
|
|
41
41
|
@buffer = ''.b
|
|
42
|
+
@offset = 0
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
# Ensures the buffer is initialized for reading without discarding
|
|
@@ -48,16 +49,30 @@ module Dalli
|
|
|
48
49
|
return if in_progress?
|
|
49
50
|
|
|
50
51
|
@buffer = ''.b
|
|
52
|
+
@offset = 0
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
# Clear the internal response buffer
|
|
54
56
|
def clear
|
|
55
57
|
@buffer = nil
|
|
58
|
+
@offset = 0
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
def in_progress?
|
|
59
62
|
!@buffer.nil?
|
|
60
63
|
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
|
|
61
76
|
end
|
|
62
77
|
end
|
|
63
78
|
end
|
|
@@ -147,14 +147,6 @@ module Dalli
|
|
|
147
147
|
true
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
def tokens_from_header_buffer(buf)
|
|
151
|
-
header = header_from_buffer(buf)
|
|
152
|
-
tokens = header.split
|
|
153
|
-
header_len = header.bytesize + TERMINATOR.length
|
|
154
|
-
body_len = body_len_from_tokens(tokens)
|
|
155
|
-
[tokens, header_len, body_len]
|
|
156
|
-
end
|
|
157
|
-
|
|
158
150
|
def full_response_from_buffer(tokens, body, resp_size)
|
|
159
151
|
value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
|
|
160
152
|
[resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
|
|
@@ -170,11 +162,15 @@ module Dalli
|
|
|
170
162
|
# The remaining three values in the array are the ResponseHeader,
|
|
171
163
|
# key, and value.
|
|
172
164
|
##
|
|
173
|
-
def getk_response_from_buffer(buf)
|
|
174
|
-
#
|
|
175
|
-
|
|
165
|
+
def getk_response_from_buffer(buf, offset = 0)
|
|
166
|
+
# Find the header terminator starting from offset
|
|
167
|
+
term_idx = buf.index(TERMINATOR, offset)
|
|
168
|
+
return [0, nil, nil, nil, nil] unless term_idx
|
|
176
169
|
|
|
177
|
-
|
|
170
|
+
header = buf.byteslice(offset, term_idx - offset)
|
|
171
|
+
tokens = header.split
|
|
172
|
+
header_len = header.bytesize + TERMINATOR.length
|
|
173
|
+
body_len = body_len_from_tokens(tokens)
|
|
178
174
|
|
|
179
175
|
# We have a complete response that has no body.
|
|
180
176
|
# This is either the response to the terminating
|
|
@@ -185,22 +181,14 @@ module Dalli
|
|
|
185
181
|
resp_size = header_len + body_len + TERMINATOR.length
|
|
186
182
|
# The header is in the buffer, but the body is not. As we don't have
|
|
187
183
|
# a complete response, don't advance the buffer
|
|
188
|
-
return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
|
|
184
|
+
return [0, nil, nil, nil, nil] unless buf.bytesize >= offset + resp_size
|
|
189
185
|
|
|
190
186
|
# The full response is in our buffer, so parse it and return
|
|
191
187
|
# the values
|
|
192
|
-
body = buf.
|
|
188
|
+
body = buf.byteslice(offset + header_len, body_len)
|
|
193
189
|
full_response_from_buffer(tokens, body, resp_size)
|
|
194
190
|
end
|
|
195
191
|
|
|
196
|
-
def contains_header?(buf)
|
|
197
|
-
buf.include?(TERMINATOR)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def header_from_buffer(buf)
|
|
201
|
-
buf.split(TERMINATOR, 2).first
|
|
202
|
-
end
|
|
203
|
-
|
|
204
192
|
def error_on_unexpected!(expected_codes)
|
|
205
193
|
tokens = next_line_to_tokens
|
|
206
194
|
|
|
@@ -6,7 +6,7 @@ module Dalli
|
|
|
6
6
|
module Protocol
|
|
7
7
|
##
|
|
8
8
|
# Dalli::Protocol::ServerConfigParser parses a server string passed to
|
|
9
|
-
# a Dalli::Protocol::
|
|
9
|
+
# a Dalli::Protocol::Meta instance into the hostname, port, weight, and
|
|
10
10
|
# socket_type.
|
|
11
11
|
##
|
|
12
12
|
class ServerConfigParser
|
data/lib/dalli/ring.rb
CHANGED
|
@@ -23,9 +23,9 @@ module Dalli
|
|
|
23
23
|
|
|
24
24
|
attr_accessor :servers, :continuum
|
|
25
25
|
|
|
26
|
-
def initialize(servers_arg,
|
|
26
|
+
def initialize(servers_arg, options)
|
|
27
27
|
@servers = servers_arg.map do |s|
|
|
28
|
-
|
|
28
|
+
Dalli::Protocol::Meta.new(s, options)
|
|
29
29
|
end
|
|
30
30
|
@continuum = nil
|
|
31
31
|
@continuum = build_continuum(servers) if servers.size > 1
|
|
@@ -16,7 +16,7 @@ module Dalli
|
|
|
16
16
|
# weight are optional (e.g. 'localhost', 'abc.com:12345', 'example.org:22222:3')
|
|
17
17
|
# * A colon separated string of (UNIX socket, weight) where the weight is optional
|
|
18
18
|
# (e.g. '/var/run/memcached/socket', '/tmp/xyz:3') (not supported on Windows)
|
|
19
|
-
# * A URI with a 'memcached' protocol
|
|
19
|
+
# * A URI with a 'memcached' protocol (e.g. 'memcached://localhost:11211')
|
|
20
20
|
#
|
|
21
21
|
# The methods in this module do not validate the format of individual server strings, but
|
|
22
22
|
# rather normalize the argument into a compact array, wherein each array entry corresponds
|
data/lib/dalli/socket.rb
CHANGED
|
@@ -108,12 +108,14 @@ module Dalli
|
|
|
108
108
|
# Detect and cache whether TCPSocket supports the connect_timeout: keyword argument.
|
|
109
109
|
# Returns false if TCPSocket#initialize has been monkey-patched by gems like
|
|
110
110
|
# socksify or resolv-replace, which don't support keyword arguments.
|
|
111
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
111
112
|
def self.supports_connect_timeout?
|
|
112
113
|
return @supports_connect_timeout if defined?(@supports_connect_timeout)
|
|
113
114
|
|
|
114
115
|
@supports_connect_timeout = RUBY_VERSION >= '3.0' &&
|
|
115
116
|
::TCPSocket.instance_method(:initialize).parameters == TCPSOCKET_NATIVE_PARAMETERS
|
|
116
117
|
end
|
|
118
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
117
119
|
|
|
118
120
|
def self.create_socket_with_timeout(host, port, options)
|
|
119
121
|
if supports_connect_timeout?
|
|
@@ -170,12 +172,14 @@ module Dalli
|
|
|
170
172
|
|
|
171
173
|
# Detect and cache the correct pack format for struct timeval on this platform.
|
|
172
174
|
# Different architectures have different sizes for time_t and suseconds_t.
|
|
175
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
173
176
|
def self.timeval_pack_format(sock)
|
|
174
177
|
@timeval_pack_format ||= begin
|
|
175
178
|
expected_size = sock.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO).data.bytesize
|
|
176
179
|
TIMEVAL_PACK_FORMATS.find { |fmt| TIMEVAL_TEST_VALUES.pack(fmt).bytesize == expected_size } || 'll'
|
|
177
180
|
end
|
|
178
181
|
end
|
|
182
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
179
183
|
|
|
180
184
|
def self.pack_timeval(sock, seconds, microseconds)
|
|
181
185
|
[seconds, microseconds].pack(timeval_pack_format(sock))
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Dalli
|
|
|
38
38
|
QUIET = :dalli_multi
|
|
39
39
|
|
|
40
40
|
def self.logger
|
|
41
|
-
@logger ||= rails_logger || default_logger
|
|
41
|
+
@logger ||= rails_logger || default_logger # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def self.rails_logger
|
|
@@ -54,7 +54,7 @@ module Dalli
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def self.logger=(logger)
|
|
57
|
-
@logger = logger
|
|
57
|
+
@logger = logger # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -62,7 +62,6 @@ require_relative 'dalli/version'
|
|
|
62
62
|
require_relative 'dalli/instrumentation'
|
|
63
63
|
|
|
64
64
|
require_relative 'dalli/compressor'
|
|
65
|
-
require_relative 'dalli/protocol_deprecations'
|
|
66
65
|
require_relative 'dalli/client'
|
|
67
66
|
require_relative 'dalli/key_manager'
|
|
68
67
|
require_relative 'dalli/pipelined_getter'
|
|
@@ -71,7 +70,6 @@ require_relative 'dalli/pipelined_deleter'
|
|
|
71
70
|
require_relative 'dalli/ring'
|
|
72
71
|
require_relative 'dalli/protocol'
|
|
73
72
|
require_relative 'dalli/protocol/base'
|
|
74
|
-
require_relative 'dalli/protocol/binary'
|
|
75
73
|
require_relative 'dalli/protocol/connection_manager'
|
|
76
74
|
require_relative 'dalli/protocol/meta'
|
|
77
75
|
require_relative 'dalli/protocol/response_buffer'
|
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:
|
|
4
|
+
version: 5.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter M. Goldstein
|
|
@@ -49,24 +49,18 @@ files:
|
|
|
49
49
|
- lib/dalli/pipelined_setter.rb
|
|
50
50
|
- lib/dalli/protocol.rb
|
|
51
51
|
- lib/dalli/protocol/base.rb
|
|
52
|
-
- lib/dalli/protocol/binary.rb
|
|
53
|
-
- lib/dalli/protocol/binary/request_formatter.rb
|
|
54
|
-
- lib/dalli/protocol/binary/response_header.rb
|
|
55
|
-
- lib/dalli/protocol/binary/response_processor.rb
|
|
56
|
-
- lib/dalli/protocol/binary/sasl_authentication.rb
|
|
57
52
|
- lib/dalli/protocol/connection_manager.rb
|
|
53
|
+
- lib/dalli/protocol/key_regularizer.rb
|
|
58
54
|
- lib/dalli/protocol/meta.rb
|
|
59
|
-
- lib/dalli/protocol/
|
|
60
|
-
- lib/dalli/protocol/meta/request_formatter.rb
|
|
61
|
-
- lib/dalli/protocol/meta/response_processor.rb
|
|
55
|
+
- lib/dalli/protocol/request_formatter.rb
|
|
62
56
|
- lib/dalli/protocol/response_buffer.rb
|
|
57
|
+
- lib/dalli/protocol/response_processor.rb
|
|
63
58
|
- lib/dalli/protocol/server_config_parser.rb
|
|
64
59
|
- lib/dalli/protocol/string_marshaller.rb
|
|
65
60
|
- lib/dalli/protocol/ttl_sanitizer.rb
|
|
66
61
|
- lib/dalli/protocol/value_compressor.rb
|
|
67
62
|
- lib/dalli/protocol/value_marshaller.rb
|
|
68
63
|
- lib/dalli/protocol/value_serializer.rb
|
|
69
|
-
- lib/dalli/protocol_deprecations.rb
|
|
70
64
|
- lib/dalli/ring.rb
|
|
71
65
|
- lib/dalli/servers_arg_normalizer.rb
|
|
72
66
|
- lib/dalli/socket.rb
|
|
@@ -86,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
86
80
|
requirements:
|
|
87
81
|
- - ">="
|
|
88
82
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '3.
|
|
83
|
+
version: '3.3'
|
|
90
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
85
|
requirements:
|
|
92
86
|
- - ">="
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dalli
|
|
4
|
-
module Protocol
|
|
5
|
-
class Binary
|
|
6
|
-
##
|
|
7
|
-
# Class that encapsulates logic for formatting binary protocol requests
|
|
8
|
-
# to memcached.
|
|
9
|
-
##
|
|
10
|
-
class RequestFormatter
|
|
11
|
-
REQUEST = 0x80
|
|
12
|
-
|
|
13
|
-
OPCODES = {
|
|
14
|
-
get: 0x00,
|
|
15
|
-
set: 0x01,
|
|
16
|
-
add: 0x02,
|
|
17
|
-
replace: 0x03,
|
|
18
|
-
delete: 0x04,
|
|
19
|
-
incr: 0x05,
|
|
20
|
-
decr: 0x06,
|
|
21
|
-
flush: 0x08,
|
|
22
|
-
noop: 0x0A,
|
|
23
|
-
version: 0x0B,
|
|
24
|
-
getkq: 0x0D,
|
|
25
|
-
append: 0x0E,
|
|
26
|
-
prepend: 0x0F,
|
|
27
|
-
stat: 0x10,
|
|
28
|
-
setq: 0x11,
|
|
29
|
-
addq: 0x12,
|
|
30
|
-
replaceq: 0x13,
|
|
31
|
-
deleteq: 0x14,
|
|
32
|
-
incrq: 0x15,
|
|
33
|
-
decrq: 0x16,
|
|
34
|
-
flushq: 0x18,
|
|
35
|
-
appendq: 0x19,
|
|
36
|
-
prependq: 0x1A,
|
|
37
|
-
touch: 0x1C,
|
|
38
|
-
gat: 0x1D,
|
|
39
|
-
auth_negotiation: 0x20,
|
|
40
|
-
auth_request: 0x21,
|
|
41
|
-
auth_continue: 0x22
|
|
42
|
-
}.freeze
|
|
43
|
-
|
|
44
|
-
REQ_HEADER_FORMAT = 'CCnCCnNNQ'
|
|
45
|
-
|
|
46
|
-
KEY_ONLY = 'a*'
|
|
47
|
-
TTL_AND_KEY = 'Na*'
|
|
48
|
-
KEY_AND_VALUE = 'a*a*'
|
|
49
|
-
INCR_DECR = 'NNNNNa*'
|
|
50
|
-
TTL_ONLY = 'N'
|
|
51
|
-
NO_BODY = ''
|
|
52
|
-
|
|
53
|
-
BODY_FORMATS = {
|
|
54
|
-
get: KEY_ONLY,
|
|
55
|
-
getkq: KEY_ONLY,
|
|
56
|
-
delete: KEY_ONLY,
|
|
57
|
-
deleteq: KEY_ONLY,
|
|
58
|
-
stat: KEY_ONLY,
|
|
59
|
-
|
|
60
|
-
append: KEY_AND_VALUE,
|
|
61
|
-
prepend: KEY_AND_VALUE,
|
|
62
|
-
appendq: KEY_AND_VALUE,
|
|
63
|
-
prependq: KEY_AND_VALUE,
|
|
64
|
-
auth_request: KEY_AND_VALUE,
|
|
65
|
-
auth_continue: KEY_AND_VALUE,
|
|
66
|
-
|
|
67
|
-
set: 'NNa*a*',
|
|
68
|
-
setq: 'NNa*a*',
|
|
69
|
-
add: 'NNa*a*',
|
|
70
|
-
addq: 'NNa*a*',
|
|
71
|
-
replace: 'NNa*a*',
|
|
72
|
-
replaceq: 'NNa*a*',
|
|
73
|
-
|
|
74
|
-
incr: INCR_DECR,
|
|
75
|
-
decr: INCR_DECR,
|
|
76
|
-
incrq: INCR_DECR,
|
|
77
|
-
decrq: INCR_DECR,
|
|
78
|
-
|
|
79
|
-
flush: TTL_ONLY,
|
|
80
|
-
flushq: TTL_ONLY,
|
|
81
|
-
|
|
82
|
-
noop: NO_BODY,
|
|
83
|
-
auth_negotiation: NO_BODY,
|
|
84
|
-
version: NO_BODY,
|
|
85
|
-
|
|
86
|
-
touch: TTL_AND_KEY,
|
|
87
|
-
gat: TTL_AND_KEY
|
|
88
|
-
}.freeze
|
|
89
|
-
FORMAT = BODY_FORMATS.transform_values { |v| REQ_HEADER_FORMAT + v }
|
|
90
|
-
|
|
91
|
-
# rubocop:disable Metrics/ParameterLists
|
|
92
|
-
def self.standard_request(opkey:, key: nil, value: nil, opaque: 0, cas: 0, bitflags: nil, ttl: nil)
|
|
93
|
-
extra_len = (bitflags.nil? ? 0 : 4) + (ttl.nil? ? 0 : 4)
|
|
94
|
-
key_len = key.nil? ? 0 : key.bytesize
|
|
95
|
-
value_len = value.nil? ? 0 : value.bytesize
|
|
96
|
-
header = [REQUEST, OPCODES[opkey], key_len, extra_len, 0, 0, extra_len + key_len + value_len, opaque, cas]
|
|
97
|
-
body = [bitflags, ttl, key, value].compact
|
|
98
|
-
(header + body).pack(FORMAT[opkey])
|
|
99
|
-
end
|
|
100
|
-
# rubocop:enable Metrics/ParameterLists
|
|
101
|
-
|
|
102
|
-
def self.decr_incr_request(opkey:, key: nil, count: nil, initial: nil, expiry: nil)
|
|
103
|
-
extra_len = 20
|
|
104
|
-
(h, l) = as_8byte_uint(count)
|
|
105
|
-
(dh, dl) = as_8byte_uint(initial)
|
|
106
|
-
header = [REQUEST, OPCODES[opkey], key.bytesize, extra_len, 0, 0, key.bytesize + extra_len, 0, 0]
|
|
107
|
-
body = [h, l, dh, dl, expiry, key]
|
|
108
|
-
(header + body).pack(FORMAT[opkey])
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def self.as_8byte_uint(val)
|
|
112
|
-
[val >> 32, val & 0xFFFFFFFF]
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dalli
|
|
4
|
-
module Protocol
|
|
5
|
-
class Binary
|
|
6
|
-
##
|
|
7
|
-
# Class that encapsulates data parsed from a memcached response header.
|
|
8
|
-
##
|
|
9
|
-
class ResponseHeader
|
|
10
|
-
SIZE = 24
|
|
11
|
-
FMT = '@2nCCnNNQ'
|
|
12
|
-
|
|
13
|
-
attr_reader :key_len, :extra_len, :data_type, :status, :body_len, :opaque, :cas
|
|
14
|
-
|
|
15
|
-
def initialize(buf)
|
|
16
|
-
raise ArgumentError, "Response buffer must be at least #{SIZE} bytes" unless buf.bytesize >= SIZE
|
|
17
|
-
|
|
18
|
-
@key_len, @extra_len, @data_type, @status, @body_len, @opaque, @cas = buf.unpack(FMT)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def ok?
|
|
22
|
-
status.zero?
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def not_found?
|
|
26
|
-
status == 1
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
NOT_STORED_STATUSES = [2, 5].freeze
|
|
30
|
-
def not_stored?
|
|
31
|
-
NOT_STORED_STATUSES.include?(status)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|