Class: Memcached

Inherits:
Object
  • Object
show all
Defined in:
lib/memcached/memcached.rb,
lib/memcached.rb,
lib/memcached/auth.rb,
lib/memcached/rails.rb,
lib/memcached/behaviors.rb,
lib/memcached/exceptions.rb

Overview

The Memcached client class.

Direct Known Subclasses

MemcacheAuth, Rails

Defined Under Namespace

Classes: Error, Rails

Constant Summary collapse

Lib =
Rlibmemcached
VERSION =
File.read("#{File.dirname(__FILE__)}/../CHANGELOG")[/v([\d\.]+)\./, 1]
BEHAVIORS =
load_constants("MEMCACHED_BEHAVIOR_")
BEHAVIOR_VALUES =
{
  false => 0, 
  true => 1
}
HASH_VALUES =
{}
DISTRIBUTION_VALUES =
{}
DIRECT_VALUE_BEHAVIORS =
[:retry_timeout, :connect_timeout, :rcv_timeout, :socket_recv_size, :poll_timeout, :socket_send_size, :server_failure_limit]
CONVERSION_FACTORS =
{
  :rcv_timeout => 1_000_000,
  :poll_timeout => 1_000,
  :connect_timeout => 1_000
}
FLAGS =
0x0
DEFAULTS =
{
  :hash => :fnv1_32,
  :no_block => false,
  :distribution => :consistent_ketama,
  :ketama_weighted => true,
  :buffer_requests => false,
  :cache_lookups => true,
  :support_cas => false,
  :tcp_nodelay => false,
  :show_backtraces => false,
  :retry_timeout => 30,
  :timeout => 0.25,
  :rcv_timeout => nil,
  :poll_timeout => nil,
  :connect_timeout => 2,
  :prefix_key => nil,
  :hash_with_prefix_key => true,
  :default_ttl => 604800,
  :default_weight => 8,
  :sort_hosts => false,
  :auto_eject_hosts => true,
  :server_failure_limit => 2,
  :verify_key => true,
  :use_udp => false,
  :binary_protocol => false,
  :credentials => nil
}
IGNORED =

:stopdoc:

0
ERRNO_HASH =
EXCEPTIONS =
[]
EMPTY_STRUCT =
Rlibmemcached::MemcachedSt.new
@@structs =

Track structs so we can free them

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(servers = nil, opts = {}) ⇒ Memcached

Returns a new instance of Memcached.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/memcached/memcached.rb', line 85

def initialize(servers = nil, opts = {})
  @struct = Lib::MemcachedSt.new
  Lib.memcached_create(@struct)
  @@structs[object_id] = @struct

  # Merge option defaults and discard meaningless keys
  @options = DEFAULTS.merge(opts)
  @options.delete_if { |k,v| not DEFAULTS.keys.include? k }
  @default_ttl = options[:default_ttl]
  
  if servers == nil || servers == []
    if ENV.key?("MEMCACHE_SERVERS")
      servers = ENV["MEMCACHE_SERVERS"].split(",").map do | s | s.strip end
    else
      servers = "127.0.0.1:11211"
    end
  end

  if options[:credentials] == nil && ENV.key?("MEMCACHE_USERNAME") && ENV.key?("MEMCACHE_PASSWORD")
    options[:credentials] = [ENV["MEMCACHE_USERNAME"], ENV["MEMCACHE_PASSWORD"]]
  end

  options[:binary_protocol] = true if options[:credentials] != nil

  # Force :buffer_requests to use :no_block
  # XXX Deleting the :no_block key should also work, but libmemcached doesn't seem to set it
  # consistently
  options[:no_block] = true if options[:buffer_requests]

  # Disallow weights without ketama
  options.delete(:ketama_weighted) if options[:distribution] != :consistent_ketama

  # Legacy accessor
  options[:prefix_key] = options.delete(:namespace) if options[:namespace]

  # Disallow :sort_hosts with consistent hashing
  if options[:sort_hosts] and options[:distribution] == :consistent
    raise ArgumentError, ":sort_hosts defeats :consistent hashing"
  end

  # Read timeouts
  options[:rcv_timeout] ||= options[:timeout] || 0
  options[:poll_timeout] ||= options[:timeout] || 0

  # Set the behaviors on the struct
  set_behaviors
  set_callbacks
  set_credentials

  # Freeze the hash
  options.freeze

  # Set the servers on the struct
  set_servers(servers)

  # Not found exceptions
  unless options[:show_backtraces]
    @not_found = NotFound.new
    @not_found.no_backtrace = true      
    @not_stored = NotStored.new
    @not_stored.no_backtrace = true      
  end

  ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)

end

Instance Attribute Details

#optionsObject (readonly)

:startdoc:



41
42
43
# File 'lib/memcached/memcached.rb', line 41

def options
  @options
end

Class Method Details

.finalize(id) ⇒ Object



152
153
154
155
156
157
158
159
# File 'lib/memcached/memcached.rb', line 152

def Memcached.finalize(id)
  # Don't leak clients!
  struct = @@structs[id]
  @@structs.delete(id)
  # Don't leak authentication data either. This will silently fail if it's not set.
  Lib.memcached_destroy_sasl_auth_data(struct)
  Lib.memcached_free(struct)
end

.load_constants(prefix, hash = {}) ⇒ Object

:stopdoc:



6
7
8
9
10
11
# File 'lib/memcached/behaviors.rb', line 6

def self.load_constants(prefix, hash = {})
  Lib.constants.grep(/^#{prefix}/).each do |const_name|
    hash[const_name[prefix.length..-1].downcase.to_sym] = Lib.const_get(const_name)
  end
  hash
end

Instance Method Details

#add(key, value, ttl = @default_ttl, marshal = true, flags = FLAGS) ⇒ Object

Add a key/value pair. Raises Memcached::NotStored if the key already exists on the server. The parameters are the same as set.



270
271
272
273
274
275
276
# File 'lib/memcached/memcached.rb', line 270

def add(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
  value = marshal ? Marshal.dump(value) : value.to_s
  check_return_code(
    Lib.memcached_add(@struct, key, value, ttl, flags),
    key
  )
end

#append(key, value) ⇒ Object

Appends a string to a key’s value. Accepts a String key and a String value. Raises Memcached::NotFound if the key does not exist on the server.

Note that the key must be initialized to an unmarshalled string first, via set, add, or replace with marshal set to false.



313
314
315
316
317
318
319
# File 'lib/memcached/memcached.rb', line 313

def append(key, value)
  # Requires memcached 1.2.4
  check_return_code(
    Lib.memcached_append(@struct, key, value.to_s, IGNORED, IGNORED),
    key
  )
end

#cas(key, ttl = @default_ttl, marshal = true, flags = FLAGS) ⇒ Object Also known as: compare_and_swap

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 Memcached::NotStored. Accepts a String key and a block.

Also accepts an optional ttl value.

CAS stands for “compare and swap”, and avoids the need for manual key mutexing. CAS support must be enabled in Memcached.new or a Memcached::ClientError will be raised. Note that CAS may be buggy in memcached itself.

Raises:

  • (ClientError)


336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/memcached/memcached.rb', line 336

def cas(key, ttl=@default_ttl, marshal=true, flags=FLAGS)
  raise ClientError, "CAS not enabled for this Memcached instance" unless options[:support_cas]

  value, flags, ret = Lib.memcached_get_rvalue(@struct, key)
  check_return_code(ret, key)
  cas = @struct.result.cas

  value = Marshal.load(value) if marshal
  value = yield value
  value = Marshal.dump(value) if marshal

  check_return_code(Lib.memcached_cas(@struct, key, value, ttl, flags, cas), key)
end

#cloneObject Also known as: dup

Safely copy this instance. Returns a Memcached instance.

clone is useful for threading, since each thread must have its own unshared Memcached object.



198
199
200
201
202
203
204
205
# File 'lib/memcached/memcached.rb', line 198

def clone
  # FIXME Memory leak
  # memcached = super
  # struct = Lib.memcached_clone(nil, @struct)
  # memcached.instance_variable_set('@struct', struct)
  # memcached
  self.class.new(servers, options)
end

#decrement(key, offset = 1) ⇒ Object Also known as: decr

Decrement a key’s value. The parameters and exception behavior are the same as increment.



290
291
292
293
294
# File 'lib/memcached/memcached.rb', line 290

def decrement(key, offset=1)
  ret, value = Lib.memcached_decrement(@struct, key, offset)
  check_return_code(ret, key)
  value
end

#delete(key) ⇒ Object

Deletes a key/value pair from the server. Accepts a String key. Raises Memcached::NotFound if the key does not exist.



355
356
357
358
359
360
# File 'lib/memcached/memcached.rb', line 355

def delete(key)
  check_return_code(
    Lib.memcached_delete(@struct, key, IGNORED),
    key
  )
end

#destroy_credentialsObject



2
3
4
5
6
# File 'lib/memcached/auth.rb', line 2

def destroy_credentials
  if options[:credentials] != nil
    check_return_code(Lib.memcached_destroy_sasl_auth_data(@struct))
  end
end

#flushObject

Flushes all key/value pairs from all the servers.



363
364
365
366
367
# File 'lib/memcached/memcached.rb', line 363

def flush
  check_return_code(
    Lib.memcached_flush(@struct, IGNORED)
  )
end

#get(keys, marshal = true) ⇒ Object

Gets a key’s value from the server. Accepts a String key or array of String keys.

Also accepts a marshal value, which defaults to true. Set marshal to false if you want the value to be returned directly as a String. Otherwise it will be assumed to be a marshalled Ruby object and unmarshalled.

If you pass a String key, and the key does not exist on the server, Memcached::NotFound will be raised. If you pass an array of keys, memcached’s multiget mode will be used, and a hash of key/value pairs will be returned. The hash will contain only the keys that were found.

The multiget behavior is subject to change in the future; however, for multiple lookups, it is much faster than normal mode.

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.



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/memcached/memcached.rb', line 381

def get(keys, marshal=true)
  if keys.is_a? Array
    # Multi get
    ret = Lib.memcached_mget(@struct, keys);
    check_return_code(ret, keys)

    hash = {}
    keys.each do
      value, key, flags, ret = Lib.memcached_fetch_rvalue(@struct)
      break if ret == Lib::MEMCACHED_END
      check_return_code(ret, key)
      # Assign the value
      hash[key] = (marshal ? Marshal.load(value) : value)
    end
    hash
  else
    # Single get
    value, flags, ret = Lib.memcached_get_rvalue(@struct, keys)
    check_return_code(ret, keys)
    marshal ? Marshal.load(value) : value
  end
end

#increment(key, offset = 1) ⇒ Object Also known as: incr

Increment a key’s value. Accepts a String key. Raises Memcached::NotFound if the key does not exist.

Also accepts an optional offset paramater, which defaults to 1. offset must be an integer.

Note that the key must be initialized to an unmarshalled integer first, via set, add, or replace with marshal set to false.



283
284
285
286
287
# File 'lib/memcached/memcached.rb', line 283

def increment(key, offset=1)
  ret, value = Lib.memcached_increment(@struct, key, offset)
  check_return_code(ret, key)
  value
end

#prepend(key, value) ⇒ Object

Prepends a string to a key’s value. The parameters and exception behavior are the same as append.



322
323
324
325
326
327
328
# File 'lib/memcached/memcached.rb', line 322

def prepend(key, value)
  # Requires memcached 1.2.4
  check_return_code(
    Lib.memcached_prepend(@struct, key, value.to_s, IGNORED, IGNORED),
    key
  )
end

#quitObject

Disconnect from all currently connected servers



221
222
223
224
# File 'lib/memcached/memcached.rb', line 221

def quit
  Lib.memcached_quit(@struct)
  self
end

#replace(key, value, ttl = @default_ttl, marshal = true, flags = FLAGS) ⇒ Object

Replace a key/value pair. Raises Memcached::NotFound if the key does not exist on the server. The parameters are the same as set.



302
303
304
305
306
307
308
# File 'lib/memcached/memcached.rb', line 302

def replace(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
  value = marshal ? Marshal.dump(value) : value.to_s
  check_return_code(
    Lib.memcached_replace(@struct, key, value, ttl, flags),
    key
  )
end

#reset(current_servers = nil) ⇒ Object

Reset the state of the libmemcached struct. This is useful for changing the server list at runtime.



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/memcached/memcached.rb', line 208

def reset(current_servers = nil)
  current_servers ||= servers
  destroy_credentials
  Lib.memcached_free(@struct)
  @struct = Lib::MemcachedSt.new
  Lib.memcached_create(@struct)
  set_behaviors
  set_callbacks
  set_credentials
  set_servers(current_servers)
end

#server_by_key(key) ⇒ Object

Return the server used by a particular key.



407
408
409
410
411
412
413
414
# File 'lib/memcached/memcached.rb', line 407

def server_by_key(key)
  ret = Lib.memcached_server_by_key(@struct, key)
  if ret.is_a?(Array)
    string = inspect_server(ret.first) 
    Rlibmemcached.memcached_server_free(ret.first)
    string
  end
end

#serversObject

Return the array of server strings used to configure this instance.



187
188
189
190
191
# File 'lib/memcached/memcached.rb', line 187

def servers
  server_structs.map do |server|
    inspect_server(server)
  end
end

#set(key, value, ttl = @default_ttl, marshal = true, flags = FLAGS) ⇒ Object

Set a key/value pair. Accepts a String key and an arbitrary Ruby object. Overwrites any existing value on the server.

Accepts an optional ttl value to specify the maximum lifetime of the key on the server. ttl can be either an integer number of seconds, or a Time elapsed time object. 0 means no ttl. Note that there is no guarantee that the key will persist as long as the ttl, but it will not persist longer.

Also accepts a marshal value, which defaults to true. Set marshal to false if you want the value to be set directly.



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/memcached/memcached.rb', line 257

def set(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
  value = marshal ? Marshal.dump(value) : value.to_s
  check_return_code(
    Lib.memcached_set(@struct, key, value, ttl, flags),
    key
  )
rescue ClientError
  # FIXME Memcached 1.2.8 occasionally rejects valid sets 
  tried = 1 and retry unless defined?(tried)
  raise
end

#set_credentialsObject



8
9
10
11
12
13
14
# File 'lib/memcached/auth.rb', line 8

def set_credentials
  # If credentials aren't provided, try to get them from the environment
  if options[:credentials] != nil
    username, password = options[:credentials]
    check_return_code(Lib.memcached_set_sasl_auth_data(@struct, username, password))
  end
end

#set_servers(servers) ⇒ Object

Set the server list. FIXME Does not necessarily free any existing server structs.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/memcached/memcached.rb', line 163

def set_servers(servers)
  Array(servers).each_with_index do |server, index|
    # Socket
    if server.is_a?(String) and File.socket?(server)
      args = [@struct, server, options[:default_weight].to_i]
      check_return_code(Lib.memcached_server_add_unix_socket_with_weight(*args))
    # Network
    elsif server.is_a?(String) and server =~ /^[\w\d\.-]+(:\d{1,5}){0,2}$/
      host, port, weight = server.split(":")
      args = [@struct, host, port.to_i, (weight || options[:default_weight]).to_i]                
      if options[:use_udp] #
        check_return_code(Lib.memcached_server_add_udp_with_weight(*args))
      else
        check_return_code(Lib.memcached_server_add_with_weight(*args))
      end
    else
      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)."
    end
  end
  # For inspect
  @servers = send(:servers)
end

#stats(subcommand = nil) ⇒ Object

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.



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/memcached/memcached.rb', line 417

def stats(subcommand=nil)
  stats = Hash.new([])

  stat_struct, ret = Lib.memcached_stat(@struct, subcommand)
  check_return_code(ret)

  keys, ret = Lib.memcached_stat_get_keys(@struct, stat_struct)
  check_return_code(ret)

  keys.each do |key|
     server_structs.size.times do |index|

       value, ret = Lib.memcached_stat_get_rvalue(
         @struct,
         Lib.memcached_select_stat_at(@struct, stat_struct, index),
         key)
       check_return_code(ret, key)

       value = case value
         when /^\d+\.\d+$/ then value.to_f
         when /^\d+$/ then value.to_i
         else value
       end

       stats[key.to_sym] += [value]
     end
  end

  Lib.memcached_stat_free(@struct, stat_struct)
  stats
rescue Memcached::SomeErrorsWereReported => _
  e = _.class.new("Error getting stats")
  e.set_backtrace(_.backtrace)
  raise e
end