dalli 3.1.1 → 3.1.5
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 +23 -0
- data/lib/dalli/client.rb +0 -2
- data/lib/dalli/protocol/base.rb +234 -0
- data/lib/dalli/protocol/binary/response_processor.rb +65 -26
- data/lib/dalli/protocol/binary/sasl_authentication.rb +1 -1
- data/lib/dalli/protocol/binary.rb +24 -237
- data/lib/dalli/protocol/response_buffer.rb +4 -4
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a9612ab509bc2a6b7bd2560a7a10564183b3b9fbb2aa0655f339b30067407fa
|
4
|
+
data.tar.gz: 510c3ed03a4e2b4ae4e7f8540eac19e7d28508341b768edc938f9886d5504fbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81e31363e781e948f66c3a143cd506162e4b51b4c67bb76a8cbdbfb2efdcdbd279216b24cdc077c3c2e4ad88b356ffec1a74f4428c556b05d17591b171b874d2
|
7
|
+
data.tar.gz: ef45c25f8552e203751a51cb32172b5670b6b509ac45cbcc41b19a4f70688f0f50eb01b09af32c3faa05d643dd93acce2e748d8cedbba5dded585070b6ed7ec6
|
data/History.md
CHANGED
@@ -4,6 +4,29 @@ Dalli Changelog
|
|
4
4
|
Unreleased
|
5
5
|
==========
|
6
6
|
|
7
|
+
3.1.5
|
8
|
+
==========
|
9
|
+
|
10
|
+
- Fix bug with get_cas key with "Not found" value [petergoldstein]
|
11
|
+
- Replace should return nil, not raise error, on miss (petergoldstein)
|
12
|
+
|
13
|
+
3.1.4
|
14
|
+
==========
|
15
|
+
|
16
|
+
- Improve response parsing performance (casperisfine)
|
17
|
+
- Reorganize binary protocol parsing a bit (petergoldstein)
|
18
|
+
- Fix handling of non-ASCII keys in get_multi (petergoldstein)
|
19
|
+
|
20
|
+
3.1.3
|
21
|
+
==========
|
22
|
+
|
23
|
+
- Restore falsey behavior on delete/delete_cas for nonexistent key (petergoldstein)
|
24
|
+
|
25
|
+
3.1.2
|
26
|
+
==========
|
27
|
+
|
28
|
+
- Make quiet? / multi? public on Dalli::Protocol::Binary (petergoldstein)
|
29
|
+
|
7
30
|
3.1.1
|
8
31
|
==========
|
9
32
|
|
data/lib/dalli/client.rb
CHANGED
@@ -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
|
@@ -0,0 +1,234 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'socket'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
module Dalli
|
8
|
+
module Protocol
|
9
|
+
##
|
10
|
+
# Base class for a single Memcached server, containing logic common to all
|
11
|
+
# protocols. Contains logic for managing connection state to the server and value
|
12
|
+
# handling.
|
13
|
+
##
|
14
|
+
class Base
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
attr_accessor :weight, :options
|
18
|
+
|
19
|
+
def_delegators :@value_marshaller, :serializer, :compressor, :compression_min_size, :compress_by_default?
|
20
|
+
def_delegators :@connection_manager, :name, :sock, :hostname, :port, :close, :connected?, :socket_timeout,
|
21
|
+
:socket_type, :up!, :down!, :write, :reconnect_down_server?, :raise_down_error
|
22
|
+
|
23
|
+
def initialize(attribs, client_options = {})
|
24
|
+
hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
|
25
|
+
@options = client_options.merge(user_creds)
|
26
|
+
@value_marshaller = ValueMarshaller.new(@options)
|
27
|
+
@connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Chokepoint method for error handling and ensuring liveness
|
31
|
+
def request(opkey, *args)
|
32
|
+
verify_state(opkey)
|
33
|
+
|
34
|
+
begin
|
35
|
+
send(opkey, *args)
|
36
|
+
rescue Dalli::MarshalError => e
|
37
|
+
log_marshal_err(args.first, e)
|
38
|
+
raise
|
39
|
+
rescue Dalli::DalliError
|
40
|
+
raise
|
41
|
+
rescue StandardError => e
|
42
|
+
log_unexpected_err(e)
|
43
|
+
down!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Boolean method used by clients of this class to determine if this
|
49
|
+
# particular memcached instance is available for use.
|
50
|
+
def alive?
|
51
|
+
ensure_connected!
|
52
|
+
rescue Dalli::NetworkError
|
53
|
+
# ensure_connected! raises a NetworkError if connection fails. We
|
54
|
+
# want to capture that error and convert it to a boolean value here.
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def lock!; end
|
59
|
+
|
60
|
+
def unlock!; end
|
61
|
+
|
62
|
+
# Start reading key/value pairs from this connection. This is usually called
|
63
|
+
# after a series of GETKQ commands. A NOOP is sent, and the server begins
|
64
|
+
# flushing responses for kv pairs that were found.
|
65
|
+
#
|
66
|
+
# Returns nothing.
|
67
|
+
def pipeline_response_setup
|
68
|
+
verify_state(:getkq)
|
69
|
+
write_noop
|
70
|
+
response_buffer.reset
|
71
|
+
@connection_manager.start_request!
|
72
|
+
end
|
73
|
+
|
74
|
+
# Attempt to receive and parse as many key/value pairs as possible
|
75
|
+
# from this server. After #pipeline_response_setup, this should be invoked
|
76
|
+
# repeatedly whenever this server's socket is readable until
|
77
|
+
# #pipeline_complete?.
|
78
|
+
#
|
79
|
+
# Returns a Hash of kv pairs received.
|
80
|
+
def pipeline_next_responses
|
81
|
+
reconnect_on_pipeline_complete!
|
82
|
+
values = {}
|
83
|
+
|
84
|
+
response_buffer.read
|
85
|
+
|
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
|
+
# in the buffer
|
89
|
+
until status.nil?
|
90
|
+
# If the status is ok and key is nil, then this is the response
|
91
|
+
# to the noop at the end of the pipeline
|
92
|
+
finish_pipeline && break if status && key.nil?
|
93
|
+
|
94
|
+
# If the status is ok and the key is not nil, then this is a
|
95
|
+
# getkq response with a value that we want to set in the response hash
|
96
|
+
values[key] = [value, cas] unless key.nil?
|
97
|
+
|
98
|
+
# Get the next response from the buffer
|
99
|
+
status, cas, key, value = response_buffer.process_single_getk_response
|
100
|
+
end
|
101
|
+
|
102
|
+
values
|
103
|
+
rescue SystemCallError, Timeout::Error, EOFError => e
|
104
|
+
@connection_manager.error_on_request!(e)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Abort current pipelined get. Generally used to signal an external
|
108
|
+
# timeout during pipelined get. The underlying socket is
|
109
|
+
# disconnected, and the exception is swallowed.
|
110
|
+
#
|
111
|
+
# Returns nothing.
|
112
|
+
def pipeline_abort
|
113
|
+
response_buffer.clear
|
114
|
+
@connection_manager.abort_request!
|
115
|
+
return true unless connected?
|
116
|
+
|
117
|
+
# Closes the connection, which ensures that our connection
|
118
|
+
# is in a clean state for future requests
|
119
|
+
@connection_manager.error_on_request!('External timeout')
|
120
|
+
rescue NetworkError
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
124
|
+
# Did the last call to #pipeline_response_setup complete successfully?
|
125
|
+
def pipeline_complete?
|
126
|
+
!response_buffer.in_progress?
|
127
|
+
end
|
128
|
+
|
129
|
+
def username
|
130
|
+
@options[:username] || ENV['MEMCACHE_USERNAME']
|
131
|
+
end
|
132
|
+
|
133
|
+
def password
|
134
|
+
@options[:password] || ENV['MEMCACHE_PASSWORD']
|
135
|
+
end
|
136
|
+
|
137
|
+
def require_auth?
|
138
|
+
!username.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
def quiet?
|
142
|
+
Thread.current[::Dalli::QUIET]
|
143
|
+
end
|
144
|
+
alias multi? quiet?
|
145
|
+
|
146
|
+
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
##
|
151
|
+
# Checks to see if we can execute the specified operation. Checks
|
152
|
+
# whether the connection is in use, and whether the command is allowed
|
153
|
+
##
|
154
|
+
def verify_state(opkey)
|
155
|
+
@connection_manager.confirm_ready!
|
156
|
+
verify_allowed_quiet!(opkey) if quiet?
|
157
|
+
|
158
|
+
# The ensure_connected call has the side effect of connecting the
|
159
|
+
# underlying socket if it is not connected, or there's been a disconnect
|
160
|
+
# because of timeout or other error. Method raises an error
|
161
|
+
# if it can't connect
|
162
|
+
raise_down_error unless ensure_connected!
|
163
|
+
end
|
164
|
+
|
165
|
+
# The socket connection to the underlying server is initialized as a side
|
166
|
+
# effect of this call. In fact, this is the ONLY place where that
|
167
|
+
# socket connection is initialized.
|
168
|
+
#
|
169
|
+
# Both this method and connect need to be in this class so we can do auth
|
170
|
+
# as required
|
171
|
+
#
|
172
|
+
# Since this is invoked exclusively in verify_state!, we don't need to worry about
|
173
|
+
# thread safety. Using it elsewhere may require revisiting that assumption.
|
174
|
+
def ensure_connected!
|
175
|
+
return true if connected?
|
176
|
+
return false unless reconnect_down_server?
|
177
|
+
|
178
|
+
connect # This call needs to be in this class so we can do auth
|
179
|
+
connected?
|
180
|
+
end
|
181
|
+
|
182
|
+
def cache_nils?(opts)
|
183
|
+
return false unless opts.is_a?(Hash)
|
184
|
+
|
185
|
+
opts[:cache_nils] ? true : false
|
186
|
+
end
|
187
|
+
|
188
|
+
def connect
|
189
|
+
@connection_manager.establish_connection
|
190
|
+
authenticate_connection if require_auth?
|
191
|
+
@version = version # Connect socket if not authed
|
192
|
+
up!
|
193
|
+
rescue Dalli::DalliError
|
194
|
+
raise
|
195
|
+
end
|
196
|
+
|
197
|
+
def pipelined_get(keys)
|
198
|
+
req = +''
|
199
|
+
keys.each do |key|
|
200
|
+
req << quiet_get_request(key)
|
201
|
+
end
|
202
|
+
# Could send noop here instead of in pipeline_response_setup
|
203
|
+
write(req)
|
204
|
+
end
|
205
|
+
|
206
|
+
def response_buffer
|
207
|
+
@response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Called after the noop response is received at the end of a set
|
211
|
+
# of pipelined gets
|
212
|
+
def finish_pipeline
|
213
|
+
response_buffer.clear
|
214
|
+
@connection_manager.finish_request!
|
215
|
+
|
216
|
+
true # to simplify response
|
217
|
+
end
|
218
|
+
|
219
|
+
def reconnect_on_pipeline_complete!
|
220
|
+
@connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
|
221
|
+
end
|
222
|
+
|
223
|
+
def log_marshal_err(key, err)
|
224
|
+
Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
|
225
|
+
Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
|
226
|
+
end
|
227
|
+
|
228
|
+
def log_unexpected_err(err)
|
229
|
+
Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
|
230
|
+
Dalli.logger.error err.backtrace.join("\n\t")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -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,21 +85,22 @@ 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
|
-
return false if resp_header.
|
97
|
+
return false if resp_header.not_found? || resp_header.not_stored?
|
95
98
|
|
96
99
|
raise_on_not_ok!(resp_header)
|
97
100
|
true
|
98
101
|
end
|
99
102
|
|
100
|
-
def data_cas_response
|
103
|
+
def data_cas_response
|
101
104
|
resp_header, body = read_response
|
102
105
|
return [nil, resp_header.cas] if resp_header.not_found?
|
103
106
|
return [nil, false] if resp_header.not_stored?
|
@@ -105,14 +108,16 @@ module Dalli
|
|
105
108
|
raise_on_not_ok!(resp_header)
|
106
109
|
return [nil, resp_header.cas] unless body
|
107
110
|
|
108
|
-
[unpack_response_body(resp_header
|
111
|
+
[unpack_response_body(resp_header, body, true).last, resp_header.cas]
|
109
112
|
end
|
110
113
|
|
111
|
-
|
112
|
-
|
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
|
113
118
|
end
|
114
119
|
|
115
|
-
def
|
120
|
+
def stats
|
116
121
|
hash = {}
|
117
122
|
loop do
|
118
123
|
resp_header, body = read_response
|
@@ -125,14 +130,49 @@ module Dalli
|
|
125
130
|
# block to clear any error responses from inside the multi.
|
126
131
|
next unless resp_header.ok?
|
127
132
|
|
128
|
-
key, value = unpack_response_body(resp_header
|
133
|
+
key, value = unpack_response_body(resp_header, body, true)
|
129
134
|
hash[key] = value
|
130
135
|
end
|
131
136
|
end
|
132
137
|
|
133
|
-
def
|
134
|
-
|
135
|
-
|
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
|
136
176
|
end
|
137
177
|
|
138
178
|
def validate_auth_format(extra_len, count)
|
@@ -156,8 +196,7 @@ module Dalli
|
|
156
196
|
end
|
157
197
|
|
158
198
|
def response_header_from_buffer(buf)
|
159
|
-
|
160
|
-
ResponseHeader.new(header)
|
199
|
+
ResponseHeader.new(buf)
|
161
200
|
end
|
162
201
|
|
163
202
|
##
|
@@ -172,7 +211,7 @@ module Dalli
|
|
172
211
|
##
|
173
212
|
def getk_response_from_buffer(buf)
|
174
213
|
# There's no header in the buffer, so don't advance
|
175
|
-
return [0, nil, nil, nil] unless contains_header?(buf)
|
214
|
+
return [0, nil, nil, nil, nil] unless contains_header?(buf)
|
176
215
|
|
177
216
|
resp_header = response_header_from_buffer(buf)
|
178
217
|
body_len = resp_header.body_len
|
@@ -181,18 +220,18 @@ module Dalli
|
|
181
220
|
# This is either the response to the terminating
|
182
221
|
# noop or, if the status is not zero, an intermediate
|
183
222
|
# error response that needs to be discarded.
|
184
|
-
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?
|
185
224
|
|
186
225
|
resp_size = ResponseHeader::SIZE + body_len
|
187
226
|
# The header is in the buffer, but the body is not. As we don't have
|
188
227
|
# a complete response, don't advance the buffer
|
189
|
-
return [0, nil, nil, nil] unless buf.bytesize >= resp_size
|
228
|
+
return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
|
190
229
|
|
191
230
|
# The full response is in our buffer, so parse it and return
|
192
231
|
# the values
|
193
|
-
body = buf.
|
194
|
-
key, value = unpack_response_body(resp_header
|
195
|
-
[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]
|
196
235
|
end
|
197
236
|
end
|
198
237
|
end
|
@@ -10,7 +10,7 @@ module Dalli
|
|
10
10
|
def perform_auth_negotiation
|
11
11
|
write(RequestFormatter.standard_request(opkey: :auth_negotiation))
|
12
12
|
|
13
|
-
status, content =
|
13
|
+
status, content = response_processor.auth_response
|
14
14
|
return [status, []] if content.nil?
|
15
15
|
|
16
16
|
# Substitute spaces for the \x00 returned by
|
@@ -4,11 +4,6 @@ require 'forwardable'
|
|
4
4
|
require 'socket'
|
5
5
|
require 'timeout'
|
6
6
|
|
7
|
-
require_relative 'binary/request_formatter'
|
8
|
-
require_relative 'binary/response_header'
|
9
|
-
require_relative 'binary/response_processor'
|
10
|
-
require_relative 'binary/sasl_authentication'
|
11
|
-
|
12
7
|
module Dalli
|
13
8
|
module Protocol
|
14
9
|
##
|
@@ -16,170 +11,13 @@ module Dalli
|
|
16
11
|
# protocol. Contains logic for managing connection state to the server (retries, etc),
|
17
12
|
# formatting requests to the server, and unpacking responses.
|
18
13
|
##
|
19
|
-
class Binary
|
20
|
-
|
21
|
-
|
22
|
-
attr_accessor :weight, :options
|
23
|
-
|
24
|
-
def_delegators :@value_marshaller, :serializer, :compressor, :compression_min_size, :compress_by_default?
|
25
|
-
def_delegators :@connection_manager, :name, :sock, :hostname, :port, :close, :connected?, :socket_timeout,
|
26
|
-
:socket_type, :up!, :down!, :write, :reconnect_down_server?, :raise_down_error
|
27
|
-
|
28
|
-
def initialize(attribs, client_options = {})
|
29
|
-
hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
|
30
|
-
@options = client_options.merge(user_creds)
|
31
|
-
@value_marshaller = ValueMarshaller.new(@options)
|
32
|
-
@connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
|
33
|
-
@response_processor = ResponseProcessor.new(@connection_manager, @value_marshaller)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Chokepoint method for error handling and ensuring liveness
|
37
|
-
def request(opkey, *args)
|
38
|
-
verify_state(opkey)
|
39
|
-
|
40
|
-
begin
|
41
|
-
send(opkey, *args)
|
42
|
-
rescue Dalli::MarshalError => e
|
43
|
-
log_marshal_err(args.first, e)
|
44
|
-
raise
|
45
|
-
rescue Dalli::DalliError
|
46
|
-
raise
|
47
|
-
rescue StandardError => e
|
48
|
-
log_unexpected_err(e)
|
49
|
-
down!
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
##
|
54
|
-
# Boolean method used by clients of this class to determine if this
|
55
|
-
# particular memcached instance is available for use.
|
56
|
-
def alive?
|
57
|
-
ensure_connected!
|
58
|
-
rescue Dalli::NetworkError
|
59
|
-
# ensure_connected! raises a NetworkError if connection fails. We
|
60
|
-
# want to capture that error and convert it to a boolean value here.
|
61
|
-
false
|
62
|
-
end
|
63
|
-
|
64
|
-
def lock!; end
|
65
|
-
|
66
|
-
def unlock!; end
|
67
|
-
|
68
|
-
# Start reading key/value pairs from this connection. This is usually called
|
69
|
-
# after a series of GETKQ commands. A NOOP is sent, and the server begins
|
70
|
-
# flushing responses for kv pairs that were found.
|
71
|
-
#
|
72
|
-
# Returns nothing.
|
73
|
-
def pipeline_response_setup
|
74
|
-
verify_state(:getkq)
|
75
|
-
write_noop
|
76
|
-
response_buffer.reset
|
77
|
-
@connection_manager.start_request!
|
78
|
-
end
|
79
|
-
|
80
|
-
# Attempt to receive and parse as many key/value pairs as possible
|
81
|
-
# from this server. After #pipeline_response_setup, this should be invoked
|
82
|
-
# repeatedly whenever this server's socket is readable until
|
83
|
-
# #pipeline_complete?.
|
84
|
-
#
|
85
|
-
# Returns a Hash of kv pairs received.
|
86
|
-
def pipeline_next_responses
|
87
|
-
reconnect_on_pipeline_complete!
|
88
|
-
values = {}
|
89
|
-
|
90
|
-
response_buffer.read
|
91
|
-
|
92
|
-
resp_header, key, value = pipeline_response
|
93
|
-
# resp_header is not nil only if we have a full response to parse
|
94
|
-
# in the buffer
|
95
|
-
while resp_header
|
96
|
-
# If the status is ok and key is nil, then this is the response
|
97
|
-
# to the noop at the end of the pipeline
|
98
|
-
finish_pipeline && break if resp_header.ok? && key.nil?
|
99
|
-
|
100
|
-
# If the status is ok and the key is not nil, then this is a
|
101
|
-
# getkq response with a value that we want to set in the response hash
|
102
|
-
values[key] = [value, resp_header.cas] unless key.nil?
|
103
|
-
|
104
|
-
# Get the next response from the buffer
|
105
|
-
resp_header, key, value = pipeline_response
|
106
|
-
end
|
107
|
-
|
108
|
-
values
|
109
|
-
rescue SystemCallError, Timeout::Error, EOFError => e
|
110
|
-
@connection_manager.error_on_request!(e)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Abort current pipelined get. Generally used to signal an external
|
114
|
-
# timeout during pipelined get. The underlying socket is
|
115
|
-
# disconnected, and the exception is swallowed.
|
116
|
-
#
|
117
|
-
# Returns nothing.
|
118
|
-
def pipeline_abort
|
119
|
-
response_buffer.clear
|
120
|
-
@connection_manager.abort_request!
|
121
|
-
return true unless connected?
|
122
|
-
|
123
|
-
# Closes the connection, which ensures that our connection
|
124
|
-
# is in a clean state for future requests
|
125
|
-
@connection_manager.error_on_request!('External timeout')
|
126
|
-
rescue NetworkError
|
127
|
-
true
|
128
|
-
end
|
129
|
-
|
130
|
-
# Did the last call to #pipeline_response_setup complete successfully?
|
131
|
-
def pipeline_complete?
|
132
|
-
!response_buffer.in_progress?
|
133
|
-
end
|
134
|
-
|
135
|
-
def username
|
136
|
-
@options[:username] || ENV['MEMCACHE_USERNAME']
|
137
|
-
end
|
138
|
-
|
139
|
-
def password
|
140
|
-
@options[:password] || ENV['MEMCACHE_PASSWORD']
|
141
|
-
end
|
142
|
-
|
143
|
-
def require_auth?
|
144
|
-
!username.nil?
|
14
|
+
class Binary < Base
|
15
|
+
def response_processor
|
16
|
+
@response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
|
145
17
|
end
|
146
18
|
|
147
|
-
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
148
|
-
|
149
19
|
private
|
150
20
|
|
151
|
-
##
|
152
|
-
# Checks to see if we can execute the specified operation. Checks
|
153
|
-
# whether the connection is in use, and whether the command is allowed
|
154
|
-
##
|
155
|
-
def verify_state(opkey)
|
156
|
-
@connection_manager.confirm_ready!
|
157
|
-
verify_allowed_quiet!(opkey) if quiet?
|
158
|
-
|
159
|
-
# The ensure_connected call has the side effect of connecting the
|
160
|
-
# underlying socket if it is not connected, or there's been a disconnect
|
161
|
-
# because of timeout or other error. Method raises an error
|
162
|
-
# if it can't connect
|
163
|
-
raise_down_error unless ensure_connected!
|
164
|
-
end
|
165
|
-
|
166
|
-
# The socket connection to the underlying server is initialized as a side
|
167
|
-
# effect of this call. In fact, this is the ONLY place where that
|
168
|
-
# socket connection is initialized.
|
169
|
-
#
|
170
|
-
# Both this method and connect need to be in this class so we can do auth
|
171
|
-
# as required
|
172
|
-
#
|
173
|
-
# Since this is invoked exclusively in verify_state!, we don't need to worry about
|
174
|
-
# thread safety. Using it elsewhere may require revisiting that assumption.
|
175
|
-
def ensure_connected!
|
176
|
-
return true if connected?
|
177
|
-
return false unless reconnect_down_server?
|
178
|
-
|
179
|
-
connect # This call needs to be in this class so we can do auth
|
180
|
-
connected?
|
181
|
-
end
|
182
|
-
|
183
21
|
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
184
22
|
def verify_allowed_quiet!(opkey)
|
185
23
|
return if ALLOWED_QUIET_OPS.include?(opkey)
|
@@ -187,34 +25,28 @@ module Dalli
|
|
187
25
|
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
188
26
|
end
|
189
27
|
|
190
|
-
def quiet?
|
191
|
-
Thread.current[::Dalli::QUIET]
|
192
|
-
end
|
193
|
-
|
194
|
-
def cache_nils?(opts)
|
195
|
-
return false unless opts.is_a?(Hash)
|
196
|
-
|
197
|
-
opts[:cache_nils] ? true : false
|
198
|
-
end
|
199
|
-
|
200
28
|
# Retrieval Commands
|
201
29
|
def get(key, options = nil)
|
202
30
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
203
31
|
write(req)
|
204
|
-
|
32
|
+
response_processor.get(cache_nils: cache_nils?(options))
|
33
|
+
end
|
34
|
+
|
35
|
+
def quiet_get_request(key)
|
36
|
+
RequestFormatter.standard_request(opkey: :getkq, key: key)
|
205
37
|
end
|
206
38
|
|
207
39
|
def gat(key, ttl, options = nil)
|
208
40
|
ttl = TtlSanitizer.sanitize(ttl)
|
209
41
|
req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
|
210
42
|
write(req)
|
211
|
-
|
43
|
+
response_processor.get(cache_nils: cache_nils?(options))
|
212
44
|
end
|
213
45
|
|
214
46
|
def touch(key, ttl)
|
215
47
|
ttl = TtlSanitizer.sanitize(ttl)
|
216
48
|
write(RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl))
|
217
|
-
|
49
|
+
response_processor.generic_response
|
218
50
|
end
|
219
51
|
|
220
52
|
# TODO: This is confusing, as there's a cas command in memcached
|
@@ -222,7 +54,7 @@ module Dalli
|
|
222
54
|
def cas(key)
|
223
55
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
224
56
|
write(req)
|
225
|
-
|
57
|
+
response_processor.data_cas_response
|
226
58
|
end
|
227
59
|
|
228
60
|
# Storage Commands
|
@@ -250,7 +82,7 @@ module Dalli
|
|
250
82
|
value: value, bitflags: bitflags,
|
251
83
|
ttl: ttl, cas: cas)
|
252
84
|
write(req)
|
253
|
-
|
85
|
+
response_processor.storage_response unless quiet?
|
254
86
|
end
|
255
87
|
# rubocop:enable Metrics/ParameterLists
|
256
88
|
|
@@ -266,7 +98,7 @@ module Dalli
|
|
266
98
|
|
267
99
|
def write_append_prepend(opkey, key, value)
|
268
100
|
write(RequestFormatter.standard_request(opkey: opkey, key: key, value: value))
|
269
|
-
|
101
|
+
response_processor.no_body_response unless quiet?
|
270
102
|
end
|
271
103
|
|
272
104
|
# Delete Commands
|
@@ -274,7 +106,7 @@ module Dalli
|
|
274
106
|
opkey = quiet? ? :deleteq : :delete
|
275
107
|
req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
|
276
108
|
write(req)
|
277
|
-
|
109
|
+
response_processor.delete unless quiet?
|
278
110
|
end
|
279
111
|
|
280
112
|
# Arithmetic Commands
|
@@ -300,37 +132,37 @@ module Dalli
|
|
300
132
|
initial ||= 0
|
301
133
|
write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
|
302
134
|
count: count, initial: initial, expiry: expiry))
|
303
|
-
|
135
|
+
response_processor.decr_incr unless quiet?
|
304
136
|
end
|
305
137
|
|
306
138
|
# Other Commands
|
307
139
|
def flush(ttl = 0)
|
308
140
|
opkey = quiet? ? :flushq : :flush
|
309
141
|
write(RequestFormatter.standard_request(opkey: opkey, ttl: ttl))
|
310
|
-
|
142
|
+
response_processor.no_body_response unless quiet?
|
311
143
|
end
|
312
144
|
|
313
145
|
# Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
|
314
146
|
# We need to read all the responses at once.
|
315
147
|
def noop
|
316
148
|
write_noop
|
317
|
-
|
149
|
+
response_processor.consume_all_responses_until_noop
|
318
150
|
end
|
319
151
|
|
320
152
|
def stats(info = '')
|
321
153
|
req = RequestFormatter.standard_request(opkey: :stat, key: info)
|
322
154
|
write(req)
|
323
|
-
|
155
|
+
response_processor.stats
|
324
156
|
end
|
325
157
|
|
326
158
|
def reset_stats
|
327
159
|
write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
|
328
|
-
|
160
|
+
response_processor.reset
|
329
161
|
end
|
330
162
|
|
331
163
|
def version
|
332
164
|
write(RequestFormatter.standard_request(opkey: :version))
|
333
|
-
|
165
|
+
response_processor.version
|
334
166
|
end
|
335
167
|
|
336
168
|
def write_noop
|
@@ -338,55 +170,10 @@ module Dalli
|
|
338
170
|
write(req)
|
339
171
|
end
|
340
172
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
up!
|
346
|
-
rescue Dalli::DalliError
|
347
|
-
raise
|
348
|
-
end
|
349
|
-
|
350
|
-
def pipelined_get(keys)
|
351
|
-
req = +''
|
352
|
-
keys.each do |key|
|
353
|
-
req << RequestFormatter.standard_request(opkey: :getkq, key: key)
|
354
|
-
end
|
355
|
-
# Could send noop here instead of in pipeline_response_setup
|
356
|
-
write(req)
|
357
|
-
end
|
358
|
-
|
359
|
-
def response_buffer
|
360
|
-
@response_buffer ||= ResponseBuffer.new(@connection_manager, @response_processor)
|
361
|
-
end
|
362
|
-
|
363
|
-
def pipeline_response
|
364
|
-
response_buffer.process_single_getk_response
|
365
|
-
end
|
366
|
-
|
367
|
-
# Called after the noop response is received at the end of a set
|
368
|
-
# of pipelined gets
|
369
|
-
def finish_pipeline
|
370
|
-
response_buffer.clear
|
371
|
-
@connection_manager.finish_request!
|
372
|
-
|
373
|
-
true # to simplify response
|
374
|
-
end
|
375
|
-
|
376
|
-
def reconnect_on_pipeline_complete!
|
377
|
-
@connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
|
378
|
-
end
|
379
|
-
|
380
|
-
def log_marshal_err(key, err)
|
381
|
-
Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
|
382
|
-
Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
|
383
|
-
end
|
384
|
-
|
385
|
-
def log_unexpected_err(err)
|
386
|
-
Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
|
387
|
-
Dalli.logger.error err.backtrace.join("\n\t")
|
388
|
-
end
|
389
|
-
|
173
|
+
require_relative 'binary/request_formatter'
|
174
|
+
require_relative 'binary/response_header'
|
175
|
+
require_relative 'binary/response_processor'
|
176
|
+
require_relative 'binary/sasl_authentication'
|
390
177
|
include SaslAuthentication
|
391
178
|
end
|
392
179
|
end
|
@@ -21,9 +21,9 @@ module Dalli
|
|
21
21
|
# Attempts to process a single response from the buffer. Starts
|
22
22
|
# by advancing the buffer to the specified start position
|
23
23
|
def process_single_getk_response
|
24
|
-
bytes,
|
24
|
+
bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer)
|
25
25
|
advance(bytes)
|
26
|
-
[
|
26
|
+
[status, cas, key, value]
|
27
27
|
end
|
28
28
|
|
29
29
|
# Advances the internal response buffer by bytes_to_advance
|
@@ -31,13 +31,13 @@ module Dalli
|
|
31
31
|
def advance(bytes_to_advance)
|
32
32
|
return unless bytes_to_advance.positive?
|
33
33
|
|
34
|
-
@buffer = @buffer
|
34
|
+
@buffer = @buffer.byteslice(bytes_to_advance..-1)
|
35
35
|
end
|
36
36
|
|
37
37
|
# Resets the internal buffer to an empty state,
|
38
38
|
# so that we're ready to read pipelined responses
|
39
39
|
def reset
|
40
|
-
@buffer =
|
40
|
+
@buffer = ''.b
|
41
41
|
end
|
42
42
|
|
43
43
|
# Clear the internal response buffer
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
@@ -62,6 +62,7 @@ require_relative 'dalli/key_manager'
|
|
62
62
|
require_relative 'dalli/pipelined_getter'
|
63
63
|
require_relative 'dalli/ring'
|
64
64
|
require_relative 'dalli/protocol'
|
65
|
+
require_relative 'dalli/protocol/base'
|
65
66
|
require_relative 'dalli/protocol/binary'
|
66
67
|
require_relative 'dalli/protocol/connection_manager'
|
67
68
|
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: 3.1.
|
4
|
+
version: 3.1.5
|
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: 2021-12-
|
12
|
+
date: 2021-12-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: connection_pool
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- lib/dalli/options.rb
|
122
122
|
- lib/dalli/pipelined_getter.rb
|
123
123
|
- lib/dalli/protocol.rb
|
124
|
+
- lib/dalli/protocol/base.rb
|
124
125
|
- lib/dalli/protocol/binary.rb
|
125
126
|
- lib/dalli/protocol/binary/request_formatter.rb
|
126
127
|
- lib/dalli/protocol/binary/response_header.rb
|