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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5450b3f6caa26344dbcf9718ee2e12c98d593df098d5aec6f41655783675e2b8
4
- data.tar.gz: fdc8ff913351a2ed85fb34c0aa50506f5014e29c3671cbf312eaf7255b526742
3
+ metadata.gz: 89dd04ff2b4fd6812f2fae613cc984c7d34029a2637ebf5c11d5398361fa7993
4
+ data.tar.gz: caf5ad6315ddb803ba61418d189c99ce7849f1acced49cf6e32e78e5ca35817f
5
5
  SHA512:
6
- metadata.gz: 226719cde318de90be8fb77160b926ddbc75002ab0632494c7de0d1917bc88a8fde01b0e827765db3baa5f439c42f840a298c546d56473109e65003509d452ff
7
- data.tar.gz: 6b9ca93a048ae7a5074bd128432ceb1f19497e16c773a4a246ba1da13ecb1d7e9024c41f9e112ecea2244205afded043d893c8368fcf5c137b14f134b9f1548e
6
+ metadata.gz: e425b9704308ce7f3737c7e07dcd2699448ba173fd8fa3ac1e9d3d8867890ce1029dd69f98fb4856bab6c8d81685d34be1c41a192039fccb631c2d4b4749b85a
7
+ data.tar.gz: 0afdb9aa3fde689a6b84b72aa44aa228e23994f747cce60f8a3471f250a7fa60fdbb3481e4ab4f2665de80be4799b3294f308bb86efa815867e121cd7dc223c9
data/History.md CHANGED
@@ -1,6 +1,16 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ Unreleased
5
+ ==========
6
+
7
+ 3.1.1
8
+ ==========
9
+
10
+ - Add quiet support for incr, decr, append, depend, and flush (petergoldstein)
11
+ - Additional refactoring to allow reuse of connection behavior (petergoldstein)
12
+ - Fix issue in flush such that it wasn't passing the delay argument to memcached (petergoldstein)
13
+
4
14
  3.1.0
5
15
  ==========
6
16
 
data/lib/dalli/client.rb CHANGED
@@ -49,7 +49,7 @@ module Dalli
49
49
  def initialize(servers = nil, options = {})
50
50
  @servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
51
51
  @options = normalize_options(options)
52
- @key_manager = ::Dalli::KeyManager.new(options)
52
+ @key_manager = ::Dalli::KeyManager.new(@options)
53
53
  @ring = nil
54
54
  end
55
55
 
@@ -58,24 +58,39 @@ module Dalli
58
58
  #
59
59
 
60
60
  ##
61
- # Turn on quiet aka noreply support.
62
- # All relevant operations within this block will be effectively
63
- # pipelined as Dalli will use 'quiet' operations where possible.
64
- # Currently supports the set, add, replace and delete operations.
65
- def multi
66
- old = Thread.current[::Dalli::MULTI_KEY]
67
- Thread.current[::Dalli::MULTI_KEY] = true
68
- yield
69
- ensure
70
- @ring&.pipeline_consume_and_ignore_responses
71
- Thread.current[::Dalli::MULTI_KEY] = old
61
+ # Get the value associated with the key.
62
+ # If a value is not found, then +nil+ is returned.
63
+ def get(key, req_options = nil)
64
+ perform(:get, key, req_options)
72
65
  end
73
66
 
74
67
  ##
75
- # Get the value associated with the key.
68
+ # Gat (get and touch) fetch an item and simultaneously update its expiration time.
69
+ #
76
70
  # If a value is not found, then +nil+ is returned.
77
- def get(key, options = nil)
78
- perform(:get, key, options)
71
+ def gat(key, ttl = nil)
72
+ perform(:gat, key, ttl_or_default(ttl))
73
+ end
74
+
75
+ ##
76
+ # Touch updates expiration time for a given key.
77
+ #
78
+ # Returns true if key exists, otherwise nil.
79
+ def touch(key, ttl = nil)
80
+ resp = perform(:touch, key, ttl_or_default(ttl))
81
+ resp.nil? ? nil : true
82
+ end
83
+
84
+ ##
85
+ # Get the value and CAS ID associated with the key. If a block is provided,
86
+ # value and CAS will be passed to the block.
87
+ def get_cas(key)
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
+ return [value, cas] unless block_given?
92
+
93
+ yield value, cas
79
94
  end
80
95
 
81
96
  ##
@@ -97,7 +112,21 @@ module Dalli
97
112
  end
98
113
  end
99
114
 
100
- CACHE_NILS = { cache_nils: true }.freeze
115
+ ##
116
+ # Fetch multiple keys efficiently, including available metadata such as CAS.
117
+ # If a block is given, yields key/data pairs one a time. Data is an array:
118
+ # [value, cas_id]
119
+ # If no block is given, returns a hash of
120
+ # { 'key' => [value, cas_id] }
121
+ def get_multi_cas(*keys)
122
+ if block_given?
123
+ pipelined_getter.process(keys) { |*args| yield(*args) }
124
+ else
125
+ {}.tap do |hash|
126
+ pipelined_getter.process(keys) { |k, data| hash[k] = data }
127
+ end
128
+ end
129
+ end
101
130
 
102
131
  # Fetch the value associated with the key.
103
132
  # If a value is found, then it is returned.
@@ -110,19 +139,11 @@ module Dalli
110
139
  def fetch(key, ttl = nil, req_options = nil)
111
140
  req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils
112
141
  val = get(key, req_options)
113
- if not_found?(val) && block_given?
114
- val = yield
115
- add(key, val, ttl_or_default(ttl), req_options)
116
- end
117
- val
118
- end
142
+ return val unless block_given? && not_found?(val)
119
143
 
120
- def not_found?(val)
121
- cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
122
- end
123
-
124
- def cache_nils
125
- @options[:cache_nils]
144
+ new_val = yield
145
+ add(key, new_val, ttl_or_default(ttl), req_options)
146
+ new_val
126
147
  end
127
148
 
128
149
  ##
@@ -136,8 +157,8 @@ module Dalli
136
157
  # - nil if the key did not exist.
137
158
  # - false if the value was changed by someone else.
138
159
  # - true if the value was successfully updated.
139
- def cas(key, ttl = nil, options = nil, &block)
140
- cas_core(key, false, ttl, options, &block)
160
+ def cas(key, ttl = nil, req_options = nil, &block)
161
+ cas_core(key, false, ttl, req_options, &block)
141
162
  end
142
163
 
143
164
  ##
@@ -147,30 +168,78 @@ module Dalli
147
168
  # Returns:
148
169
  # - false if the value was changed by someone else.
149
170
  # - true if the value was successfully updated.
150
- def cas!(key, ttl = nil, options = nil, &block)
151
- cas_core(key, true, ttl, options, &block)
171
+ def cas!(key, ttl = nil, req_options = nil, &block)
172
+ cas_core(key, true, ttl, req_options, &block)
173
+ end
174
+
175
+ ##
176
+ # Turn on quiet aka noreply support for a number of
177
+ # memcached operations.
178
+ #
179
+ # All relevant operations within this block will be effectively
180
+ # pipelined as Dalli will use 'quiet' versions. The invoked methods
181
+ # will all return nil, rather than their usual response. Method
182
+ # latency will be substantially lower, as the caller will not be
183
+ # blocking on responses.
184
+ #
185
+ # Currently supports storage (set, add, replace, append, prepend),
186
+ # arithmetic (incr, decr), flush and delete operations. Use of
187
+ # unsupported operations inside a block will raise an error.
188
+ #
189
+ # Any error replies will be discarded at the end of the block, and
190
+ # Dalli client methods invoked inside the block will not
191
+ # have return values
192
+ def quiet
193
+ old = Thread.current[::Dalli::QUIET]
194
+ Thread.current[::Dalli::QUIET] = true
195
+ yield
196
+ ensure
197
+ @ring&.pipeline_consume_and_ignore_responses
198
+ Thread.current[::Dalli::QUIET] = old
152
199
  end
200
+ alias multi quiet
153
201
 
154
- def set(key, value, ttl = nil, options = nil)
155
- perform(:set, key, value, ttl_or_default(ttl), 0, options)
202
+ def set(key, value, ttl = nil, req_options = nil)
203
+ set_cas(key, value, 0, ttl, req_options)
204
+ end
205
+
206
+ ##
207
+ # Set the key-value pair, verifying existing CAS.
208
+ # Returns the resulting CAS value if succeeded, and falsy otherwise.
209
+ def set_cas(key, value, cas, ttl = nil, req_options = nil)
210
+ perform(:set, key, value, ttl_or_default(ttl), cas, req_options)
156
211
  end
157
212
 
158
213
  ##
159
214
  # Conditionally add a key/value pair, if the key does not already exist
160
215
  # on the server. Returns truthy if the operation succeeded.
161
- def add(key, value, ttl = nil, options = nil)
162
- perform(:add, key, value, ttl_or_default(ttl), options)
216
+ def add(key, value, ttl = nil, req_options = nil)
217
+ perform(:add, key, value, ttl_or_default(ttl), req_options)
163
218
  end
164
219
 
165
220
  ##
166
221
  # Conditionally add a key/value pair, only if the key already exists
167
222
  # on the server. Returns truthy if the operation succeeded.
168
- def replace(key, value, ttl = nil, options = nil)
169
- perform(:replace, key, value, ttl_or_default(ttl), 0, options)
223
+ def replace(key, value, ttl = nil, req_options = nil)
224
+ replace_cas(key, value, 0, ttl, req_options)
225
+ end
226
+
227
+ ##
228
+ # Conditionally add a key/value pair, verifying existing CAS, only if the
229
+ # key already exists on the server. Returns the new CAS value if the
230
+ # operation succeeded, or falsy otherwise.
231
+ def replace_cas(key, value, cas, ttl = nil, req_options = nil)
232
+ perform(:replace, key, value, ttl_or_default(ttl), cas, req_options)
233
+ end
234
+
235
+ # Delete a key/value pair, verifying existing CAS.
236
+ # Returns true if succeeded, and falsy otherwise.
237
+ def delete_cas(key, cas = 0)
238
+ perform(:delete, key, cas)
170
239
  end
171
240
 
172
241
  def delete(key)
173
- perform(:delete, key, 0)
242
+ delete_cas(key, 0)
174
243
  end
175
244
 
176
245
  ##
@@ -187,13 +256,6 @@ module Dalli
187
256
  perform(:prepend, key, value.to_s)
188
257
  end
189
258
 
190
- def flush(delay = 0)
191
- time = -delay
192
- ring.servers.map { |s| s.request(:flush, time += delay) }
193
- end
194
-
195
- alias flush_all flush
196
-
197
259
  ##
198
260
  # Incr adds the given amount to the counter on the memcached server.
199
261
  # Amt must be a positive integer value.
@@ -205,8 +267,10 @@ module Dalli
205
267
  # Note that the ttl will only apply if the counter does not already
206
268
  # exist. To increase an existing counter and update its TTL, use
207
269
  # #cas.
270
+ #
271
+ # If the value already exists, it must have been set with raw: true
208
272
  def incr(key, amt = 1, ttl = nil, default = nil)
209
- raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
273
+ check_positive!(amt)
210
274
 
211
275
  perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
212
276
  end
@@ -225,35 +289,31 @@ module Dalli
225
289
  # Note that the ttl will only apply if the counter does not already
226
290
  # exist. To decrease an existing counter and update its TTL, use
227
291
  # #cas.
292
+ #
293
+ # If the value already exists, it must have been set with raw: true
228
294
  def decr(key, amt = 1, ttl = nil, default = nil)
229
- raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
295
+ check_positive!(amt)
230
296
 
231
297
  perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
232
298
  end
233
299
 
234
300
  ##
235
- # Touch updates expiration time for a given key.
236
- #
237
- # Returns true if key exists, otherwise nil.
238
- def touch(key, ttl = nil)
239
- resp = perform(:touch, key, ttl_or_default(ttl))
240
- resp.nil? ? nil : true
241
- end
242
-
301
+ # Flush the memcached server, at 'delay' seconds in the future.
302
+ # Delay defaults to zero seconds, which means an immediate flush.
243
303
  ##
244
- # Gat (get and touch) fetch an item and simultaneously update its expiration time.
245
- #
246
- # If a value is not found, then +nil+ is returned.
247
- def gat(key, ttl = nil)
248
- perform(:gat, key, ttl_or_default(ttl))
304
+ def flush(delay = 0)
305
+ ring.servers.map { |s| s.request(:flush, delay) }
249
306
  end
307
+ alias flush_all flush
308
+
309
+ ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
250
310
 
251
311
  ##
252
312
  # Collect the stats for each server.
253
313
  # You can optionally pass a type including :items, :slabs or :settings to get specific stats
254
314
  # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
255
315
  def stats(type = nil)
256
- type = nil unless [nil, :items, :slabs, :settings].include? type
316
+ type = nil unless ALLOWED_STAT_KEYS.include? type
257
317
  values = {}
258
318
  ring.servers.each do |server|
259
319
  values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
@@ -269,12 +329,6 @@ module Dalli
269
329
  end
270
330
  end
271
331
 
272
- ##
273
- ## Make sure memcache servers are alive, or raise an Dalli::RingError
274
- def alive!
275
- ring.server_for_key('')
276
- end
277
-
278
332
  ##
279
333
  ## Version of the memcache servers.
280
334
  def version
@@ -286,55 +340,9 @@ module Dalli
286
340
  end
287
341
 
288
342
  ##
289
- # Get the value and CAS ID associated with the key. If a block is provided,
290
- # value and CAS will be passed to the block.
291
- def get_cas(key)
292
- (value, cas) = perform(:cas, key)
293
- value = nil if !value || value == 'Not found'
294
- if block_given?
295
- yield value, cas
296
- else
297
- [value, cas]
298
- end
299
- end
300
-
301
- ##
302
- # Fetch multiple keys efficiently, including available metadata such as CAS.
303
- # If a block is given, yields key/data pairs one a time. Data is an array:
304
- # [value, cas_id]
305
- # If no block is given, returns a hash of
306
- # { 'key' => [value, cas_id] }
307
- def get_multi_cas(*keys)
308
- if block_given?
309
- pipelined_getter.process(keys) { |*args| yield(*args) }
310
- else
311
- {}.tap do |hash|
312
- pipelined_getter.process(keys) { |k, data| hash[k] = data }
313
- end
314
- end
315
- end
316
-
317
- ##
318
- # Set the key-value pair, verifying existing CAS.
319
- # Returns the resulting CAS value if succeeded, and falsy otherwise.
320
- def set_cas(key, value, cas, ttl = nil, options = nil)
321
- ttl ||= @options[:expires_in].to_i
322
- perform(:set, key, value, ttl, cas, options)
323
- end
324
-
325
- ##
326
- # Conditionally add a key/value pair, verifying existing CAS, only if the
327
- # key already exists on the server. Returns the new CAS value if the
328
- # operation succeeded, or falsy otherwise.
329
- def replace_cas(key, value, cas, ttl = nil, options = nil)
330
- ttl ||= @options[:expires_in].to_i
331
- perform(:replace, key, value, ttl, cas, options)
332
- end
333
-
334
- # Delete a key/value pair, verifying existing CAS.
335
- # Returns true if succeeded, and falsy otherwise.
336
- def delete_cas(key, cas = 0)
337
- perform(:delete, key, cas)
343
+ ## Make sure memcache servers are alive, or raise an Dalli::RingError
344
+ def alive!
345
+ ring.server_for_key('')
338
346
  end
339
347
 
340
348
  ##
@@ -346,6 +354,16 @@ module Dalli
346
354
  end
347
355
  alias reset close
348
356
 
357
+ CACHE_NILS = { cache_nils: true }.freeze
358
+
359
+ def not_found?(val)
360
+ cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
361
+ end
362
+
363
+ def cache_nils
364
+ @options[:cache_nils]
365
+ end
366
+
349
367
  # Stub method so a bare Dalli client can pretend to be a connection pool.
350
368
  def with
351
369
  yield self
@@ -353,15 +371,23 @@ module Dalli
353
371
 
354
372
  private
355
373
 
356
- def cas_core(key, always_set, ttl = nil, options = nil)
374
+ def check_positive!(amt)
375
+ raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
376
+ end
377
+
378
+ def cas_core(key, always_set, ttl = nil, req_options = nil)
357
379
  (value, cas) = perform(:cas, key)
358
380
  value = nil if !value || value == 'Not found'
359
381
  return if value.nil? && !always_set
360
382
 
361
383
  newvalue = yield(value)
362
- perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
384
+ perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
363
385
  end
364
386
 
387
+ ##
388
+ # Uses the argument TTL or the client-wide default. Ensures
389
+ # that the value is an integer
390
+ ##
365
391
  def ttl_or_default(ttl)
366
392
  (ttl || @options[:expires_in]).to_i
367
393
  rescue NoMethodError
@@ -382,7 +408,16 @@ module Dalli
382
408
  @protocol_implementation ||= @options.fetch(:protocol_implementation, Dalli::Protocol::Binary)
383
409
  end
384
410
 
385
- # Chokepoint method for instrumentation
411
+ ##
412
+ # Chokepoint method for memcached methods with a key argument.
413
+ # Validates the key, resolves the key to the appropriate server
414
+ # instance, and invokes the memcached method on the appropriate
415
+ # server.
416
+ #
417
+ # This method also forces retries on network errors - when
418
+ # a particular memcached instance becomes unreachable, or the
419
+ # operational times out.
420
+ ##
386
421
  def perform(*all_args)
387
422
  return yield if block_given?
388
423
 
data/lib/dalli/options.rb CHANGED
@@ -31,19 +31,19 @@ module Dalli
31
31
  end
32
32
  end
33
33
 
34
- def pipeline_response_start
34
+ def pipeline_response_setup
35
35
  @lock.synchronize do
36
36
  super
37
37
  end
38
38
  end
39
39
 
40
- def process_outstanding_pipeline_requests
40
+ def pipeline_next_responses
41
41
  @lock.synchronize do
42
42
  super
43
43
  end
44
44
  end
45
45
 
46
- def pipeline_response_abort
46
+ def pipeline_abort
47
47
  @lock.synchronize do
48
48
  super
49
49
  end
@@ -19,13 +19,7 @@ module Dalli
19
19
  @ring.lock do
20
20
  servers = setup_requests(keys)
21
21
  start_time = Time.now
22
- loop do
23
- # Remove any servers which are not connected
24
- servers.delete_if { |s| !s.connected? }
25
- break if servers.empty?
26
-
27
- servers = fetch_responses(servers, start_time, @ring.socket_timeout, &block)
28
- end
22
+ servers = fetch_responses(servers, start_time, @ring.socket_timeout, &block) until servers.empty?
29
23
  end
30
24
  rescue NetworkError => e
31
25
  Dalli.logger.debug { e.inspect }
@@ -44,6 +38,10 @@ module Dalli
44
38
  ##
45
39
  # Loop through the server-grouped sets of keys, writing
46
40
  # the corresponding getkq requests to the appropriate servers
41
+ #
42
+ # It's worth noting that we could potentially reduce bytes
43
+ # on the wire by switching from getkq to getq, and using
44
+ # the opaque value to match requests to responses.
47
45
  ##
48
46
  def make_getkq_requests(groups)
49
47
  groups.each do |server, keys_for_server|
@@ -80,7 +78,7 @@ module Dalli
80
78
  end
81
79
 
82
80
  def finish_query_for_server(server)
83
- server.pipeline_response_start
81
+ server.pipeline_response_setup
84
82
  rescue Dalli::NetworkError
85
83
  raise
86
84
  rescue Dalli::DalliError => e
@@ -91,10 +89,14 @@ module Dalli
91
89
 
92
90
  # Swallows Dalli::NetworkError
93
91
  def abort_without_timeout(servers)
94
- servers.each(&:pipeline_response_abort)
92
+ servers.each(&:pipeline_abort)
95
93
  end
96
94
 
97
95
  def fetch_responses(servers, start_time, timeout, &block)
96
+ # Remove any servers which are not connected
97
+ servers.delete_if { |s| !s.connected? }
98
+ return [] if servers.empty?
99
+
98
100
  time_left = remaining_time(start_time, timeout)
99
101
  readable_servers = servers_with_response(servers, time_left)
100
102
  if readable_servers.empty?
@@ -135,11 +137,11 @@ module Dalli
135
137
  # Processes responses from a server. Returns true if there are no
136
138
  # additional responses from this server.
137
139
  def process_server(server)
138
- server.process_outstanding_pipeline_requests.each_pair do |key, value_list|
140
+ server.pipeline_next_responses.each_pair do |key, value_list|
139
141
  yield @key_manager.key_without_namespace(key), value_list
140
142
  end
141
143
 
142
- server.pipeline_response_completed?
144
+ server.pipeline_complete?
143
145
  end
144
146
 
145
147
  def servers_with_response(servers, timeout)
@@ -31,11 +31,14 @@ module Dalli
31
31
  deleteq: 0x14,
32
32
  incrq: 0x15,
33
33
  decrq: 0x16,
34
+ flushq: 0x18,
35
+ appendq: 0x19,
36
+ prependq: 0x1A,
37
+ touch: 0x1C,
38
+ gat: 0x1D,
34
39
  auth_negotiation: 0x20,
35
40
  auth_request: 0x21,
36
- auth_continue: 0x22,
37
- touch: 0x1C,
38
- gat: 0x1D
41
+ auth_continue: 0x22
39
42
  }.freeze
40
43
 
41
44
  REQ_HEADER_FORMAT = 'CCnCCnNNQ'
@@ -56,6 +59,8 @@ module Dalli
56
59
 
57
60
  append: KEY_AND_VALUE,
58
61
  prepend: KEY_AND_VALUE,
62
+ appendq: KEY_AND_VALUE,
63
+ prependq: KEY_AND_VALUE,
59
64
  auth_request: KEY_AND_VALUE,
60
65
  auth_continue: KEY_AND_VALUE,
61
66
 
@@ -68,8 +73,11 @@ module Dalli
68
73
 
69
74
  incr: INCR_DECR,
70
75
  decr: INCR_DECR,
76
+ incrq: INCR_DECR,
77
+ decrq: INCR_DECR,
71
78
 
72
79
  flush: TTL_ONLY,
80
+ flushq: TTL_ONLY,
73
81
 
74
82
  noop: NO_BODY,
75
83
  auth_negotiation: NO_BODY,
@@ -58,7 +58,7 @@ module Dalli
58
58
  read(ResponseHeader::SIZE) || raise(Dalli::NetworkError, 'No response')
59
59
  end
60
60
 
61
- def raise_on_not_ok_status!(resp_header)
61
+ def raise_on_not_ok!(resp_header)
62
62
  return if resp_header.ok?
63
63
 
64
64
  raise Dalli::DalliError, "Response error #{resp_header.status}: #{RESPONSE_CODES[resp_header.status]}"
@@ -67,24 +67,45 @@ module Dalli
67
67
  def generic_response(unpack: false, cache_nils: false)
68
68
  resp_header, body = read_response
69
69
 
70
- return cache_nils ? ::Dalli::NOT_FOUND : nil if resp_header.not_found?
71
70
  return false if resp_header.not_stored? # Not stored, normal status for add operation
71
+ return cache_nils ? ::Dalli::NOT_FOUND : nil if resp_header.not_found?
72
72
 
73
- raise_on_not_ok_status!(resp_header)
73
+ raise_on_not_ok!(resp_header)
74
74
  return true unless body
75
75
 
76
76
  unpack_response_body(resp_header.extra_len, resp_header.key_len, body, unpack).last
77
77
  end
78
78
 
79
- def data_cas_response
79
+ ##
80
+ # Response for a storage operation. Returns the cas on success. False
81
+ # if the value wasn't stored. And raises an error on all other error
82
+ # codes from memcached.
83
+ ##
84
+ def storage_response
85
+ resp_header, = read_response
86
+ return false if resp_header.not_stored? # Not stored, normal status for add operation
87
+
88
+ raise_on_not_ok!(resp_header)
89
+ resp_header.cas
90
+ end
91
+
92
+ def no_body_response
93
+ resp_header, = read_response
94
+ return false if resp_header.not_stored? # Not stored, possible status for append/prepend
95
+
96
+ raise_on_not_ok!(resp_header)
97
+ true
98
+ end
99
+
100
+ def data_cas_response(unpack: true)
80
101
  resp_header, body = read_response
81
102
  return [nil, resp_header.cas] if resp_header.not_found?
82
103
  return [nil, false] if resp_header.not_stored?
83
104
 
84
- raise_on_not_ok_status!(resp_header)
105
+ raise_on_not_ok!(resp_header)
85
106
  return [nil, resp_header.cas] unless body
86
107
 
87
- [unpack_response_body(resp_header.extra_len, resp_header.key_len, body, true).last, resp_header.cas]
108
+ [unpack_response_body(resp_header.extra_len, resp_header.key_len, body, unpack).last, resp_header.cas]
88
109
  end
89
110
 
90
111
  def cas_response
@@ -146,31 +167,32 @@ module Dalli
146
167
  # complete response is found in the buffer, this will
147
168
  # be the response size. Otherwise it is zero.
148
169
  #
149
- # The remaining four values in the array are the status, key,
150
- # value, and cas returned from the response.
170
+ # The remaining three values in the array are the ResponseHeader,
171
+ # key, and value.
151
172
  ##
152
173
  def getk_response_from_buffer(buf)
153
174
  # There's no header in the buffer, so don't advance
154
- return [0, 0, nil, nil, nil] unless contains_header?(buf)
175
+ return [0, nil, nil, nil] unless contains_header?(buf)
155
176
 
156
177
  resp_header = response_header_from_buffer(buf)
157
178
  body_len = resp_header.body_len
158
179
 
159
- # The response has no body - so we need to advance the
160
- # buffer. This is either the response to the terminating
180
+ # We have a complete response that has no body.
181
+ # This is either the response to the terminating
161
182
  # noop or, if the status is not zero, an intermediate
162
183
  # error response that needs to be discarded.
163
- return [ResponseHeader::SIZE, resp_header.status, nil, nil, resp_header.cas] if body_len.zero?
184
+ return [ResponseHeader::SIZE, resp_header, nil, nil] if body_len.zero?
164
185
 
165
- # The header is in the buffer, but the body is not
166
186
  resp_size = ResponseHeader::SIZE + body_len
167
- return [0, resp_header.status, nil, nil, nil] unless buf.bytesize >= resp_size
187
+ # The header is in the buffer, but the body is not. As we don't have
188
+ # a complete response, don't advance the buffer
189
+ return [0, nil, nil, nil] unless buf.bytesize >= resp_size
168
190
 
169
191
  # The full response is in our buffer, so parse it and return
170
192
  # the values
171
193
  body = buf.slice(ResponseHeader::SIZE, body_len)
172
194
  key, value = unpack_response_body(resp_header.extra_len, resp_header.key_len, body, true)
173
- [resp_size, resp_header.status, key, value, resp_header.cas]
195
+ [resp_size, resp_header, key, value]
174
196
  end
175
197
  end
176
198
  end