dalli 3.0.5 → 3.1.2
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 +29 -0
- data/lib/dalli/client.rb +163 -261
- data/lib/dalli/options.rb +3 -3
- data/lib/dalli/pipelined_getter.rb +177 -0
- data/lib/dalli/protocol/binary/request_formatter.rb +11 -3
- data/lib/dalli/protocol/binary/response_header.rb +36 -0
- data/lib/dalli/protocol/binary/response_processor.rb +96 -45
- data/lib/dalli/protocol/binary/sasl_authentication.rb +7 -4
- data/lib/dalli/protocol/binary.rb +202 -352
- data/lib/dalli/protocol/connection_manager.rb +242 -0
- data/lib/dalli/protocol/response_buffer.rb +53 -0
- data/lib/dalli/protocol/server_config_parser.rb +7 -7
- data/lib/dalli/ring.rb +5 -1
- data/lib/dalli/socket.rb +8 -6
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +24 -16
- data/lib/rack/session/dalli.rb +83 -74
- metadata +14 -4
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'English'
|
4
3
|
require 'forwardable'
|
5
4
|
require 'socket'
|
6
5
|
require 'timeout'
|
7
6
|
|
8
7
|
require_relative 'binary/request_formatter'
|
8
|
+
require_relative 'binary/response_header'
|
9
9
|
require_relative 'binary/response_processor'
|
10
10
|
require_relative 'binary/sasl_authentication'
|
11
11
|
|
@@ -19,59 +19,30 @@ module Dalli
|
|
19
19
|
class Binary
|
20
20
|
extend Forwardable
|
21
21
|
|
22
|
-
attr_accessor :
|
23
|
-
attr_reader :sock, :socket_type
|
22
|
+
attr_accessor :weight, :options
|
24
23
|
|
25
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
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# connect/read/write timeout for socket operations
|
31
|
-
socket_timeout: 1,
|
32
|
-
# times a socket operation may fail before considering the server dead
|
33
|
-
socket_max_failures: 2,
|
34
|
-
# amount of time to sleep between retries when a failure occurs
|
35
|
-
socket_failure_delay: 0.1,
|
36
|
-
username: nil,
|
37
|
-
password: nil
|
38
|
-
}.freeze
|
39
|
-
|
40
|
-
def initialize(attribs, options = {})
|
41
|
-
@hostname, @port, @weight, @socket_type, options = ServerConfigParser.parse(attribs, options)
|
42
|
-
@options = DEFAULTS.merge(options)
|
28
|
+
def initialize(attribs, client_options = {})
|
29
|
+
hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
|
30
|
+
@options = client_options.merge(user_creds)
|
43
31
|
@value_marshaller = ValueMarshaller.new(@options)
|
44
|
-
@
|
45
|
-
|
46
|
-
reset_down_info
|
47
|
-
@sock = nil
|
48
|
-
@pid = nil
|
49
|
-
@request_in_progress = false
|
50
|
-
end
|
51
|
-
|
52
|
-
def name
|
53
|
-
if socket_type == :unix
|
54
|
-
hostname
|
55
|
-
else
|
56
|
-
"#{hostname}:#{port}"
|
57
|
-
end
|
32
|
+
@connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
|
33
|
+
@response_processor = ResponseProcessor.new(@connection_manager, @value_marshaller)
|
58
34
|
end
|
59
35
|
|
60
36
|
# Chokepoint method for error handling and ensuring liveness
|
61
|
-
def request(
|
62
|
-
verify_state
|
63
|
-
# The alive? call has the side effect of connecting the underlying
|
64
|
-
# socket if it is not connected, or there's been a disconnect
|
65
|
-
# because of timeout or other error. Method raises an error
|
66
|
-
# if it can't connect
|
67
|
-
raise_memcached_down_err unless alive?
|
37
|
+
def request(opkey, *args)
|
38
|
+
verify_state(opkey)
|
68
39
|
|
69
40
|
begin
|
70
|
-
send(
|
41
|
+
send(opkey, *args)
|
71
42
|
rescue Dalli::MarshalError => e
|
72
|
-
|
43
|
+
log_marshal_err(args.first, e)
|
73
44
|
raise
|
74
|
-
rescue Dalli::DalliError
|
45
|
+
rescue Dalli::DalliError
|
75
46
|
raise
|
76
47
|
rescue StandardError => e
|
77
48
|
log_unexpected_err(e)
|
@@ -79,63 +50,17 @@ module Dalli
|
|
79
50
|
end
|
80
51
|
end
|
81
52
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
"ensure memcached version is > #{::Dalli::MIN_SUPPORTED_MEMCACHED_VERSION}."
|
86
|
-
end
|
87
|
-
|
88
|
-
def log_marshall_err(key, err)
|
89
|
-
Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
|
90
|
-
Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
|
91
|
-
end
|
92
|
-
|
93
|
-
def log_unexpected_err(err)
|
94
|
-
Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
|
95
|
-
Dalli.logger.error err.backtrace.join("\n\t")
|
96
|
-
end
|
97
|
-
|
98
|
-
# The socket connection to the underlying server is initialized as a side
|
99
|
-
# effect of this call. In fact, this is the ONLY place where that
|
100
|
-
# socket connection is initialized.
|
53
|
+
##
|
54
|
+
# Boolean method used by clients of this class to determine if this
|
55
|
+
# particular memcached instance is available for use.
|
101
56
|
def alive?
|
102
|
-
|
103
|
-
return false unless reconnect_down_server?
|
104
|
-
|
105
|
-
connect
|
106
|
-
!!@sock
|
57
|
+
ensure_connected!
|
107
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.
|
108
61
|
false
|
109
62
|
end
|
110
63
|
|
111
|
-
def reconnect_down_server?
|
112
|
-
return true unless @last_down_at
|
113
|
-
|
114
|
-
time_to_next_reconnect = @last_down_at + options[:down_retry_delay] - Time.now
|
115
|
-
return true unless time_to_next_reconnect.positive?
|
116
|
-
|
117
|
-
Dalli.logger.debug do
|
118
|
-
format('down_retry_delay not reached for %<name>s (%<time>.3f seconds left)', name: name,
|
119
|
-
time: time_to_next_reconnect)
|
120
|
-
end
|
121
|
-
false
|
122
|
-
end
|
123
|
-
|
124
|
-
# Closes the underlying socket and cleans up
|
125
|
-
# socket state.
|
126
|
-
def close
|
127
|
-
return unless @sock
|
128
|
-
|
129
|
-
begin
|
130
|
-
@sock.close
|
131
|
-
rescue StandardError
|
132
|
-
nil
|
133
|
-
end
|
134
|
-
@sock = nil
|
135
|
-
@pid = nil
|
136
|
-
abort_request!
|
137
|
-
end
|
138
|
-
|
139
64
|
def lock!; end
|
140
65
|
|
141
66
|
def unlock!; end
|
@@ -145,213 +70,126 @@ module Dalli
|
|
145
70
|
# flushing responses for kv pairs that were found.
|
146
71
|
#
|
147
72
|
# Returns nothing.
|
148
|
-
def
|
149
|
-
verify_state
|
73
|
+
def pipeline_response_setup
|
74
|
+
verify_state(:getkq)
|
150
75
|
write_noop
|
151
|
-
|
152
|
-
@
|
153
|
-
start_request!
|
154
|
-
end
|
155
|
-
|
156
|
-
# Did the last call to #multi_response_start complete successfully?
|
157
|
-
def multi_response_completed?
|
158
|
-
@multi_buffer.nil?
|
76
|
+
response_buffer.reset
|
77
|
+
@connection_manager.start_request!
|
159
78
|
end
|
160
79
|
|
161
80
|
# Attempt to receive and parse as many key/value pairs as possible
|
162
|
-
# from this server. After #
|
81
|
+
# from this server. After #pipeline_response_setup, this should be invoked
|
163
82
|
# repeatedly whenever this server's socket is readable until
|
164
|
-
# #
|
83
|
+
# #pipeline_complete?.
|
165
84
|
#
|
166
85
|
# Returns a Hash of kv pairs received.
|
167
|
-
def
|
168
|
-
|
169
|
-
|
170
|
-
@multi_buffer << @sock.read_available
|
171
|
-
buf = @multi_buffer
|
172
|
-
pos = @position
|
86
|
+
def pipeline_next_responses
|
87
|
+
reconnect_on_pipeline_complete!
|
173
88
|
values = {}
|
174
89
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
#
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
key, value = @response_processor.unpack_response_body(extra_len, key_len, body, true)
|
192
|
-
values[key] = [value, cas]
|
193
|
-
rescue DalliError
|
194
|
-
# TODO: Determine if we should be swallowing
|
195
|
-
# this error
|
196
|
-
end
|
197
|
-
|
198
|
-
pos = pos + ResponseProcessor::RESP_HEADER_SIZE + body_len
|
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
|
199
106
|
end
|
200
|
-
# TODO: We should be discarding the already processed buffer at this point
|
201
|
-
@position = pos
|
202
107
|
|
203
108
|
values
|
204
109
|
rescue SystemCallError, Timeout::Error, EOFError => e
|
205
|
-
|
206
|
-
end
|
207
|
-
|
208
|
-
def finish_multi_response
|
209
|
-
@multi_buffer = nil
|
210
|
-
@position = nil
|
211
|
-
finish_request!
|
110
|
+
@connection_manager.error_on_request!(e)
|
212
111
|
end
|
213
112
|
|
214
|
-
# Abort
|
215
|
-
# timeout.
|
216
|
-
# swallowed.
|
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.
|
217
116
|
#
|
218
117
|
# Returns nothing.
|
219
|
-
def
|
220
|
-
|
221
|
-
@
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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')
|
226
126
|
rescue NetworkError
|
227
127
|
true
|
228
128
|
end
|
229
129
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
finish_request!
|
234
|
-
data
|
235
|
-
rescue SystemCallError, Timeout::Error, EOFError => e
|
236
|
-
failure!(e)
|
130
|
+
# Did the last call to #pipeline_response_setup complete successfully?
|
131
|
+
def pipeline_complete?
|
132
|
+
!response_buffer.in_progress?
|
237
133
|
end
|
238
134
|
|
239
|
-
def
|
240
|
-
|
241
|
-
result = @sock.write(bytes)
|
242
|
-
finish_request!
|
243
|
-
result
|
244
|
-
rescue SystemCallError, Timeout::Error => e
|
245
|
-
failure!(e)
|
246
|
-
end
|
247
|
-
|
248
|
-
def socket_timeout
|
249
|
-
@socket_timeout ||= @options[:socket_timeout]
|
250
|
-
end
|
251
|
-
|
252
|
-
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
253
|
-
|
254
|
-
private
|
255
|
-
|
256
|
-
def request_in_progress?
|
257
|
-
@request_in_progress
|
258
|
-
end
|
259
|
-
|
260
|
-
def start_request!
|
261
|
-
@request_in_progress = true
|
262
|
-
end
|
263
|
-
|
264
|
-
def finish_request!
|
265
|
-
@request_in_progress = false
|
266
|
-
end
|
267
|
-
|
268
|
-
def abort_request!
|
269
|
-
@request_in_progress = false
|
270
|
-
end
|
271
|
-
|
272
|
-
def verify_state
|
273
|
-
failure!(RuntimeError.new('Already writing to socket')) if request_in_progress?
|
274
|
-
reconnect_on_fork if fork_detected?
|
135
|
+
def username
|
136
|
+
@options[:username] || ENV['MEMCACHE_USERNAME']
|
275
137
|
end
|
276
138
|
|
277
|
-
def
|
278
|
-
@
|
139
|
+
def password
|
140
|
+
@options[:password] || ENV['MEMCACHE_PASSWORD']
|
279
141
|
end
|
280
142
|
|
281
|
-
def
|
282
|
-
|
283
|
-
Dalli.logger.info { message }
|
284
|
-
reconnect! message
|
143
|
+
def require_auth?
|
144
|
+
!username.nil?
|
285
145
|
end
|
286
146
|
|
287
|
-
|
288
|
-
|
289
|
-
# to clean up socket state
|
290
|
-
def reconnect!(message)
|
291
|
-
close
|
292
|
-
sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
|
293
|
-
raise Dalli::NetworkError, message
|
147
|
+
def quiet?
|
148
|
+
Thread.current[::Dalli::QUIET]
|
294
149
|
end
|
150
|
+
alias multi? quiet?
|
295
151
|
|
296
|
-
#
|
297
|
-
def failure!(exception)
|
298
|
-
message = "#{name} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
|
299
|
-
Dalli.logger.warn { message }
|
300
|
-
|
301
|
-
@fail_count += 1
|
302
|
-
if @fail_count >= options[:socket_max_failures]
|
303
|
-
down!
|
304
|
-
else
|
305
|
-
reconnect! 'Socket operation failed, retrying...'
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
# Marks the server instance as down. Updates the down_at state
|
310
|
-
# and raises an Dalli::NetworkError that includes the underlying
|
311
|
-
# error in the message. Calls close to clean up socket state
|
312
|
-
def down!
|
313
|
-
close
|
314
|
-
log_down_detected
|
152
|
+
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
315
153
|
|
316
|
-
|
317
|
-
@msg ||= $ERROR_INFO&.message
|
318
|
-
raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
|
319
|
-
end
|
154
|
+
private
|
320
155
|
|
321
|
-
|
322
|
-
|
156
|
+
##
|
157
|
+
# Checks to see if we can execute the specified operation. Checks
|
158
|
+
# whether the connection is in use, and whether the command is allowed
|
159
|
+
##
|
160
|
+
def verify_state(opkey)
|
161
|
+
@connection_manager.confirm_ready!
|
162
|
+
verify_allowed_quiet!(opkey) if quiet?
|
323
163
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
Dalli.logger.warn("#{name} is down")
|
330
|
-
end
|
164
|
+
# The ensure_connected call has the side effect of connecting the
|
165
|
+
# underlying socket if it is not connected, or there's been a disconnect
|
166
|
+
# because of timeout or other error. Method raises an error
|
167
|
+
# if it can't connect
|
168
|
+
raise_down_error unless ensure_connected!
|
331
169
|
end
|
332
170
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
171
|
+
# The socket connection to the underlying server is initialized as a side
|
172
|
+
# effect of this call. In fact, this is the ONLY place where that
|
173
|
+
# socket connection is initialized.
|
174
|
+
#
|
175
|
+
# Both this method and connect need to be in this class so we can do auth
|
176
|
+
# as required
|
177
|
+
#
|
178
|
+
# Since this is invoked exclusively in verify_state!, we don't need to worry about
|
179
|
+
# thread safety. Using it elsewhere may require revisiting that assumption.
|
180
|
+
def ensure_connected!
|
181
|
+
return true if connected?
|
182
|
+
return false unless reconnect_down_server?
|
339
183
|
|
340
|
-
|
341
|
-
|
342
|
-
reset_down_info
|
184
|
+
connect # This call needs to be in this class so we can do auth
|
185
|
+
connected?
|
343
186
|
end
|
344
187
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
@last_down_at = nil
|
349
|
-
@msg = nil
|
350
|
-
@error = nil
|
351
|
-
end
|
188
|
+
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
189
|
+
def verify_allowed_quiet!(opkey)
|
190
|
+
return if ALLOWED_QUIET_OPS.include?(opkey)
|
352
191
|
|
353
|
-
|
354
|
-
Thread.current[:dalli_multi]
|
192
|
+
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
355
193
|
end
|
356
194
|
|
357
195
|
def cache_nils?(opts)
|
@@ -360,39 +198,52 @@ module Dalli
|
|
360
198
|
opts[:cache_nils] ? true : false
|
361
199
|
end
|
362
200
|
|
201
|
+
# Retrieval Commands
|
363
202
|
def get(key, options = nil)
|
364
203
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
365
204
|
write(req)
|
366
205
|
@response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
|
367
206
|
end
|
368
207
|
|
369
|
-
def
|
370
|
-
|
371
|
-
|
372
|
-
req << RequestFormatter.standard_request(opkey: :getkq, key: key)
|
373
|
-
end
|
374
|
-
# Could send noop here instead of in multi_response_start
|
208
|
+
def gat(key, ttl, options = nil)
|
209
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
210
|
+
req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
|
375
211
|
write(req)
|
212
|
+
@response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
|
213
|
+
end
|
214
|
+
|
215
|
+
def touch(key, ttl)
|
216
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
217
|
+
write(RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl))
|
218
|
+
@response_processor.generic_response
|
376
219
|
end
|
377
220
|
|
221
|
+
# TODO: This is confusing, as there's a cas command in memcached
|
222
|
+
# and this isn't it. Maybe rename? Maybe eliminate?
|
223
|
+
def cas(key)
|
224
|
+
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
225
|
+
write(req)
|
226
|
+
@response_processor.data_cas_response
|
227
|
+
end
|
228
|
+
|
229
|
+
# Storage Commands
|
378
230
|
def set(key, value, ttl, cas, options)
|
379
|
-
opkey =
|
380
|
-
|
231
|
+
opkey = quiet? ? :setq : :set
|
232
|
+
storage_req(opkey, key, value, ttl, cas, options)
|
381
233
|
end
|
382
234
|
|
383
235
|
def add(key, value, ttl, options)
|
384
|
-
opkey =
|
385
|
-
|
386
|
-
process_value_req(opkey, key, value, ttl, cas, options)
|
236
|
+
opkey = quiet? ? :addq : :add
|
237
|
+
storage_req(opkey, key, value, ttl, 0, options)
|
387
238
|
end
|
388
239
|
|
389
240
|
def replace(key, value, ttl, cas, options)
|
390
|
-
opkey =
|
391
|
-
|
241
|
+
opkey = quiet? ? :replaceq : :replace
|
242
|
+
storage_req(opkey, key, value, ttl, cas, options)
|
392
243
|
end
|
393
244
|
|
394
245
|
# rubocop:disable Metrics/ParameterLists
|
395
|
-
def
|
246
|
+
def storage_req(opkey, key, value, ttl, cas, options)
|
396
247
|
(value, bitflags) = @value_marshaller.store(key, value, options)
|
397
248
|
ttl = TtlSanitizer.sanitize(ttl)
|
398
249
|
|
@@ -400,21 +251,42 @@ module Dalli
|
|
400
251
|
value: value, bitflags: bitflags,
|
401
252
|
ttl: ttl, cas: cas)
|
402
253
|
write(req)
|
403
|
-
@response_processor.
|
254
|
+
@response_processor.storage_response unless quiet?
|
404
255
|
end
|
405
256
|
# rubocop:enable Metrics/ParameterLists
|
406
257
|
|
258
|
+
def append(key, value)
|
259
|
+
opkey = quiet? ? :appendq : :append
|
260
|
+
write_append_prepend opkey, key, value
|
261
|
+
end
|
262
|
+
|
263
|
+
def prepend(key, value)
|
264
|
+
opkey = quiet? ? :prependq : :prepend
|
265
|
+
write_append_prepend opkey, key, value
|
266
|
+
end
|
267
|
+
|
268
|
+
def write_append_prepend(opkey, key, value)
|
269
|
+
write(RequestFormatter.standard_request(opkey: opkey, key: key, value: value))
|
270
|
+
@response_processor.no_body_response unless quiet?
|
271
|
+
end
|
272
|
+
|
273
|
+
# Delete Commands
|
407
274
|
def delete(key, cas)
|
408
|
-
opkey =
|
275
|
+
opkey = quiet? ? :deleteq : :delete
|
409
276
|
req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
|
410
277
|
write(req)
|
411
|
-
@response_processor.
|
278
|
+
@response_processor.no_body_response unless quiet?
|
412
279
|
end
|
413
280
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
281
|
+
# Arithmetic Commands
|
282
|
+
def decr(key, count, ttl, initial)
|
283
|
+
opkey = quiet? ? :decrq : :decr
|
284
|
+
decr_incr opkey, key, count, ttl, initial
|
285
|
+
end
|
286
|
+
|
287
|
+
def incr(key, count, ttl, initial)
|
288
|
+
opkey = quiet? ? :incrq : :incr
|
289
|
+
decr_incr opkey, key, count, ttl, initial
|
418
290
|
end
|
419
291
|
|
420
292
|
# This allows us to special case a nil initial value, and
|
@@ -429,29 +301,14 @@ module Dalli
|
|
429
301
|
initial ||= 0
|
430
302
|
write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
|
431
303
|
count: count, initial: initial, expiry: expiry))
|
432
|
-
@response_processor.decr_incr_response
|
433
|
-
end
|
434
|
-
|
435
|
-
def decr(key, count, ttl, initial)
|
436
|
-
decr_incr :decr, key, count, ttl, initial
|
437
|
-
end
|
438
|
-
|
439
|
-
def incr(key, count, ttl, initial)
|
440
|
-
decr_incr :incr, key, count, ttl, initial
|
304
|
+
@response_processor.decr_incr_response unless quiet?
|
441
305
|
end
|
442
306
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
write(bytes)
|
449
|
-
@response_processor.generic_response
|
450
|
-
end
|
451
|
-
|
452
|
-
def write_noop
|
453
|
-
req = RequestFormatter.standard_request(opkey: :noop)
|
454
|
-
write(req)
|
307
|
+
# Other Commands
|
308
|
+
def flush(ttl = 0)
|
309
|
+
opkey = quiet? ? :flushq : :flush
|
310
|
+
write(RequestFormatter.standard_request(opkey: opkey, ttl: ttl))
|
311
|
+
@response_processor.no_body_response unless quiet?
|
455
312
|
end
|
456
313
|
|
457
314
|
# Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
|
@@ -461,14 +318,6 @@ module Dalli
|
|
461
318
|
@response_processor.multi_with_keys_response
|
462
319
|
end
|
463
320
|
|
464
|
-
def append(key, value)
|
465
|
-
write_append_prepend :append, key, value
|
466
|
-
end
|
467
|
-
|
468
|
-
def prepend(key, value)
|
469
|
-
write_append_prepend :prepend, key, value
|
470
|
-
end
|
471
|
-
|
472
321
|
def stats(info = '')
|
473
322
|
req = RequestFormatter.standard_request(opkey: :stat, key: info)
|
474
323
|
write(req)
|
@@ -476,66 +325,67 @@ module Dalli
|
|
476
325
|
end
|
477
326
|
|
478
327
|
def reset_stats
|
479
|
-
|
328
|
+
write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
|
329
|
+
@response_processor.generic_response
|
480
330
|
end
|
481
331
|
|
482
|
-
def
|
483
|
-
|
484
|
-
|
485
|
-
@response_processor.data_cas_response
|
332
|
+
def version
|
333
|
+
write(RequestFormatter.standard_request(opkey: :version))
|
334
|
+
@response_processor.generic_response
|
486
335
|
end
|
487
336
|
|
488
|
-
def
|
489
|
-
|
337
|
+
def write_noop
|
338
|
+
req = RequestFormatter.standard_request(opkey: :noop)
|
339
|
+
write(req)
|
490
340
|
end
|
491
341
|
|
492
|
-
def
|
493
|
-
|
494
|
-
|
342
|
+
def connect
|
343
|
+
@connection_manager.establish_connection
|
344
|
+
authenticate_connection if require_auth?
|
345
|
+
@version = version # Connect socket if not authed
|
346
|
+
up!
|
347
|
+
rescue Dalli::DalliError
|
348
|
+
raise
|
495
349
|
end
|
496
350
|
|
497
|
-
def
|
498
|
-
|
499
|
-
|
351
|
+
def pipelined_get(keys)
|
352
|
+
req = +''
|
353
|
+
keys.each do |key|
|
354
|
+
req << RequestFormatter.standard_request(opkey: :getkq, key: key)
|
355
|
+
end
|
356
|
+
# Could send noop here instead of in pipeline_response_setup
|
500
357
|
write(req)
|
501
|
-
@response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
|
502
358
|
end
|
503
359
|
|
504
|
-
def
|
505
|
-
|
360
|
+
def response_buffer
|
361
|
+
@response_buffer ||= ResponseBuffer.new(@connection_manager, @response_processor)
|
362
|
+
end
|
506
363
|
|
507
|
-
|
508
|
-
|
509
|
-
@sock = memcached_socket
|
510
|
-
authenticate_connection if require_auth?
|
511
|
-
@version = version # Connect socket if not authed
|
512
|
-
up!
|
513
|
-
rescue Dalli::DalliError # SASL auth failure
|
514
|
-
raise
|
515
|
-
rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
|
516
|
-
# SocketError = DNS resolution failure
|
517
|
-
failure!(e)
|
518
|
-
end
|
364
|
+
def pipeline_response
|
365
|
+
response_buffer.process_single_getk_response
|
519
366
|
end
|
520
367
|
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
368
|
+
# Called after the noop response is received at the end of a set
|
369
|
+
# of pipelined gets
|
370
|
+
def finish_pipeline
|
371
|
+
response_buffer.clear
|
372
|
+
@connection_manager.finish_request!
|
373
|
+
|
374
|
+
true # to simplify response
|
527
375
|
end
|
528
376
|
|
529
|
-
def
|
530
|
-
!
|
377
|
+
def reconnect_on_pipeline_complete!
|
378
|
+
@connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
|
531
379
|
end
|
532
380
|
|
533
|
-
def
|
534
|
-
|
381
|
+
def log_marshal_err(key, err)
|
382
|
+
Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
|
383
|
+
Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
|
535
384
|
end
|
536
385
|
|
537
|
-
def
|
538
|
-
|
386
|
+
def log_unexpected_err(err)
|
387
|
+
Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
|
388
|
+
Dalli.logger.error err.backtrace.join("\n\t")
|
539
389
|
end
|
540
390
|
|
541
391
|
include SaslAuthentication
|