memcached-seanl 0.19.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,649 @@
1
+
2
+ =begin rdoc
3
+ The Memcached client class.
4
+ =end
5
+ class Memcached
6
+ FLAGS = 0x0
7
+
8
+ DEFAULTS = {
9
+ :hash => :fnv1_32,
10
+ :no_block => false,
11
+ :distribution => :consistent_ketama,
12
+ :ketama_weighted => true,
13
+ :buffer_requests => false,
14
+ :cache_lookups => true,
15
+ :support_cas => false,
16
+ :tcp_nodelay => false,
17
+ :show_backtraces => false,
18
+ :retry_timeout => 30,
19
+ :timeout => 0.25,
20
+ :rcv_timeout => nil,
21
+ :poll_timeout => nil,
22
+ :connect_timeout => 4,
23
+ :prefix_key => '',
24
+ :prefix_delimiter => '',
25
+ :hash_with_prefix_key => true,
26
+ :default_ttl => 604800,
27
+ :default_weight => 8,
28
+ :sort_hosts => false,
29
+ :auto_eject_hosts => true,
30
+ :server_failure_limit => 2,
31
+ :verify_key => true,
32
+ :use_udp => false,
33
+ :binary_protocol => false,
34
+ :credentials => nil,
35
+ :exception_retry_limit => 5,
36
+ :exceptions_to_retry => [
37
+ Memcached::ServerIsMarkedDead,
38
+ Memcached::ATimeoutOccurred,
39
+ Memcached::ConnectionBindFailure,
40
+ Memcached::ConnectionFailure,
41
+ Memcached::ConnectionSocketCreateFailure,
42
+ Memcached::Failure,
43
+ Memcached::MemoryAllocationFailure,
44
+ Memcached::ReadFailure,
45
+ Memcached::ServerError,
46
+ Memcached::SystemError,
47
+ Memcached::UnknownReadFailure,
48
+ Memcached::WriteFailure]
49
+ }
50
+
51
+ #:stopdoc:
52
+ IGNORED = 0
53
+ #:startdoc:
54
+
55
+ attr_reader :options # Return the options Hash used to configure this instance.
56
+
57
+ ###### Configuration
58
+
59
+ =begin rdoc
60
+ Create a new Memcached instance. Accepts string or array of server strings, as well an an optional configuration hash.
61
+
62
+ Memcached.new('localhost', ...) # A single server
63
+ Memcached.new(['web001:11212', 'web002:11212'], ...) # Two servers with custom ports
64
+ Memcached.new(['web001:11211:2', 'web002:11211:8'], ...) # Two servers with default ports and explicit weights
65
+
66
+ Weights only affect Ketama hashing. If you use Ketama hashing and don't specify a weight, the client will poll each server's stats and use its size as the weight.
67
+
68
+ Valid option parameters are:
69
+
70
+ <tt>:prefix_key</tt>:: A string to prepend to every key, for namespacing. Max length is 127. Defaults to the empty string.
71
+ <tt>:prefix_delimiter</tt>:: A character to postpend to the prefix key. Defaults to the empty string.
72
+ <tt>:hash</tt>:: The name of a hash function to use. Possible values are: <tt>:crc</tt>, <tt>:default</tt>, <tt>:fnv1_32</tt>, <tt>:fnv1_64</tt>, <tt>:fnv1a_32</tt>, <tt>:fnv1a_64</tt>, <tt>:hsieh</tt>, <tt>:md5</tt>, and <tt>:murmur</tt>. <tt>:fnv1_32</tt> is fast and well known, and is the default. Use <tt>:md5</tt> for compatibility with other ketama clients.
73
+ <tt>:distribution</tt>:: Either <tt>:modula</tt>, <tt>:consistent_ketama</tt>, <tt>:consistent_wheel</tt>, or <tt>:ketama</tt>. Defaults to <tt>:ketama</tt>.
74
+ <tt>:server_failure_limit</tt>:: How many consecutive failures to allow before marking a host as dead. Has no effect unless <tt>:retry_timeout</tt> is also set.
75
+ <tt>:retry_timeout</tt>:: How long to wait until retrying a dead server. Has no effect unless <tt>:server_failure_limit</tt> is non-zero. Defaults to <tt>30</tt>.
76
+ <tt>:auto_eject_hosts</tt>:: Whether to temporarily eject dead hosts from the pool. Defaults to <tt>true</tt>. Note that in the event of an ejection, <tt>:auto_eject_hosts</tt> will remap the entire pool unless <tt>:distribution</tt> is set to <tt>:consistent</tt>.
77
+ <tt>:exception_retry_limit</tt>:: How many times to retry before raising exceptions in <tt>:exceptions_to_retry</tt>. Defaults to <tt>5</tt>.
78
+ <tt>:exceptions_to_retry</tt>:: Which exceptions to retry. Defaults to <b>ServerIsMarkedDead</b>, <b>ATimeoutOccurred</b>, <b>ConnectionBindFailure</b>, <b>ConnectionFailure</b>, <b>ConnectionSocketCreateFailure</b>, <b>Failure</b>, <b>MemoryAllocationFailure</b>, <b>ReadFailure</b>, <b>ServerError</b>, <b>SystemError</b>, <b>UnknownReadFailure</b>, and <b>WriteFailure</b>.
79
+ <tt>:cache_lookups</tt>:: Whether to cache hostname lookups for the life of the instance. Defaults to <tt>true</tt>.
80
+ <tt>:support_cas</tt>:: Flag CAS support in the client. Accepts <tt>true</tt> or <tt>false</tt>. Defaults to <tt>false</tt> because it imposes a slight performance penalty. Note that your server must also support CAS or you will trigger <b>ProtocolError</b> exceptions.
81
+ <tt>:tcp_nodelay</tt>:: Turns on the no-delay feature for connecting sockets. Accepts <tt>true</tt> or <tt>false</tt>. Performance may or may not change, depending on your system.
82
+ <tt>:no_block</tt>:: Whether to use pipelining for writes. Accepts <tt>true</tt> or <tt>false</tt>.
83
+ <tt>:buffer_requests</tt>:: Whether to use an internal write buffer. Accepts <tt>true</tt> or <tt>false</tt>. Calling <tt>get</tt> or closing the connection will force the buffer to flush. Note that <tt>:buffer_requests</tt> might not work well without <tt>:no_block</tt> also enabled.
84
+ <tt>:show_backtraces</tt>:: Whether <b>NotFound</b> and <b>NotStored</b> exceptions should include backtraces. Generating backtraces is slow, so this is off by default. Turn it on to ease debugging.
85
+ <tt>:connect_timeout</tt>:: How long to wait for a connection to a server. Defaults to 2 seconds. Set to <tt>0</tt> if you want to wait forever.
86
+ <tt>:timeout</tt>:: How long to wait for a response from the server. Defaults to 0.25 seconds. Set to <tt>0</tt> if you want to wait forever.
87
+ <tt>:default_ttl</tt>:: The <tt>ttl</tt> to use on set if no <tt>ttl</tt> is specified, in seconds. Defaults to one week. Set to <tt>0</tt> if you want things to never expire.
88
+ <tt>:default_weight</tt>:: The weight to use if <tt>:ketama_weighted</tt> is <tt>true</tt>, but no weight is specified for a server.
89
+ <tt>:hash_with_prefix_key</tt>:: Whether to include the prefix when calculating which server a key falls on. Defaults to <tt>true</tt>.
90
+ <tt>:use_udp</tt>:: Use the UDP protocol to reduce connection overhead. Defaults to false.
91
+ <tt>:binary_protocol</tt>:: Use the binary protocol to reduce query processing overhead. Defaults to false.
92
+ <tt>:sort_hosts</tt>:: Whether to force the server list to stay sorted. This defeats consistent hashing and is rarely useful.
93
+ <tt>:verify_key</tt>:: Validate keys before accepting them. Never disable this.
94
+
95
+ Please note that when <tt>:no_block => true</tt>, update methods do not raise on errors. For example, if you try to <tt>set</tt> an invalid key, it will appear to succeed. The actual setting of the key occurs after libmemcached has returned control to your program, so there is no way to backtrack and raise the exception.
96
+
97
+ =end
98
+
99
+ def initialize(servers = nil, opts = {})
100
+ @struct = Lib.memcached_create(nil)
101
+
102
+ # Merge option defaults and discard meaningless keys
103
+ @options = DEFAULTS.merge(opts)
104
+ @options.delete_if { |k,v| not DEFAULTS.keys.include? k }
105
+ @default_ttl = options[:default_ttl]
106
+
107
+ if servers == nil || servers == []
108
+ if ENV.key?("MEMCACHE_SERVERS")
109
+ servers = ENV["MEMCACHE_SERVERS"].split(",").map do | s | s.strip end
110
+ else
111
+ servers = "127.0.0.1:11211"
112
+ end
113
+ end
114
+
115
+ if options[:credentials] == nil && ENV.key?("MEMCACHE_USERNAME") && ENV.key?("MEMCACHE_PASSWORD")
116
+ options[:credentials] = [ENV["MEMCACHE_USERNAME"], ENV["MEMCACHE_PASSWORD"]]
117
+ end
118
+
119
+ options[:binary_protocol] = true if options[:credentials] != nil
120
+
121
+ # Force :buffer_requests to use :no_block
122
+ # XXX Deleting the :no_block key should also work, but libmemcached doesn't seem to set it
123
+ # consistently
124
+ options[:no_block] = true if options[:buffer_requests]
125
+
126
+ # Disallow weights without ketama
127
+ options.delete(:ketama_weighted) if options[:distribution] != :consistent_ketama
128
+
129
+ # Disallow :sort_hosts with consistent hashing
130
+ if options[:sort_hosts] and options[:distribution] == :consistent
131
+ raise ArgumentError, ":sort_hosts defeats :consistent hashing"
132
+ end
133
+
134
+ # Read timeouts
135
+ options[:rcv_timeout] ||= options[:timeout] || 0
136
+ options[:poll_timeout] ||= options[:timeout] || 0
137
+
138
+ # Set the prefix key. Support the legacy name.
139
+ set_prefix_key(options.delete(:prefix_key) || options.delete(:namespace))
140
+
141
+ # Set the behaviors and credentials on the struct
142
+ set_behaviors
143
+ set_credentials
144
+
145
+ # Freeze the hash
146
+ options.freeze
147
+
148
+ # Set the servers on the struct
149
+ set_servers(servers)
150
+
151
+ # Not found exceptions
152
+ unless options[:show_backtraces]
153
+ @not_found = NotFound.new
154
+ @not_found.no_backtrace = true
155
+ @not_stored = NotStored.new
156
+ @not_stored.no_backtrace = true
157
+ end
158
+ end
159
+
160
+ # Set the server list.
161
+ # FIXME Does not necessarily free any existing server structs.
162
+ def set_servers(servers)
163
+ Array(servers).each_with_index do |server, index|
164
+ # Socket
165
+ check_return_code(
166
+ if server.is_a?(String) and File.socket?(server)
167
+ args = [@struct, server, options[:default_weight].to_i]
168
+ Lib.memcached_server_add_unix_socket_with_weight(*args)
169
+ # Network
170
+ elsif server.is_a?(String) and server =~ /^[\w\d\.-]+(:\d{1,5}){0,2}$/
171
+ host, port, weight = server.split(":")
172
+ args = [@struct, host, port.to_i, (weight || options[:default_weight]).to_i]
173
+ if options[:use_udp]
174
+ Lib.memcached_server_add_udp_with_weight(*args)
175
+ else
176
+ Lib.memcached_server_add_with_weight(*args)
177
+ end
178
+ else
179
+ raise ArgumentError, "Servers must be either in the format 'host:port[:weight]' (e.g., 'localhost:11211' or 'localhost:11211:10') for a network server, or a valid path to a Unix domain socket (e.g., /var/run/memcached)."
180
+ end
181
+ )
182
+ end
183
+ # For inspect
184
+ @servers = send(:servers)
185
+ end
186
+
187
+ # Return the array of server strings used to configure this instance.
188
+ def servers
189
+ server_structs.map do |server|
190
+ inspect_server(server)
191
+ end
192
+ end
193
+
194
+ # Set the prefix key.
195
+ def set_prefix_key(key)
196
+ check_return_code(
197
+ if key
198
+ key += options[:prefix_delimiter]
199
+ raise ArgumentError, "Max prefix key + prefix delimiter size is #{Lib::MEMCACHED_PREFIX_KEY_MAX_SIZE - 1}" unless
200
+ key.size < Lib::MEMCACHED_PREFIX_KEY_MAX_SIZE
201
+ Lib.memcached_callback_set(@struct, Lib::MEMCACHED_CALLBACK_PREFIX_KEY, key)
202
+ else
203
+ Lib.memcached_callback_set(@struct, Lib::MEMCACHED_CALLBACK_PREFIX_KEY, "")
204
+ end
205
+ )
206
+ end
207
+ alias :set_namespace :set_prefix_key
208
+
209
+ # Return the current prefix key.
210
+ def prefix_key
211
+ @struct.prefix_key[0..-1 - options[:prefix_delimiter].size] if @struct.prefix_key.size > 0
212
+ end
213
+ alias :namespace :prefix_key
214
+
215
+ # Safely copy this instance. Returns a Memcached instance.
216
+ #
217
+ # <tt>clone</tt> is useful for threading, since each thread must have its own unshared Memcached
218
+ # object.
219
+ #
220
+ def clone
221
+ # FIXME Memory leak
222
+ # memcached = super
223
+ # struct = Lib.memcached_clone(nil, @struct)
224
+ # memcached.instance_variable_set('@struct', struct)
225
+ # memcached
226
+ self.class.new(servers, options.merge(:prefix_key => prefix_key))
227
+ end
228
+
229
+ # Reset the state of the libmemcached struct. This is useful for changing the server list at runtime.
230
+ def reset(current_servers = nil, with_prefix_key = true)
231
+ # Store state and teardown
232
+ current_servers ||= servers
233
+ prev_prefix_key = prefix_key
234
+
235
+ # Create
236
+ # FIXME Duplicates logic with initialize()
237
+ @struct = Lib.memcached_create(nil)
238
+ set_prefix_key(prev_prefix_key) if with_prefix_key
239
+ set_behaviors
240
+ set_credentials
241
+ set_servers(current_servers)
242
+ end
243
+
244
+ # Disconnect from all currently connected servers
245
+ def quit
246
+ Lib.memcached_quit(@struct)
247
+ self
248
+ end
249
+
250
+ # Should retry the exception
251
+ def should_retry(e)
252
+ options[:exceptions_to_retry].each {|ex_class| return true if e.instance_of?(ex_class)}
253
+ false
254
+ end
255
+
256
+ #:stopdoc:
257
+ alias :dup :clone #:nodoc:
258
+ #:startdoc:
259
+
260
+ # change the prefix_key after we're in motion
261
+ def prefix_key=(key)
262
+ unless key.size < Lib::MEMCACHED_PREFIX_KEY_MAX_SIZE
263
+ raise ArgumentError, "Max prefix_key size is #{Lib::MEMCACHED_PREFIX_KEY_MAX_SIZE - 1}"
264
+ end
265
+ check_return_code(
266
+ Lib.memcached_callback_set(@struct, Lib::MEMCACHED_CALLBACK_PREFIX_KEY, "#{key}#{options[:prefix_delimiter]}")
267
+ )
268
+ end
269
+ alias namespace= prefix_key=
270
+
271
+ # report the prefix_key
272
+ def prefix_key
273
+ @struct.prefix_key[0..-1 - options[:prefix_delimiter].size]
274
+ end
275
+ alias namespace prefix_key
276
+
277
+
278
+ ### Configuration helpers
279
+
280
+ private
281
+
282
+ # Return an array of raw <tt>memcached_host_st</tt> structs for this instance.
283
+ def server_structs
284
+ array = []
285
+ if @struct.hosts
286
+ @struct.hosts.count.times do |i|
287
+ array << Lib.memcached_select_server_at(@struct, i)
288
+ end
289
+ end
290
+ array
291
+ end
292
+
293
+ ###### Operations
294
+
295
+ public
296
+
297
+ ### Setters
298
+
299
+ # Set a key/value pair. Accepts a String <tt>key</tt> and an arbitrary Ruby object. Overwrites any existing value on the server.
300
+ #
301
+ # Accepts an optional <tt>ttl</tt> value to specify the maximum lifetime of the key on the server. <tt>ttl</tt> can be either an integer number of seconds, or a Time elapsed time object. <tt>0</tt> means no ttl. Note that there is no guarantee that the key will persist as long as the <tt>ttl</tt>, but it will not persist longer.
302
+ #
303
+ # Also accepts a <tt>marshal</tt> value, which defaults to <tt>true</tt>. Set <tt>marshal</tt> to <tt>false</tt> if you want the <tt>value</tt> to be set directly.
304
+ #
305
+ def set(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
306
+ value = marshal ? Marshal.dump(value) : value.to_s
307
+ begin
308
+ check_return_code(
309
+ Lib.memcached_set(@struct, key, value, ttl, flags),
310
+ key
311
+ )
312
+ rescue => e
313
+ tries ||= 0
314
+ retry if e.instance_of?(ClientError) && !tries
315
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
316
+ tries += 1
317
+ retry
318
+ end
319
+ end
320
+
321
+ # Add a key/value pair. Raises <b>Memcached::NotStored</b> if the key already exists on the server. The parameters are the same as <tt>set</tt>.
322
+ def add(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
323
+ value = marshal ? Marshal.dump(value) : value.to_s
324
+ begin
325
+ check_return_code(
326
+ Lib.memcached_add(@struct, key, value, ttl, flags),
327
+ key
328
+ )
329
+ rescue => e
330
+ tries ||= 0
331
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
332
+ tries += 1
333
+ retry
334
+ end
335
+ end
336
+
337
+ # Increment a key's value. Accepts a String <tt>key</tt>. Raises <b>Memcached::NotFound</b> if the key does not exist.
338
+ #
339
+ # Also accepts an optional <tt>offset</tt> paramater, which defaults to 1. <tt>offset</tt> must be an integer.
340
+ #
341
+ # Note that the key must be initialized to an unmarshalled integer first, via <tt>set</tt>, <tt>add</tt>, or <tt>replace</tt> with <tt>marshal</tt> set to <tt>false</tt>.
342
+ def increment(key, offset=1)
343
+ ret, value = Lib.memcached_increment(@struct, key, offset)
344
+ check_return_code(ret, key)
345
+ value
346
+ rescue => e
347
+ tries ||= 0
348
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
349
+ tries += 1
350
+ retry
351
+ end
352
+
353
+ # Decrement a key's value. The parameters and exception behavior are the same as <tt>increment</tt>.
354
+ def decrement(key, offset=1)
355
+ ret, value = Lib.memcached_decrement(@struct, key, offset)
356
+ check_return_code(ret, key)
357
+ value
358
+ rescue => e
359
+ tries ||= 0
360
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
361
+ tries += 1
362
+ retry
363
+ end
364
+
365
+ #:stopdoc:
366
+ alias :incr :increment
367
+ alias :decr :decrement
368
+ #:startdoc:
369
+
370
+ # Replace a key/value pair. Raises <b>Memcached::NotFound</b> if the key does not exist on the server. The parameters are the same as <tt>set</tt>.
371
+ def replace(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
372
+ value = marshal ? Marshal.dump(value) : value.to_s
373
+ begin
374
+ check_return_code(
375
+ Lib.memcached_replace(@struct, key, value, ttl, flags),
376
+ key
377
+ )
378
+ rescue => e
379
+ tries ||= 0
380
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
381
+ tries += 1
382
+ retry
383
+ end
384
+ end
385
+
386
+ # Appends a string to a key's value. Accepts a String <tt>key</tt> and a String <tt>value</tt>. Raises <b>Memcached::NotFound</b> if the key does not exist on the server.
387
+ #
388
+ # Note that the key must be initialized to an unmarshalled string first, via <tt>set</tt>, <tt>add</tt>, or <tt>replace</tt> with <tt>marshal</tt> set to <tt>false</tt>.
389
+ def append(key, value)
390
+ # Requires memcached 1.2.4
391
+ check_return_code(
392
+ Lib.memcached_append(@struct, key, value.to_s, IGNORED, IGNORED),
393
+ key
394
+ )
395
+ rescue => e
396
+ tries ||= 0
397
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
398
+ tries += 1
399
+ retry
400
+ end
401
+
402
+ # Prepends a string to a key's value. The parameters and exception behavior are the same as <tt>append</tt>.
403
+ def prepend(key, value)
404
+ # Requires memcached 1.2.4
405
+ check_return_code(
406
+ Lib.memcached_prepend(@struct, key, value.to_s, IGNORED, IGNORED),
407
+ key
408
+ )
409
+ rescue => e
410
+ tries ||= 0
411
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
412
+ tries += 1
413
+ retry
414
+ end
415
+
416
+ # Reads a key's value from the server and yields it to a block. Replaces the key's value with the result of the block as long as the key hasn't been updated in the meantime, otherwise raises <b>Memcached::NotStored</b>. Accepts a String <tt>key</tt> and a block.
417
+ #
418
+ # Also accepts an optional <tt>ttl</tt> value.
419
+ #
420
+ # CAS stands for "compare and swap", and avoids the need for manual key mutexing. CAS support must be enabled in Memcached.new or a <b>Memcached::ClientError</b> will be raised. Note that CAS may be buggy in memcached itself.
421
+ # :retry_on_exceptions does not apply to this method
422
+ def cas(key, ttl=@default_ttl, marshal=true, flags=FLAGS)
423
+ raise ClientError, "CAS not enabled for this Memcached instance" unless options[:support_cas]
424
+
425
+ begin
426
+ value, flags, ret = Lib.memcached_get_rvalue(@struct, key)
427
+ check_return_code(ret, key)
428
+ rescue => e
429
+ tries_for_get ||= 0
430
+ raise unless tries_for_get < options[:exception_retry_limit] && should_retry(e)
431
+ tries_for_get += 1
432
+ retry
433
+ end
434
+
435
+ cas = @struct.result.cas
436
+
437
+ value = Marshal.load(value) if marshal
438
+ value = yield value
439
+ value = Marshal.dump(value) if marshal
440
+
441
+ begin
442
+ check_return_code(
443
+ Lib.memcached_cas(@struct, key, value, ttl, flags, cas),
444
+ key
445
+ )
446
+ rescue => e
447
+ tries_for_cas ||= 0
448
+ raise unless tries_for_cas < options[:exception_retry_limit] && should_retry(e)
449
+ tries_for_cas += 1
450
+ retry
451
+ end
452
+ end
453
+
454
+ alias :compare_and_swap :cas
455
+
456
+ ### Deleters
457
+
458
+ # Deletes a key/value pair from the server. Accepts a String <tt>key</tt>. Raises <b>Memcached::NotFound</b> if the key does not exist.
459
+ def delete(key)
460
+ check_return_code(
461
+ Lib.memcached_delete(@struct, key, IGNORED),
462
+ key
463
+ )
464
+ rescue => e
465
+ tries ||= 0
466
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
467
+ tries += 1
468
+ retry
469
+ end
470
+
471
+ # Flushes all key/value pairs from all the servers.
472
+ def flush
473
+ check_return_code(
474
+ Lib.memcached_flush(@struct, IGNORED)
475
+ )
476
+ rescue => e
477
+ tries ||= 0
478
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
479
+ tries += 1
480
+ retry
481
+ end
482
+
483
+ ### Getters
484
+
485
+ # Gets a key's value from the server. Accepts a String <tt>key</tt> or array of String <tt>keys</tt>.
486
+ #
487
+ # Also accepts a <tt>marshal</tt> value, which defaults to <tt>true</tt>. Set <tt>marshal</tt> to <tt>false</tt> if you want the <tt>value</tt> to be returned directly as a String. Otherwise it will be assumed to be a marshalled Ruby object and unmarshalled.
488
+ #
489
+ # If you pass a String key, and the key does not exist on the server, <b>Memcached::NotFound</b> will be raised. If you pass an array of keys, memcached's <tt>multiget</tt> mode will be used, and a hash of key/value pairs will be returned. The hash will contain only the keys that were found.
490
+ #
491
+ # The multiget behavior is subject to change in the future; however, for multiple lookups, it is much faster than normal mode.
492
+ #
493
+ # Note that when you rescue Memcached::NotFound exceptions, you should use a the block rescue syntax instead of the inline syntax. Block rescues are very fast, but inline rescues are very slow.
494
+ #
495
+ def get(keys, marshal=true)
496
+ if keys.is_a? Array
497
+ # Multi get
498
+ ret = Lib.memcached_mget(@struct, keys);
499
+ check_return_code(ret, keys)
500
+
501
+ hash = {}
502
+ keys.each do
503
+ value, key, flags, ret = Lib.memcached_fetch_rvalue(@struct)
504
+ break if ret == Lib::MEMCACHED_END
505
+ check_return_code(ret, key)
506
+ # Assign the value
507
+ hash[key] = (marshal ? Marshal.load(value) : value)
508
+ end
509
+ hash
510
+ else
511
+ # Single get
512
+ value, flags, ret = Lib.memcached_get_rvalue(@struct, keys)
513
+ check_return_code(ret, keys)
514
+ marshal ? Marshal.load(value) : value
515
+ end
516
+ rescue => e
517
+ tries ||= 0
518
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
519
+ tries += 1
520
+ retry
521
+ end
522
+
523
+ ### Information methods
524
+
525
+ # Return the server used by a particular key.
526
+ def server_by_key(key)
527
+ ret = Lib.memcached_server_by_key(@struct, key)
528
+ if ret.is_a?(Array)
529
+ check_return_code(ret.last)
530
+ inspect_server(ret.first)
531
+ else
532
+ check_return_code(ret)
533
+ end
534
+ end
535
+
536
+ # Return a Hash of statistics responses from the set of servers. Each value is an array with one entry for each server, in the same order the servers were defined.
537
+ def stats(subcommand = nil)
538
+ stats = Hash.new([])
539
+
540
+ stat_struct, ret = Lib.memcached_stat(@struct, subcommand)
541
+ check_return_code(ret)
542
+
543
+ keys, ret = Lib.memcached_stat_get_keys(@struct, stat_struct)
544
+ check_return_code(ret)
545
+
546
+ keys.each do |key|
547
+ server_structs.size.times do |index|
548
+
549
+ value, ret = Lib.memcached_stat_get_rvalue(
550
+ @struct,
551
+ Lib.memcached_select_stat_at(@struct, stat_struct, index),
552
+ key)
553
+ check_return_code(ret, key)
554
+
555
+ value = case value
556
+ when /^\d+\.\d+$/ then value.to_f
557
+ when /^\d+$/ then value.to_i
558
+ else value
559
+ end
560
+
561
+ stats[key.to_sym] += [value]
562
+ end
563
+ end
564
+
565
+ Lib.memcached_stat_free(@struct, stat_struct)
566
+ stats
567
+ rescue Memcached::SomeErrorsWereReported => _
568
+ e = _.class.new("Error getting stats")
569
+ e.set_backtrace(_.backtrace)
570
+ raise e
571
+ end
572
+
573
+ ### Operations helpers
574
+
575
+ private
576
+
577
+ # Checks the return code from Rlibmemcached against the exception list. Raises the corresponding exception if the return code is not Memcached::Success or Memcached::ActionQueued. Accepts an integer return code and an optional key, for exception messages.
578
+ def check_return_code(ret, key = nil) #:doc:
579
+ if ret == 0 # Memcached::Success
580
+ elsif ret == Lib::MEMCACHED_BUFFERED # Memcached::ActionQueued
581
+ elsif ret == Lib::MEMCACHED_NOTFOUND and @not_found
582
+ raise @not_found
583
+ elsif ret == Lib::MEMCACHED_NOTSTORED and @not_stored
584
+ raise @not_stored
585
+ else
586
+ message = "Key #{inspect_keys(key, (detect_failure if ret == Lib::MEMCACHED_SERVER_MARKED_DEAD)).inspect}"
587
+ if key.is_a?(String)
588
+ if ret == Lib::MEMCACHED_ERRNO
589
+ server = Lib.memcached_server_by_key(@struct, key)
590
+ errno = server.first.cached_errno
591
+ message = "Errno #{errno}: #{ERRNO_HASH[errno].inspect}. #{message}"
592
+ elsif ret == Lib::MEMCACHED_SERVER_ERROR
593
+ server = Lib.memcached_server_by_key(@struct, key)
594
+ message = "\"#{server.first.cached_server_error}\". #{message}."
595
+ end
596
+ end
597
+ raise EXCEPTIONS[ret], message
598
+ end
599
+ end
600
+
601
+ # Turn an array of keys into a hash of keys to servers.
602
+ def inspect_keys(keys, server = nil)
603
+ Hash[*Array(keys).map do |key|
604
+ [key, server || server_by_key(key)]
605
+ end.flatten]
606
+ end
607
+
608
+ # Find which server failed most recently.
609
+ # FIXME Is this still necessary with cached_errno?
610
+ def detect_failure
611
+ time = Time.now
612
+ server = server_structs.detect do |server|
613
+ server.next_retry > time
614
+ end
615
+ inspect_server(server) if server
616
+ end
617
+
618
+ # Set the behaviors on the struct from the current options.
619
+ def set_behaviors
620
+ BEHAVIORS.keys.each do |behavior|
621
+ set_behavior(behavior, options[behavior]) if options.key?(behavior)
622
+ end
623
+ # BUG Hash must be last due to the weird Libmemcached multi-behaviors
624
+ set_behavior(:hash, options[:hash])
625
+ end
626
+
627
+ # Set the SASL credentials from the current options. If credentials aren't provided, try to get them from the environment.
628
+ def set_credentials
629
+ if options[:credentials]
630
+ check_return_code(
631
+ Lib.memcached_set_sasl_auth_data(@struct, *options[:credentials])
632
+ )
633
+ end
634
+ end
635
+
636
+ def is_unix_socket?(server)
637
+ server.type == Lib::MEMCACHED_CONNECTION_UNIX_SOCKET
638
+ end
639
+
640
+ # Stringify an opaque server struct
641
+ def inspect_server(server)
642
+ strings = [server.hostname]
643
+ if !is_unix_socket?(server)
644
+ strings << ":#{server.port}"
645
+ strings << ":#{server.weight}" if options[:ketama_weighted]
646
+ end
647
+ strings.join
648
+ end
649
+ end