dalli 3.1.3 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +26 -0
- data/lib/dalli/client.rb +8 -6
- data/lib/dalli/protocol/base.rb +13 -10
- data/lib/dalli/protocol/binary/request_formatter.rb +1 -1
- data/lib/dalli/protocol/binary/response_processor.rb +64 -33
- data/lib/dalli/protocol/binary.rb +8 -15
- data/lib/dalli/protocol/connection_manager.rb +10 -0
- data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
- data/lib/dalli/protocol/meta/request_formatter.rb +108 -0
- data/lib/dalli/protocol/meta/response_processor.rb +211 -0
- data/lib/dalli/protocol/meta.rb +177 -0
- data/lib/dalli/protocol/response_buffer.rb +5 -4
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +1 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56babb2362639cca3fd24225eea2a76a0d9a075a3b113b7f8ea09814a79bf59a
|
4
|
+
data.tar.gz: 309f6be3fce52b38608c4b22e5dce439e68944796fbe1f2240b2ed40760e8700
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16847c7b39ff624deeb5fecd324d3f0bddafdcd6096c5f65cfa7a79e8b2369930b7078e8fc019bd49015c3415b43f33ea07cf85380b28d3c46199339bc9aeb2a
|
7
|
+
data.tar.gz: 890f87a779947ec269fb9889334eaa7b4424a654995b9b12b723dee7452f82de066b538465800c20bf9ed9bb2c5a81fbca3ae5ff382340f241f0525ad158df0c
|
data/History.md
CHANGED
@@ -4,6 +4,32 @@ Dalli Changelog
|
|
4
4
|
Unreleased
|
5
5
|
==========
|
6
6
|
|
7
|
+
3.2.0
|
8
|
+
==========
|
9
|
+
|
10
|
+
- BREAKING CHANGE: Remove protocol_implementation client option (petergoldstein)
|
11
|
+
- Add protocol option with meta implementation (petergoldstein)
|
12
|
+
|
13
|
+
3.1.6
|
14
|
+
==========
|
15
|
+
|
16
|
+
- Fix bug with cas/cas! with "Not found" value (petergoldstein)
|
17
|
+
- Add Ruby 3.1 to CI (petergoldstein)
|
18
|
+
- Replace reject(&:nil?) with compact (petergoldstein)
|
19
|
+
|
20
|
+
3.1.5
|
21
|
+
==========
|
22
|
+
|
23
|
+
- Fix bug with get_cas key with "Not found" value (petergoldstein)
|
24
|
+
- Replace should return nil, not raise error, on miss (petergoldstein)
|
25
|
+
|
26
|
+
3.1.4
|
27
|
+
==========
|
28
|
+
|
29
|
+
- Improve response parsing performance (casperisfine)
|
30
|
+
- Reorganize binary protocol parsing a bit (petergoldstein)
|
31
|
+
- Fix handling of non-ASCII keys in get_multi (petergoldstein)
|
32
|
+
|
7
33
|
3.1.3
|
8
34
|
==========
|
9
35
|
|
data/lib/dalli/client.rb
CHANGED
@@ -43,8 +43,8 @@ module Dalli
|
|
43
43
|
# #fetch operations.
|
44
44
|
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
|
45
45
|
# useful for injecting a FIPS compliant hash object.
|
46
|
-
# - :
|
47
|
-
#
|
46
|
+
# - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
|
47
|
+
# to communicate with memcached.
|
48
48
|
#
|
49
49
|
def initialize(servers = nil, options = {})
|
50
50
|
@servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
@@ -86,8 +86,6 @@ module Dalli
|
|
86
86
|
# value and CAS will be passed to the block.
|
87
87
|
def get_cas(key)
|
88
88
|
(value, cas) = perform(:cas, key)
|
89
|
-
# TODO: This is odd. Confirm this is working as expected.
|
90
|
-
value = nil if !value || value == 'Not found'
|
91
89
|
return [value, cas] unless block_given?
|
92
90
|
|
93
91
|
yield value, cas
|
@@ -377,7 +375,6 @@ module Dalli
|
|
377
375
|
|
378
376
|
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
379
377
|
(value, cas) = perform(:cas, key)
|
380
|
-
value = nil if !value || value == 'Not found'
|
381
378
|
return if value.nil? && !always_set
|
382
379
|
|
383
380
|
newvalue = yield(value)
|
@@ -405,7 +402,12 @@ module Dalli
|
|
405
402
|
end
|
406
403
|
|
407
404
|
def protocol_implementation
|
408
|
-
@protocol_implementation ||= @options
|
405
|
+
@protocol_implementation ||= case @options[:protocol]&.to_s
|
406
|
+
when 'meta'
|
407
|
+
Dalli::Protocol::Meta
|
408
|
+
else
|
409
|
+
Dalli::Protocol::Binary
|
410
|
+
end
|
409
411
|
end
|
410
412
|
|
411
413
|
##
|
data/lib/dalli/protocol/base.rb
CHANGED
@@ -83,20 +83,20 @@ module Dalli
|
|
83
83
|
|
84
84
|
response_buffer.read
|
85
85
|
|
86
|
-
|
87
|
-
#
|
86
|
+
status, cas, key, value = response_buffer.process_single_getk_response
|
87
|
+
# status is not nil only if we have a full response to parse
|
88
88
|
# in the buffer
|
89
|
-
|
89
|
+
until status.nil?
|
90
90
|
# If the status is ok and key is nil, then this is the response
|
91
91
|
# to the noop at the end of the pipeline
|
92
|
-
finish_pipeline && break if
|
92
|
+
finish_pipeline && break if status && key.nil?
|
93
93
|
|
94
94
|
# If the status is ok and the key is not nil, then this is a
|
95
95
|
# getkq response with a value that we want to set in the response hash
|
96
|
-
values[key] = [value,
|
96
|
+
values[key] = [value, cas] unless key.nil?
|
97
97
|
|
98
98
|
# Get the next response from the buffer
|
99
|
-
|
99
|
+
status, cas, key, value = response_buffer.process_single_getk_response
|
100
100
|
end
|
101
101
|
|
102
102
|
values
|
@@ -147,6 +147,13 @@ module Dalli
|
|
147
147
|
|
148
148
|
private
|
149
149
|
|
150
|
+
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
151
|
+
def verify_allowed_quiet!(opkey)
|
152
|
+
return if ALLOWED_QUIET_OPS.include?(opkey)
|
153
|
+
|
154
|
+
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
155
|
+
end
|
156
|
+
|
150
157
|
##
|
151
158
|
# Checks to see if we can execute the specified operation. Checks
|
152
159
|
# whether the connection is in use, and whether the command is allowed
|
@@ -207,10 +214,6 @@ module Dalli
|
|
207
214
|
@response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor)
|
208
215
|
end
|
209
216
|
|
210
|
-
def pipeline_response
|
211
|
-
response_buffer.process_single_getk_response
|
212
|
-
end
|
213
|
-
|
214
217
|
# Called after the noop response is received at the end of a set
|
215
218
|
# of pipelined gets
|
216
219
|
def finish_pipeline
|
@@ -94,7 +94,7 @@ module Dalli
|
|
94
94
|
key_len = key.nil? ? 0 : key.bytesize
|
95
95
|
value_len = value.nil? ? 0 : value.bytesize
|
96
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].
|
97
|
+
body = [bitflags, ttl, key, value].compact
|
98
98
|
(header + body).pack(FORMAT[opkey])
|
99
99
|
end
|
100
100
|
# rubocop:enable Metrics/ParameterLists
|
@@ -46,11 +46,13 @@ module Dalli
|
|
46
46
|
[resp_header, body]
|
47
47
|
end
|
48
48
|
|
49
|
-
def unpack_response_body(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
def unpack_response_body(resp_header, body, parse_as_stored_value)
|
50
|
+
extra_len = resp_header.extra_len
|
51
|
+
key_len = resp_header.key_len
|
52
|
+
bitflags = extra_len.positive? ? body.unpack1('N') : 0x0
|
53
|
+
key = body.byteslice(extra_len, key_len).force_encoding('UTF-8') if key_len.positive?
|
54
|
+
value = body.byteslice((extra_len + key_len)..-1)
|
55
|
+
value = parse_as_stored_value ? @value_marshaller.retrieve(value, bitflags) : value
|
54
56
|
[key, value]
|
55
57
|
end
|
56
58
|
|
@@ -64,7 +66,7 @@ module Dalli
|
|
64
66
|
raise Dalli::DalliError, "Response error #{resp_header.status}: #{RESPONSE_CODES[resp_header.status]}"
|
65
67
|
end
|
66
68
|
|
67
|
-
def
|
69
|
+
def get(cache_nils: false)
|
68
70
|
resp_header, body = read_response
|
69
71
|
|
70
72
|
return false if resp_header.not_stored? # Not stored, normal status for add operation
|
@@ -73,7 +75,7 @@ module Dalli
|
|
73
75
|
raise_on_not_ok!(resp_header)
|
74
76
|
return true unless body
|
75
77
|
|
76
|
-
unpack_response_body(resp_header
|
78
|
+
unpack_response_body(resp_header, body, true).last
|
77
79
|
end
|
78
80
|
|
79
81
|
##
|
@@ -83,13 +85,14 @@ module Dalli
|
|
83
85
|
##
|
84
86
|
def storage_response
|
85
87
|
resp_header, = read_response
|
88
|
+
return nil if resp_header.not_found?
|
86
89
|
return false if resp_header.not_stored? # Not stored, normal status for add operation
|
87
90
|
|
88
91
|
raise_on_not_ok!(resp_header)
|
89
92
|
resp_header.cas
|
90
93
|
end
|
91
94
|
|
92
|
-
def
|
95
|
+
def delete
|
93
96
|
resp_header, = read_response
|
94
97
|
return false if resp_header.not_found? || resp_header.not_stored?
|
95
98
|
|
@@ -97,15 +100,7 @@ module Dalli
|
|
97
100
|
true
|
98
101
|
end
|
99
102
|
|
100
|
-
def
|
101
|
-
resp_header, = read_response
|
102
|
-
return false if resp_header.not_stored? # Not stored, possible status for append/prepend
|
103
|
-
|
104
|
-
raise_on_not_ok!(resp_header)
|
105
|
-
true
|
106
|
-
end
|
107
|
-
|
108
|
-
def data_cas_response(unpack: true)
|
103
|
+
def data_cas_response
|
109
104
|
resp_header, body = read_response
|
110
105
|
return [nil, resp_header.cas] if resp_header.not_found?
|
111
106
|
return [nil, false] if resp_header.not_stored?
|
@@ -113,14 +108,16 @@ module Dalli
|
|
113
108
|
raise_on_not_ok!(resp_header)
|
114
109
|
return [nil, resp_header.cas] unless body
|
115
110
|
|
116
|
-
[unpack_response_body(resp_header
|
111
|
+
[unpack_response_body(resp_header, body, true).last, resp_header.cas]
|
117
112
|
end
|
118
113
|
|
119
|
-
|
120
|
-
|
114
|
+
# Returns the new value for the key, if found and updated
|
115
|
+
def decr_incr
|
116
|
+
body = generic_response
|
117
|
+
body ? body.unpack1('Q>') : body
|
121
118
|
end
|
122
119
|
|
123
|
-
def
|
120
|
+
def stats
|
124
121
|
hash = {}
|
125
122
|
loop do
|
126
123
|
resp_header, body = read_response
|
@@ -133,14 +130,49 @@ module Dalli
|
|
133
130
|
# block to clear any error responses from inside the multi.
|
134
131
|
next unless resp_header.ok?
|
135
132
|
|
136
|
-
key, value = unpack_response_body(resp_header
|
133
|
+
key, value = unpack_response_body(resp_header, body, true)
|
137
134
|
hash[key] = value
|
138
135
|
end
|
139
136
|
end
|
140
137
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
138
|
+
def flush
|
139
|
+
no_body_response
|
140
|
+
end
|
141
|
+
|
142
|
+
def reset
|
143
|
+
generic_response
|
144
|
+
end
|
145
|
+
|
146
|
+
def version
|
147
|
+
generic_response
|
148
|
+
end
|
149
|
+
|
150
|
+
def consume_all_responses_until_noop
|
151
|
+
loop do
|
152
|
+
resp_header, = read_response
|
153
|
+
# This is the response to the terminating noop / end of stat
|
154
|
+
return true if resp_header.ok? && resp_header.key_len.zero?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def generic_response
|
159
|
+
resp_header, body = read_response
|
160
|
+
|
161
|
+
return false if resp_header.not_stored? # Not stored, normal status for add operation
|
162
|
+
return nil if resp_header.not_found?
|
163
|
+
|
164
|
+
raise_on_not_ok!(resp_header)
|
165
|
+
return true unless body
|
166
|
+
|
167
|
+
unpack_response_body(resp_header, body, false).last
|
168
|
+
end
|
169
|
+
|
170
|
+
def no_body_response
|
171
|
+
resp_header, = read_response
|
172
|
+
return false if resp_header.not_stored? # Not stored, possible status for append/prepend/delete
|
173
|
+
|
174
|
+
raise_on_not_ok!(resp_header)
|
175
|
+
true
|
144
176
|
end
|
145
177
|
|
146
178
|
def validate_auth_format(extra_len, count)
|
@@ -164,8 +196,7 @@ module Dalli
|
|
164
196
|
end
|
165
197
|
|
166
198
|
def response_header_from_buffer(buf)
|
167
|
-
|
168
|
-
ResponseHeader.new(header)
|
199
|
+
ResponseHeader.new(buf)
|
169
200
|
end
|
170
201
|
|
171
202
|
##
|
@@ -180,7 +211,7 @@ module Dalli
|
|
180
211
|
##
|
181
212
|
def getk_response_from_buffer(buf)
|
182
213
|
# There's no header in the buffer, so don't advance
|
183
|
-
return [0, nil, nil, nil] unless contains_header?(buf)
|
214
|
+
return [0, nil, nil, nil, nil] unless contains_header?(buf)
|
184
215
|
|
185
216
|
resp_header = response_header_from_buffer(buf)
|
186
217
|
body_len = resp_header.body_len
|
@@ -189,18 +220,18 @@ module Dalli
|
|
189
220
|
# This is either the response to the terminating
|
190
221
|
# noop or, if the status is not zero, an intermediate
|
191
222
|
# error response that needs to be discarded.
|
192
|
-
return [ResponseHeader::SIZE, resp_header, nil, nil] if body_len.zero?
|
223
|
+
return [ResponseHeader::SIZE, resp_header.ok?, resp_header.cas, nil, nil] if body_len.zero?
|
193
224
|
|
194
225
|
resp_size = ResponseHeader::SIZE + body_len
|
195
226
|
# The header is in the buffer, but the body is not. As we don't have
|
196
227
|
# a complete response, don't advance the buffer
|
197
|
-
return [0, nil, nil, nil] unless buf.bytesize >= resp_size
|
228
|
+
return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
|
198
229
|
|
199
230
|
# The full response is in our buffer, so parse it and return
|
200
231
|
# the values
|
201
|
-
body = buf.
|
202
|
-
key, value = unpack_response_body(resp_header
|
203
|
-
[resp_size, resp_header, key, value]
|
232
|
+
body = buf.byteslice(ResponseHeader::SIZE, body_len)
|
233
|
+
key, value = unpack_response_body(resp_header, body, true)
|
234
|
+
[resp_size, resp_header.ok?, resp_header.cas, key, value]
|
204
235
|
end
|
205
236
|
end
|
206
237
|
end
|
@@ -18,18 +18,11 @@ module Dalli
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
22
|
-
def verify_allowed_quiet!(opkey)
|
23
|
-
return if ALLOWED_QUIET_OPS.include?(opkey)
|
24
|
-
|
25
|
-
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
26
|
-
end
|
27
|
-
|
28
21
|
# Retrieval Commands
|
29
22
|
def get(key, options = nil)
|
30
23
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
31
24
|
write(req)
|
32
|
-
response_processor.
|
25
|
+
response_processor.get(cache_nils: cache_nils?(options))
|
33
26
|
end
|
34
27
|
|
35
28
|
def quiet_get_request(key)
|
@@ -40,7 +33,7 @@ module Dalli
|
|
40
33
|
ttl = TtlSanitizer.sanitize(ttl)
|
41
34
|
req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
|
42
35
|
write(req)
|
43
|
-
response_processor.
|
36
|
+
response_processor.get(cache_nils: cache_nils?(options))
|
44
37
|
end
|
45
38
|
|
46
39
|
def touch(key, ttl)
|
@@ -106,7 +99,7 @@ module Dalli
|
|
106
99
|
opkey = quiet? ? :deleteq : :delete
|
107
100
|
req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
|
108
101
|
write(req)
|
109
|
-
response_processor.
|
102
|
+
response_processor.delete unless quiet?
|
110
103
|
end
|
111
104
|
|
112
105
|
# Arithmetic Commands
|
@@ -132,7 +125,7 @@ module Dalli
|
|
132
125
|
initial ||= 0
|
133
126
|
write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
|
134
127
|
count: count, initial: initial, expiry: expiry))
|
135
|
-
response_processor.
|
128
|
+
response_processor.decr_incr unless quiet?
|
136
129
|
end
|
137
130
|
|
138
131
|
# Other Commands
|
@@ -146,23 +139,23 @@ module Dalli
|
|
146
139
|
# We need to read all the responses at once.
|
147
140
|
def noop
|
148
141
|
write_noop
|
149
|
-
response_processor.
|
142
|
+
response_processor.consume_all_responses_until_noop
|
150
143
|
end
|
151
144
|
|
152
145
|
def stats(info = '')
|
153
146
|
req = RequestFormatter.standard_request(opkey: :stat, key: info)
|
154
147
|
write(req)
|
155
|
-
response_processor.
|
148
|
+
response_processor.stats
|
156
149
|
end
|
157
150
|
|
158
151
|
def reset_stats
|
159
152
|
write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
|
160
|
-
response_processor.
|
153
|
+
response_processor.reset
|
161
154
|
end
|
162
155
|
|
163
156
|
def version
|
164
157
|
write(RequestFormatter.standard_request(opkey: :version))
|
165
|
-
response_processor.
|
158
|
+
response_processor.version
|
166
159
|
end
|
167
160
|
|
168
161
|
def write_noop
|
@@ -133,6 +133,16 @@ module Dalli
|
|
133
133
|
@request_in_progress = false
|
134
134
|
end
|
135
135
|
|
136
|
+
def read_line
|
137
|
+
start_request!
|
138
|
+
data = @sock.gets("\r\n")
|
139
|
+
error_on_request!('EOF in read_line') if data.nil?
|
140
|
+
finish_request!
|
141
|
+
data
|
142
|
+
rescue SystemCallError, Timeout::Error, EOFError => e
|
143
|
+
error_on_request!(e)
|
144
|
+
end
|
145
|
+
|
136
146
|
def read(count)
|
137
147
|
start_request!
|
138
148
|
data = @sock.readfull(count)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Dalli
|
6
|
+
module Protocol
|
7
|
+
class Meta
|
8
|
+
##
|
9
|
+
# The meta protocol requires that keys be ASCII only, so Unicode keys are
|
10
|
+
# not supported. In addition, the use of whitespace in the key is not
|
11
|
+
# allowed.
|
12
|
+
# memcached supports the use of base64 hashes for keys containing
|
13
|
+
# whitespace or non-ASCII characters, provided the 'b' flag is included in the request.
|
14
|
+
class KeyRegularizer
|
15
|
+
WHITESPACE = /\s/.freeze
|
16
|
+
|
17
|
+
def self.encode(key)
|
18
|
+
return [key, false] if key.ascii_only? && !WHITESPACE.match(key)
|
19
|
+
|
20
|
+
[Base64.strict_encode64(key), true]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decode(encoded_key, base64_encoded)
|
24
|
+
return encoded_key unless base64_encoded
|
25
|
+
|
26
|
+
Base64.strict_decode64(encoded_key).force_encoding('UTF-8')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
class Meta
|
6
|
+
##
|
7
|
+
# Class that encapsulates logic for formatting meta protocol requests
|
8
|
+
# to memcached.
|
9
|
+
##
|
10
|
+
class RequestFormatter
|
11
|
+
# Since these are string construction methods, we're going to disable these
|
12
|
+
# Rubocop directives. We really can't make this construction much simpler,
|
13
|
+
# and introducing an intermediate object seems like overkill.
|
14
|
+
#
|
15
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
17
|
+
# rubocop:disable Metrics/ParameterLists
|
18
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
19
|
+
def self.meta_get(key:, value: true, return_cas: false, ttl: nil, base64: false, quiet: false)
|
20
|
+
cmd = "mg #{key}"
|
21
|
+
cmd << ' v f' if value
|
22
|
+
cmd << ' c' if return_cas
|
23
|
+
cmd << ' b' if base64
|
24
|
+
cmd << " T#{ttl}" if ttl
|
25
|
+
cmd << ' k q s' if quiet # Return the key in the response if quiet
|
26
|
+
cmd + TERMINATOR
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, base64: false, quiet: false)
|
30
|
+
cmd = "ms #{key} #{value.bytesize}"
|
31
|
+
cmd << ' c' unless %i[append prepend].include?(mode)
|
32
|
+
cmd << ' b' if base64
|
33
|
+
cmd << " F#{bitflags}" if bitflags
|
34
|
+
cmd << " C#{cas}" if cas && !cas.zero?
|
35
|
+
cmd << " T#{ttl}" if ttl
|
36
|
+
cmd << " M#{mode_to_token(mode)}"
|
37
|
+
cmd << ' q' if quiet
|
38
|
+
cmd << TERMINATOR
|
39
|
+
cmd << value
|
40
|
+
cmd + TERMINATOR
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false)
|
44
|
+
cmd = "md #{key}"
|
45
|
+
cmd << ' b' if base64
|
46
|
+
cmd << " C#{cas}" if cas && !cas.zero?
|
47
|
+
cmd << " T#{ttl}" if ttl
|
48
|
+
cmd << ' q' if quiet
|
49
|
+
cmd + TERMINATOR
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, base64: false, quiet: false)
|
53
|
+
cmd = "ma #{key} v"
|
54
|
+
cmd << ' b' if base64
|
55
|
+
cmd << " D#{delta}" if delta
|
56
|
+
cmd << " J#{initial}" if initial
|
57
|
+
cmd << " C#{cas}" if cas && !cas.zero?
|
58
|
+
cmd << " N#{ttl}" if ttl
|
59
|
+
cmd << ' q' if quiet
|
60
|
+
cmd << " M#{incr ? 'I' : 'D'}"
|
61
|
+
cmd + TERMINATOR
|
62
|
+
end
|
63
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
64
|
+
# rubocop:enable Metrics/MethodLength
|
65
|
+
# rubocop:enable Metrics/ParameterLists
|
66
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
67
|
+
|
68
|
+
def self.meta_noop
|
69
|
+
"mn#{TERMINATOR}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.version
|
73
|
+
"version#{TERMINATOR}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.flush(delay: nil, quiet: false)
|
77
|
+
cmd = +'flush_all'
|
78
|
+
cmd << " #{delay}" if delay
|
79
|
+
cmd << ' noreply' if quiet
|
80
|
+
cmd + TERMINATOR
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.stats(arg = nil)
|
84
|
+
cmd = +'stats'
|
85
|
+
cmd << " #{arg}" if arg
|
86
|
+
cmd + TERMINATOR
|
87
|
+
end
|
88
|
+
|
89
|
+
# rubocop:disable Metrics/MethodLength
|
90
|
+
def self.mode_to_token(mode)
|
91
|
+
case mode
|
92
|
+
when :add
|
93
|
+
'E'
|
94
|
+
when :replace
|
95
|
+
'R'
|
96
|
+
when :append
|
97
|
+
'A'
|
98
|
+
when :prepend
|
99
|
+
'P'
|
100
|
+
else
|
101
|
+
'S'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
# rubocop:enable Metrics/MethodLength
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
class Meta
|
6
|
+
##
|
7
|
+
# Class that encapsulates logic for processing meta protocol responses
|
8
|
+
# from memcached. Includes logic for pulling data from an IO source
|
9
|
+
# and parsing into local values. Handles errors on unexpected values.
|
10
|
+
##
|
11
|
+
class ResponseProcessor
|
12
|
+
EN = 'EN'
|
13
|
+
END_TOKEN = 'END'
|
14
|
+
EX = 'EX'
|
15
|
+
HD = 'HD'
|
16
|
+
MN = 'MN'
|
17
|
+
NF = 'NF'
|
18
|
+
NS = 'NS'
|
19
|
+
OK = 'OK'
|
20
|
+
RESET = 'RESET'
|
21
|
+
STAT = 'STAT'
|
22
|
+
VA = 'VA'
|
23
|
+
VERSION = 'VERSION'
|
24
|
+
|
25
|
+
def initialize(io_source, value_marshaller)
|
26
|
+
@io_source = io_source
|
27
|
+
@value_marshaller = value_marshaller
|
28
|
+
end
|
29
|
+
|
30
|
+
def meta_get_with_value(cache_nils: false)
|
31
|
+
tokens = error_on_unexpected!([VA, EN, HD])
|
32
|
+
return cache_nils ? ::Dalli::NOT_FOUND : nil if tokens.first == EN
|
33
|
+
return true unless tokens.first == VA
|
34
|
+
|
35
|
+
@value_marshaller.retrieve(read_line, bitflags_from_tokens(tokens))
|
36
|
+
end
|
37
|
+
|
38
|
+
def meta_get_with_value_and_cas
|
39
|
+
tokens = error_on_unexpected!([VA, EN, HD])
|
40
|
+
return [nil, 0] if tokens.first == EN
|
41
|
+
|
42
|
+
cas = cas_from_tokens(tokens)
|
43
|
+
return [nil, cas] unless tokens.first == VA
|
44
|
+
|
45
|
+
[@value_marshaller.retrieve(read_line, bitflags_from_tokens(tokens)), cas]
|
46
|
+
end
|
47
|
+
|
48
|
+
def meta_get_without_value
|
49
|
+
tokens = error_on_unexpected!([EN, HD])
|
50
|
+
tokens.first == EN ? nil : true
|
51
|
+
end
|
52
|
+
|
53
|
+
def meta_set_with_cas
|
54
|
+
tokens = error_on_unexpected!([HD, NS, NF, EX])
|
55
|
+
return false unless tokens.first == HD
|
56
|
+
|
57
|
+
cas_from_tokens(tokens)
|
58
|
+
end
|
59
|
+
|
60
|
+
def meta_set_append_prepend
|
61
|
+
tokens = error_on_unexpected!([HD, NS, NF, EX])
|
62
|
+
return false unless tokens.first == HD
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def meta_delete
|
68
|
+
tokens = error_on_unexpected!([HD, NF, EX])
|
69
|
+
tokens.first == HD
|
70
|
+
end
|
71
|
+
|
72
|
+
def decr_incr
|
73
|
+
tokens = error_on_unexpected!([VA, NF, NS, EX])
|
74
|
+
return false if [NS, EX].include?(tokens.first)
|
75
|
+
return nil if tokens.first == NF
|
76
|
+
|
77
|
+
read_line.to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def stats
|
81
|
+
tokens = error_on_unexpected!([END_TOKEN, STAT])
|
82
|
+
values = {}
|
83
|
+
while tokens.first != END_TOKEN
|
84
|
+
values[tokens[1]] = tokens[2]
|
85
|
+
tokens = next_line_to_tokens
|
86
|
+
end
|
87
|
+
values
|
88
|
+
end
|
89
|
+
|
90
|
+
def flush
|
91
|
+
error_on_unexpected!([OK])
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset
|
97
|
+
error_on_unexpected!([RESET])
|
98
|
+
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def version
|
103
|
+
tokens = error_on_unexpected!([VERSION])
|
104
|
+
tokens.last
|
105
|
+
end
|
106
|
+
|
107
|
+
def consume_all_responses_until_mn
|
108
|
+
tokens = next_line_to_tokens
|
109
|
+
|
110
|
+
tokens = next_line_to_tokens while tokens.first != MN
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def tokens_from_header_buffer(buf)
|
115
|
+
header = header_from_buffer(buf)
|
116
|
+
tokens = header.split
|
117
|
+
header_len = header.bytesize + TERMINATOR.length
|
118
|
+
body_len = body_len_from_tokens(tokens)
|
119
|
+
[tokens, header_len, body_len]
|
120
|
+
end
|
121
|
+
|
122
|
+
def full_response_from_buffer(tokens, body, resp_size)
|
123
|
+
value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
|
124
|
+
[resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# This method returns an array of values used in a pipelined
|
129
|
+
# getk process. The first value is the number of bytes by
|
130
|
+
# which to advance the pointer in the buffer. If the
|
131
|
+
# complete response is found in the buffer, this will
|
132
|
+
# be the response size. Otherwise it is zero.
|
133
|
+
#
|
134
|
+
# The remaining three values in the array are the ResponseHeader,
|
135
|
+
# key, and value.
|
136
|
+
##
|
137
|
+
def getk_response_from_buffer(buf)
|
138
|
+
# There's no header in the buffer, so don't advance
|
139
|
+
return [0, nil, nil, nil, nil] unless contains_header?(buf)
|
140
|
+
|
141
|
+
tokens, header_len, body_len = tokens_from_header_buffer(buf)
|
142
|
+
|
143
|
+
# We have a complete response that has no body.
|
144
|
+
# This is either the response to the terminating
|
145
|
+
# noop or, if the status is not MN, an intermediate
|
146
|
+
# error response that needs to be discarded.
|
147
|
+
return [header_len, true, nil, nil, nil] if body_len.zero?
|
148
|
+
|
149
|
+
resp_size = header_len + body_len + TERMINATOR.length
|
150
|
+
# The header is in the buffer, but the body is not. As we don't have
|
151
|
+
# a complete response, don't advance the buffer
|
152
|
+
return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
|
153
|
+
|
154
|
+
# The full response is in our buffer, so parse it and return
|
155
|
+
# the values
|
156
|
+
body = buf.slice(header_len, body_len)
|
157
|
+
full_response_from_buffer(tokens, body, resp_size)
|
158
|
+
end
|
159
|
+
|
160
|
+
def contains_header?(buf)
|
161
|
+
buf.include?(TERMINATOR)
|
162
|
+
end
|
163
|
+
|
164
|
+
def header_from_buffer(buf)
|
165
|
+
buf.split(TERMINATOR, 2).first
|
166
|
+
end
|
167
|
+
|
168
|
+
def error_on_unexpected!(expected_codes)
|
169
|
+
tokens = next_line_to_tokens
|
170
|
+
raise Dalli::DalliError, "Response error: #{tokens.first}" unless expected_codes.include?(tokens.first)
|
171
|
+
|
172
|
+
tokens
|
173
|
+
end
|
174
|
+
|
175
|
+
def bitflags_from_tokens(tokens)
|
176
|
+
value_from_tokens(tokens, 'f')&.to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def cas_from_tokens(tokens)
|
180
|
+
value_from_tokens(tokens, 'c')&.to_i
|
181
|
+
end
|
182
|
+
|
183
|
+
def key_from_tokens(tokens)
|
184
|
+
encoded_key = value_from_tokens(tokens, 'k')
|
185
|
+
base64_encoded = tokens.any?('b')
|
186
|
+
KeyRegularizer.decode(encoded_key, base64_encoded)
|
187
|
+
end
|
188
|
+
|
189
|
+
def body_len_from_tokens(tokens)
|
190
|
+
value_from_tokens(tokens, 's')&.to_i
|
191
|
+
end
|
192
|
+
|
193
|
+
def value_from_tokens(tokens, flag)
|
194
|
+
bitflags_token = tokens.find { |t| t.start_with?(flag) }
|
195
|
+
return 0 unless bitflags_token
|
196
|
+
|
197
|
+
bitflags_token[1..-1]
|
198
|
+
end
|
199
|
+
|
200
|
+
def read_line
|
201
|
+
@io_source.read_line&.chomp!(TERMINATOR)
|
202
|
+
end
|
203
|
+
|
204
|
+
def next_line_to_tokens
|
205
|
+
line = read_line
|
206
|
+
line&.split || []
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'socket'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
module Dalli
|
8
|
+
module Protocol
|
9
|
+
##
|
10
|
+
# Access point for a single Memcached server, accessed via Memcached's meta
|
11
|
+
# protocol. Contains logic for managing connection state to the server (retries, etc),
|
12
|
+
# formatting requests to the server, and unpacking responses.
|
13
|
+
##
|
14
|
+
class Meta < Base
|
15
|
+
TERMINATOR = "\r\n"
|
16
|
+
|
17
|
+
def response_processor
|
18
|
+
@response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
|
19
|
+
end
|
20
|
+
|
21
|
+
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Retrieval Commands
|
26
|
+
def get(key, options = nil)
|
27
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
28
|
+
req = RequestFormatter.meta_get(key: encoded_key, base64: base64)
|
29
|
+
write(req)
|
30
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
31
|
+
end
|
32
|
+
|
33
|
+
def quiet_get_request(key)
|
34
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
35
|
+
RequestFormatter.meta_get(key: encoded_key, return_cas: true, base64: base64, quiet: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def gat(key, ttl, options = nil)
|
39
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
40
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
41
|
+
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, base64: base64)
|
42
|
+
write(req)
|
43
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
44
|
+
end
|
45
|
+
|
46
|
+
def touch(key, ttl)
|
47
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
48
|
+
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, value: false, base64: base64)
|
49
|
+
write(req)
|
50
|
+
response_processor.meta_get_without_value
|
51
|
+
end
|
52
|
+
|
53
|
+
# TODO: This is confusing, as there's a cas command in memcached
|
54
|
+
# and this isn't it. Maybe rename? Maybe eliminate?
|
55
|
+
def cas(key)
|
56
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
57
|
+
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
|
58
|
+
write(req)
|
59
|
+
response_processor.meta_get_with_value_and_cas
|
60
|
+
end
|
61
|
+
|
62
|
+
# Storage Commands
|
63
|
+
def set(key, value, ttl, cas, options)
|
64
|
+
write_storage_req(:set, key, value, ttl, cas, options)
|
65
|
+
response_processor.meta_set_with_cas unless quiet?
|
66
|
+
end
|
67
|
+
|
68
|
+
def add(key, value, ttl, options)
|
69
|
+
write_storage_req(:add, key, value, ttl, nil, options)
|
70
|
+
response_processor.meta_set_with_cas unless quiet?
|
71
|
+
end
|
72
|
+
|
73
|
+
def replace(key, value, ttl, cas, options)
|
74
|
+
write_storage_req(:replace, key, value, ttl, cas, options)
|
75
|
+
response_processor.meta_set_with_cas unless quiet?
|
76
|
+
end
|
77
|
+
|
78
|
+
# rubocop:disable Metrics/ParameterLists
|
79
|
+
def write_storage_req(mode, key, raw_value, ttl = nil, cas = nil, options = {})
|
80
|
+
(value, bitflags) = @value_marshaller.store(key, raw_value, options)
|
81
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
82
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
83
|
+
req = RequestFormatter.meta_set(key: encoded_key, value: value,
|
84
|
+
bitflags: bitflags, cas: cas,
|
85
|
+
ttl: ttl, mode: mode, quiet: quiet?, base64: base64)
|
86
|
+
write(req)
|
87
|
+
end
|
88
|
+
# rubocop:enable Metrics/ParameterLists
|
89
|
+
|
90
|
+
def append(key, value)
|
91
|
+
write_append_prepend_req(:append, key, value)
|
92
|
+
response_processor.meta_set_append_prepend unless quiet?
|
93
|
+
end
|
94
|
+
|
95
|
+
def prepend(key, value)
|
96
|
+
write_append_prepend_req(:prepend, key, value)
|
97
|
+
response_processor.meta_set_append_prepend unless quiet?
|
98
|
+
end
|
99
|
+
|
100
|
+
# rubocop:disable Metrics/ParameterLists
|
101
|
+
def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil, _options = {})
|
102
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
103
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
104
|
+
req = RequestFormatter.meta_set(key: encoded_key, value: value, base64: base64,
|
105
|
+
cas: cas, ttl: ttl, mode: mode, quiet: quiet?)
|
106
|
+
write(req)
|
107
|
+
end
|
108
|
+
# rubocop:enable Metrics/ParameterLists
|
109
|
+
|
110
|
+
# Delete Commands
|
111
|
+
def delete(key, cas)
|
112
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
113
|
+
req = RequestFormatter.meta_delete(key: encoded_key, cas: cas,
|
114
|
+
base64: base64, quiet: quiet?)
|
115
|
+
write(req)
|
116
|
+
response_processor.meta_delete unless quiet?
|
117
|
+
end
|
118
|
+
|
119
|
+
# Arithmetic Commands
|
120
|
+
def decr(key, count, ttl, initial)
|
121
|
+
decr_incr false, key, count, ttl, initial
|
122
|
+
end
|
123
|
+
|
124
|
+
def incr(key, count, ttl, initial)
|
125
|
+
decr_incr true, key, count, ttl, initial
|
126
|
+
end
|
127
|
+
|
128
|
+
def decr_incr(incr, key, delta, ttl, initial)
|
129
|
+
ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
|
130
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
131
|
+
write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: initial, incr: incr, ttl: ttl,
|
132
|
+
quiet: quiet?, base64: base64))
|
133
|
+
response_processor.decr_incr unless quiet?
|
134
|
+
end
|
135
|
+
|
136
|
+
# Other Commands
|
137
|
+
def flush(delay = 0)
|
138
|
+
write(RequestFormatter.flush(delay: delay))
|
139
|
+
response_processor.flush unless quiet?
|
140
|
+
end
|
141
|
+
|
142
|
+
# Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
|
143
|
+
# We need to read all the responses at once.
|
144
|
+
def noop
|
145
|
+
write_noop
|
146
|
+
response_processor.consume_all_responses_until_mn
|
147
|
+
end
|
148
|
+
|
149
|
+
def stats(info = nil)
|
150
|
+
write(RequestFormatter.stats(info))
|
151
|
+
response_processor.stats
|
152
|
+
end
|
153
|
+
|
154
|
+
def reset_stats
|
155
|
+
write(RequestFormatter.stats('reset'))
|
156
|
+
response_processor.reset
|
157
|
+
end
|
158
|
+
|
159
|
+
def version
|
160
|
+
write(RequestFormatter.version)
|
161
|
+
response_processor.version
|
162
|
+
end
|
163
|
+
|
164
|
+
def write_noop
|
165
|
+
write(RequestFormatter.meta_noop)
|
166
|
+
end
|
167
|
+
|
168
|
+
def authenticate_connection
|
169
|
+
raise Dalli::DalliError, 'Authentication not supported for the meta protocol.'
|
170
|
+
end
|
171
|
+
|
172
|
+
require_relative 'meta/key_regularizer'
|
173
|
+
require_relative 'meta/request_formatter'
|
174
|
+
require_relative 'meta/response_processor'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -12,6 +12,7 @@ module Dalli
|
|
12
12
|
def initialize(io_source, response_processor)
|
13
13
|
@io_source = io_source
|
14
14
|
@response_processor = response_processor
|
15
|
+
@buffer = nil
|
15
16
|
end
|
16
17
|
|
17
18
|
def read
|
@@ -21,9 +22,9 @@ module Dalli
|
|
21
22
|
# Attempts to process a single response from the buffer. Starts
|
22
23
|
# by advancing the buffer to the specified start position
|
23
24
|
def process_single_getk_response
|
24
|
-
bytes,
|
25
|
+
bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer)
|
25
26
|
advance(bytes)
|
26
|
-
[
|
27
|
+
[status, cas, key, value]
|
27
28
|
end
|
28
29
|
|
29
30
|
# Advances the internal response buffer by bytes_to_advance
|
@@ -31,13 +32,13 @@ module Dalli
|
|
31
32
|
def advance(bytes_to_advance)
|
32
33
|
return unless bytes_to_advance.positive?
|
33
34
|
|
34
|
-
@buffer = @buffer
|
35
|
+
@buffer = @buffer.byteslice(bytes_to_advance..-1)
|
35
36
|
end
|
36
37
|
|
37
38
|
# Resets the internal buffer to an empty state,
|
38
39
|
# so that we're ready to read pipelined responses
|
39
40
|
def reset
|
40
|
-
@buffer =
|
41
|
+
@buffer = ''.b
|
41
42
|
end
|
42
43
|
|
43
44
|
# Clear the internal response buffer
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
@@ -65,6 +65,7 @@ require_relative 'dalli/protocol'
|
|
65
65
|
require_relative 'dalli/protocol/base'
|
66
66
|
require_relative 'dalli/protocol/binary'
|
67
67
|
require_relative 'dalli/protocol/connection_manager'
|
68
|
+
require_relative 'dalli/protocol/meta'
|
68
69
|
require_relative 'dalli/protocol/response_buffer'
|
69
70
|
require_relative 'dalli/protocol/server_config_parser'
|
70
71
|
require_relative 'dalli/protocol/ttl_sanitizer'
|
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.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter M. Goldstein
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-01-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: connection_pool
|
@@ -128,6 +128,10 @@ files:
|
|
128
128
|
- lib/dalli/protocol/binary/response_processor.rb
|
129
129
|
- lib/dalli/protocol/binary/sasl_authentication.rb
|
130
130
|
- lib/dalli/protocol/connection_manager.rb
|
131
|
+
- lib/dalli/protocol/meta.rb
|
132
|
+
- lib/dalli/protocol/meta/key_regularizer.rb
|
133
|
+
- lib/dalli/protocol/meta/request_formatter.rb
|
134
|
+
- lib/dalli/protocol/meta/response_processor.rb
|
131
135
|
- lib/dalli/protocol/response_buffer.rb
|
132
136
|
- lib/dalli/protocol/server_config_parser.rb
|
133
137
|
- lib/dalli/protocol/ttl_sanitizer.rb
|
@@ -160,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
164
|
- !ruby/object:Gem::Version
|
161
165
|
version: '0'
|
162
166
|
requirements: []
|
163
|
-
rubygems_version: 3.
|
167
|
+
rubygems_version: 3.3.4
|
164
168
|
signing_key:
|
165
169
|
specification_version: 4
|
166
170
|
summary: High performance memcached client for Ruby
|