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