arunthampi-memcached 0.17.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.gitmodules +3 -0
- data/BENCHMARKS +120 -0
- data/CHANGELOG +58 -0
- data/LICENSE +184 -0
- data/Manifest +27 -0
- data/README +118 -0
- data/Rakefile +45 -0
- data/TODO +4 -0
- data/VERSION +1 -0
- data/ext/extconf.rb +106 -0
- data/ext/libmemcached-0.32.tar.gz +0 -0
- data/ext/libmemcached.patch +270 -0
- data/ext/rlibmemcached.i +212 -0
- data/ext/rlibmemcached_wrap.c +13090 -0
- data/lib/memcached.rb +31 -0
- data/lib/memcached/behaviors.rb +78 -0
- data/lib/memcached/exceptions.rb +84 -0
- data/lib/memcached/integer.rb +6 -0
- data/lib/memcached/memcached.rb +554 -0
- data/lib/memcached/rails.rb +97 -0
- data/test/profile/benchmark.rb +210 -0
- data/test/profile/profile.rb +14 -0
- data/test/profile/valgrind.rb +147 -0
- data/test/setup.rb +29 -0
- data/test/teardown.rb +0 -0
- data/test/test_helper.rb +18 -0
- data/test/unit/binding_test.rb +8 -0
- data/test/unit/memcached_test.rb +1132 -0
- data/test/unit/rails_test.rb +102 -0
- metadata +94 -0
data/lib/memcached.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
=begin rdoc
|
3
|
+
The generated SWIG module for accessing libmemcached's C API.
|
4
|
+
|
5
|
+
Includes the full set of libmemcached static methods (as defined in <tt>$INCLUDE_PATH/libmemcached/memcached.h</tt>), and classes for the available structs:
|
6
|
+
|
7
|
+
* <b>Rlibmemcached::MemcachedResultSt</b>
|
8
|
+
* <b>Rlibmemcached::MemcachedServerSt</b>
|
9
|
+
* <b>Rlibmemcached::MemcachedSt</b>
|
10
|
+
* <b>Rlibmemcached::MemcachedStatSt</b>
|
11
|
+
* <b>Rlibmemcached::MemcachedStringSt</b>
|
12
|
+
|
13
|
+
A number of SWIG typemaps and C helper methods are also defined in <tt>ext/libmemcached.i</tt>.
|
14
|
+
|
15
|
+
=end
|
16
|
+
module Rlibmemcached
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rlibmemcached'
|
20
|
+
|
21
|
+
class Memcached
|
22
|
+
Lib = Rlibmemcached
|
23
|
+
raise "libmemcached 0.32 required; you somehow linked to #{Lib.memcached_lib_version}." unless "0.32" == Lib.memcached_lib_version
|
24
|
+
VERSION = File.read("#{File.dirname(__FILE__)}/../CHANGELOG")[/v([\d\.]+)\./, 1]
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'memcached/integer'
|
28
|
+
require 'memcached/exceptions'
|
29
|
+
require 'memcached/behaviors'
|
30
|
+
require 'memcached/memcached'
|
31
|
+
require 'memcached/rails'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
class Memcached
|
3
|
+
|
4
|
+
#:stopdoc:
|
5
|
+
|
6
|
+
def self.load_constants(prefix, hash = {})
|
7
|
+
Lib.constants.grep(/^#{prefix}/).each do |const_name|
|
8
|
+
hash[const_name[prefix.length..-1].downcase.to_sym] = Lib.const_get(const_name)
|
9
|
+
end
|
10
|
+
hash
|
11
|
+
end
|
12
|
+
|
13
|
+
BEHAVIORS = load_constants("MEMCACHED_BEHAVIOR_")
|
14
|
+
|
15
|
+
BEHAVIOR_VALUES = {
|
16
|
+
false => 0,
|
17
|
+
true => 1
|
18
|
+
}
|
19
|
+
|
20
|
+
HASH_VALUES = {}
|
21
|
+
BEHAVIOR_VALUES.merge!(load_constants("MEMCACHED_HASH_", HASH_VALUES))
|
22
|
+
|
23
|
+
DISTRIBUTION_VALUES = {}
|
24
|
+
BEHAVIOR_VALUES.merge!(load_constants("MEMCACHED_DISTRIBUTION_", DISTRIBUTION_VALUES))
|
25
|
+
|
26
|
+
DIRECT_VALUE_BEHAVIORS = [:retry_timeout, :connect_timeout, :rcv_timeout, :socket_recv_size, :poll_timeout, :socket_send_size, :server_failure_limit]
|
27
|
+
|
28
|
+
CONVERSION_FACTORS = {
|
29
|
+
:rcv_timeout => 1_000_000,
|
30
|
+
:poll_timeout => 1_000,
|
31
|
+
:connect_timeout => 1_000
|
32
|
+
}
|
33
|
+
|
34
|
+
#:startdoc:
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Set a behavior option for this Memcached instance. Accepts a Symbol <tt>behavior</tt> and either <tt>true</tt>, <tt>false</tt>, or a Symbol for <tt>value</tt>. Arguments are validated and converted into integers for the struct setter method.
|
39
|
+
def set_behavior(behavior, value) #:doc:
|
40
|
+
raise ArgumentError, "No behavior #{behavior.inspect}" unless b_id = BEHAVIORS[behavior]
|
41
|
+
|
42
|
+
# Scoped validations; annoying
|
43
|
+
msg = "Invalid behavior value #{value.inspect} for #{behavior.inspect}"
|
44
|
+
case behavior
|
45
|
+
when :hash then raise(ArgumentError, msg) unless HASH_VALUES[value]
|
46
|
+
when :distribution then raise(ArgumentError, msg) unless DISTRIBUTION_VALUES[value]
|
47
|
+
when *DIRECT_VALUE_BEHAVIORS then raise(ArgumentError, msg) unless value.is_a?(Numeric) and value >= 0
|
48
|
+
else
|
49
|
+
raise(ArgumentError, msg) unless BEHAVIOR_VALUES[value]
|
50
|
+
end
|
51
|
+
|
52
|
+
lib_value = BEHAVIOR_VALUES[value] || (value * (CONVERSION_FACTORS[behavior] || 1)).to_i
|
53
|
+
#STDERR.puts "Setting #{behavior}:#{b_id} => #{value} (#{lib_value})"
|
54
|
+
Lib.memcached_behavior_set(@struct, b_id, lib_value)
|
55
|
+
#STDERR.puts " -> set to #{get_behavior(behavior).inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get a behavior value for this Memcached instance. Accepts a Symbol.
|
59
|
+
def get_behavior(behavior)
|
60
|
+
raise ArgumentError, "No behavior #{behavior.inspect}" unless b_id = BEHAVIORS[behavior]
|
61
|
+
value = Lib.memcached_behavior_get(@struct, b_id)
|
62
|
+
|
63
|
+
if BEHAVIOR_VALUES.invert.has_key?(value)
|
64
|
+
# False, nil are valid values so we can not rely on direct lookups
|
65
|
+
case behavior
|
66
|
+
# Scoped values; still annoying
|
67
|
+
when :hash then HASH_VALUES.invert[value]
|
68
|
+
when :distribution then DISTRIBUTION_VALUES.invert[value]
|
69
|
+
when *DIRECT_VALUE_BEHAVIORS then value
|
70
|
+
else
|
71
|
+
BEHAVIOR_VALUES.invert[value]
|
72
|
+
end
|
73
|
+
else
|
74
|
+
value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
class Memcached
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
|
6
|
+
Superclass for all Memcached runtime exceptions.
|
7
|
+
|
8
|
+
Subclasses correspond one-to-one with server response strings or libmemcached errors. For example, raising <b>Memcached::NotFound</b> means that the server returned <tt>"NOT_FOUND\r\n"</tt>.
|
9
|
+
|
10
|
+
== Subclasses
|
11
|
+
|
12
|
+
* Memcached::ABadKeyWasProvidedOrCharactersOutOfRange
|
13
|
+
* Memcached::AKeyLengthOfZeroWasProvided
|
14
|
+
* Memcached::ATimeoutOccurred
|
15
|
+
* Memcached::ActionNotSupported
|
16
|
+
* Memcached::ActionQueued
|
17
|
+
* Memcached::ClientError
|
18
|
+
* Memcached::ConnectionBindFailure
|
19
|
+
* Memcached::ConnectionDataDoesNotExist
|
20
|
+
* Memcached::ConnectionDataExists
|
21
|
+
* Memcached::ConnectionFailure
|
22
|
+
* Memcached::ConnectionSocketCreateFailure
|
23
|
+
* Memcached::CouldNotOpenUnixSocket
|
24
|
+
* Memcached::EncounteredAnUnknownStatKey
|
25
|
+
* Memcached::Failure
|
26
|
+
* Memcached::FetchWasNotCompleted
|
27
|
+
* Memcached::HostnameLookupFailure
|
28
|
+
* Memcached::ItemValue
|
29
|
+
* Memcached::MemoryAllocationFailure
|
30
|
+
* Memcached::NoServersDefined
|
31
|
+
* Memcached::NotFound
|
32
|
+
* Memcached::NotStored
|
33
|
+
* Memcached::PartialRead
|
34
|
+
* Memcached::ProtocolError
|
35
|
+
* Memcached::ReadFailure
|
36
|
+
* Memcached::ServerDelete
|
37
|
+
* Memcached::ServerEnd
|
38
|
+
* Memcached::ServerError
|
39
|
+
* Memcached::ServerIsMarkedDead
|
40
|
+
* Memcached::ServerValue
|
41
|
+
* Memcached::SomeErrorsWereReported
|
42
|
+
* Memcached::StatValue
|
43
|
+
* Memcached::SystemError
|
44
|
+
* Memcached::TheHostTransportProtocolDoesNotMatchThatOfTheClient
|
45
|
+
* Memcached::UnknownReadFailure
|
46
|
+
* Memcached::WriteFailure
|
47
|
+
|
48
|
+
=end
|
49
|
+
class Error < RuntimeError
|
50
|
+
attr_accessor :no_backtrace
|
51
|
+
|
52
|
+
def set_backtrace(*args)
|
53
|
+
@no_backtrace ? [] : super
|
54
|
+
end
|
55
|
+
|
56
|
+
def backtrace(*args)
|
57
|
+
@no_backtrace ? [] : super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#:stopdoc:
|
62
|
+
|
63
|
+
class << self
|
64
|
+
private
|
65
|
+
def camelize(string)
|
66
|
+
string.downcase.gsub('/', ' or ').split(' ').map {|s| s.capitalize}.join
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ERRNO_HASH = Hash[*Errno.constants.map{ |c| [Errno.const_get(c)::Errno, Errno.const_get(c).new.message] }.flatten]
|
71
|
+
|
72
|
+
EXCEPTIONS = []
|
73
|
+
EMPTY_STRUCT = Rlibmemcached::MemcachedSt.new
|
74
|
+
Rlibmemcached.memcached_create(EMPTY_STRUCT)
|
75
|
+
|
76
|
+
# Generate exception classes
|
77
|
+
Rlibmemcached::MEMCACHED_MAXIMUM_RETURN.times do |index|
|
78
|
+
description = Rlibmemcached.memcached_strerror(EMPTY_STRUCT, index).gsub("!", "")
|
79
|
+
exception_class = eval("class #{camelize(description)} < Error; self; end")
|
80
|
+
EXCEPTIONS << exception_class
|
81
|
+
end
|
82
|
+
|
83
|
+
#:startdoc:
|
84
|
+
end
|
@@ -0,0 +1,554 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
The Memcached client class.
|
5
|
+
=end
|
6
|
+
class Memcached
|
7
|
+
|
8
|
+
FLAGS = 0x0
|
9
|
+
|
10
|
+
DEFAULTS = {
|
11
|
+
:hash => :fnv1_32,
|
12
|
+
:no_block => false,
|
13
|
+
:distribution => :consistent_ketama,
|
14
|
+
:ketama_weighted => true,
|
15
|
+
:buffer_requests => false,
|
16
|
+
:cache_lookups => true,
|
17
|
+
:support_cas => false,
|
18
|
+
:tcp_nodelay => false,
|
19
|
+
:show_backtraces => false,
|
20
|
+
:retry_timeout => 30,
|
21
|
+
:timeout => 0.25,
|
22
|
+
:rcv_timeout => nil,
|
23
|
+
:poll_timeout => nil,
|
24
|
+
:connect_timeout => 2,
|
25
|
+
:prefix_key => nil,
|
26
|
+
:hash_with_prefix_key => true,
|
27
|
+
:default_ttl => 604800,
|
28
|
+
:default_weight => 8,
|
29
|
+
:sort_hosts => false,
|
30
|
+
:auto_eject_hosts => true,
|
31
|
+
:server_failure_limit => 2,
|
32
|
+
:verify_key => true,
|
33
|
+
:use_udp => false,
|
34
|
+
:binary_protocol => false,
|
35
|
+
:chunk_split_size => 1048300
|
36
|
+
}
|
37
|
+
|
38
|
+
#:stopdoc:
|
39
|
+
IGNORED = 0
|
40
|
+
#:startdoc:
|
41
|
+
|
42
|
+
attr_reader :options # Return the options Hash used to configure this instance.
|
43
|
+
|
44
|
+
###### Configuration
|
45
|
+
|
46
|
+
=begin rdoc
|
47
|
+
Create a new Memcached instance. Accepts string or array of server strings, as well an an optional configuration hash.
|
48
|
+
|
49
|
+
Memcached.new('localhost', ...) # A single server
|
50
|
+
Memcached.new(['web001:11212', 'web002:11212'], ...) # Two servers with custom ports
|
51
|
+
Memcached.new(['web001:11211:2', 'web002:11211:8'], ...) # Two servers with default ports and explicit weights
|
52
|
+
|
53
|
+
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.
|
54
|
+
|
55
|
+
Valid option parameters are:
|
56
|
+
|
57
|
+
<tt>:prefix_key</tt>:: A string to prepend to every key, for namespacing. Max length is 127.
|
58
|
+
<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.
|
59
|
+
<tt>:distribution</tt>:: Either <tt>:modula</tt>, <tt>:consistent_ketama</tt>, <tt>:consistent_wheel</tt>, or <tt>:ketama</tt>. Defaults to <tt>:ketama</tt>.
|
60
|
+
<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.
|
61
|
+
<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>.
|
62
|
+
<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>.
|
63
|
+
<tt>:cache_lookups</tt>:: Whether to cache hostname lookups for the life of the instance. Defaults to <tt>true</tt>.
|
64
|
+
<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>Memcached::ProtocolError</b> exceptions.
|
65
|
+
<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.
|
66
|
+
<tt>:no_block</tt>:: Whether to use pipelining for writes. Accepts <tt>true</tt> or <tt>false</tt>.
|
67
|
+
<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.
|
68
|
+
<tt>:show_backtraces</tt>:: Whether <b>Memcached::NotFound</b> and <b>Memcached::NotStored</b> exceptions should include backtraces. Generating backtraces is slow, so this is off by default. Turn it on to ease debugging.
|
69
|
+
<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.
|
70
|
+
<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.
|
71
|
+
<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.
|
72
|
+
<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.
|
73
|
+
<tt>:hash_with_prefix_key</tt>:: Whether to include the prefix when calculating which server a key falls on. Defaults to <tt>true</tt>.
|
74
|
+
<tt>:use_udp</tt>:: Use the UDP protocol to reduce connection overhead. Defaults to false.
|
75
|
+
<tt>:binary_protocol</tt>:: Use the binary protocol to reduce query processing overhead. Defaults to false.
|
76
|
+
<tt>:sort_hosts</tt>:: Whether to force the server list to stay sorted. This defeats consistent hashing and is rarely useful.
|
77
|
+
<tt>:verify_key</tt>:: Validate keys before accepting them. Never disable this.
|
78
|
+
<tt>:chunk_split_size</tt>:: Number of bytes after which items that are bigger than the 1MB memcached bucket max. size are split.
|
79
|
+
|
80
|
+
Please note that when pipelining is enabled, setter and deleter methods do not raise on errors. For example, if you try to set an invalid key with <tt>:no_block => true</tt>, 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.
|
81
|
+
|
82
|
+
=end
|
83
|
+
|
84
|
+
def initialize(servers = "localhost:11211", opts = {})
|
85
|
+
@struct = Lib::MemcachedSt.new
|
86
|
+
Lib.memcached_create(@struct)
|
87
|
+
|
88
|
+
# Merge option defaults and discard meaningless keys
|
89
|
+
@options = DEFAULTS.merge(opts)
|
90
|
+
@options.delete_if { |k,v| not DEFAULTS.keys.include? k }
|
91
|
+
@default_ttl = options[:default_ttl]
|
92
|
+
|
93
|
+
# Force :buffer_requests to use :no_block
|
94
|
+
# XXX Deleting the :no_block key should also work, but libmemcached doesn't seem to set it
|
95
|
+
# consistently
|
96
|
+
options[:no_block] = true if options[:buffer_requests]
|
97
|
+
|
98
|
+
# Disallow weights without ketama
|
99
|
+
options.delete(:ketama_weighted) if options[:distribution] != :consistent_ketama
|
100
|
+
|
101
|
+
# Legacy accessor
|
102
|
+
options[:prefix_key] = options.delete(:namespace) if options[:namespace]
|
103
|
+
|
104
|
+
# Disallow :sort_hosts with consistent hashing
|
105
|
+
if options[:sort_hosts] and options[:distribution] == :consistent
|
106
|
+
raise ArgumentError, ":sort_hosts defeats :consistent hashing"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Read timeouts
|
110
|
+
options[:rcv_timeout] ||= options[:timeout] || 0
|
111
|
+
options[:poll_timeout] ||= options[:timeout] || 0
|
112
|
+
|
113
|
+
# Set the behaviors on the struct
|
114
|
+
set_behaviors
|
115
|
+
set_callbacks
|
116
|
+
|
117
|
+
# Freeze the hash
|
118
|
+
options.freeze
|
119
|
+
|
120
|
+
# Set the servers on the struct
|
121
|
+
set_servers(servers)
|
122
|
+
|
123
|
+
# Not found exceptions
|
124
|
+
unless options[:show_backtraces]
|
125
|
+
@not_found = NotFound.new
|
126
|
+
@not_found.no_backtrace = true
|
127
|
+
@not_stored = NotStored.new
|
128
|
+
@not_stored.no_backtrace = true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Set the server list.
|
133
|
+
# FIXME Does not necessarily free any existing server structs.
|
134
|
+
def set_servers(servers)
|
135
|
+
Array(servers).each_with_index do |server, index|
|
136
|
+
# Socket
|
137
|
+
if server.is_a?(String) and File.socket?(server)
|
138
|
+
args = [@struct, server, options[:default_weight].to_i]
|
139
|
+
check_return_code(Lib.memcached_server_add_unix_socket_with_weight(*args))
|
140
|
+
# Network
|
141
|
+
elsif server.is_a?(String) and server =~ /^[\w\d\.-]+(:\d{1,5}){0,2}$/
|
142
|
+
host, port, weight = server.split(":")
|
143
|
+
args = [@struct, host, port.to_i, (weight || options[:default_weight]).to_i]
|
144
|
+
if options[:use_udp] #
|
145
|
+
check_return_code(Lib.memcached_server_add_udp_with_weight(*args))
|
146
|
+
else
|
147
|
+
check_return_code(Lib.memcached_server_add_with_weight(*args))
|
148
|
+
end
|
149
|
+
else
|
150
|
+
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)."
|
151
|
+
end
|
152
|
+
end
|
153
|
+
# For inspect
|
154
|
+
@servers = send(:servers)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Return the array of server strings used to configure this instance.
|
158
|
+
def servers
|
159
|
+
server_structs.map do |server|
|
160
|
+
inspect_server(server)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Safely copy this instance. Returns a Memcached instance.
|
165
|
+
#
|
166
|
+
# <tt>clone</tt> is useful for threading, since each thread must have its own unshared Memcached
|
167
|
+
# object.
|
168
|
+
#
|
169
|
+
def clone
|
170
|
+
# FIXME Memory leak
|
171
|
+
# memcached = super
|
172
|
+
# struct = Lib.memcached_clone(nil, @struct)
|
173
|
+
# memcached.instance_variable_set('@struct', struct)
|
174
|
+
# memcached
|
175
|
+
self.class.new(servers, options)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Reset the state of the libmemcached struct. This is useful for changing the server list at runtime.
|
179
|
+
def reset(current_servers = nil)
|
180
|
+
current_servers ||= servers
|
181
|
+
@struct = Lib::MemcachedSt.new
|
182
|
+
Lib.memcached_create(@struct)
|
183
|
+
set_behaviors
|
184
|
+
set_callbacks
|
185
|
+
set_servers(current_servers)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Disconnect from all currently connected servers
|
189
|
+
def quit
|
190
|
+
Lib.memcached_quit(@struct)
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
#:stopdoc:
|
195
|
+
alias :dup :clone #:nodoc:
|
196
|
+
#:startdoc:
|
197
|
+
|
198
|
+
### Configuration helpers
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# Return an array of raw <tt>memcached_host_st</tt> structs for this instance.
|
203
|
+
def server_structs
|
204
|
+
array = []
|
205
|
+
if @struct.hosts
|
206
|
+
@struct.hosts.count.times do |i|
|
207
|
+
array << Lib.memcached_select_server_at(@struct, i)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
array
|
211
|
+
end
|
212
|
+
|
213
|
+
###### Operations
|
214
|
+
|
215
|
+
public
|
216
|
+
|
217
|
+
### Setters
|
218
|
+
|
219
|
+
# Set a key/value pair. Accepts a String <tt>key</tt> and an arbitrary Ruby object. Overwrites any existing value on the server.
|
220
|
+
#
|
221
|
+
# 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.
|
222
|
+
#
|
223
|
+
# 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.
|
224
|
+
#
|
225
|
+
def set(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
226
|
+
value = marshal ? Marshal.dump(value) : value.to_s
|
227
|
+
check_return_code(
|
228
|
+
Lib.memcached_set(@struct, key, value, ttl, flags),
|
229
|
+
key
|
230
|
+
)
|
231
|
+
rescue ClientError
|
232
|
+
# FIXME Memcached 1.2.8 occasionally rejects valid sets
|
233
|
+
tried = 1 and retry unless defined?(tried)
|
234
|
+
raise
|
235
|
+
end
|
236
|
+
|
237
|
+
# EXPERIMENTAL
|
238
|
+
#
|
239
|
+
# Sets a key/value pair that is (potentially) bigger than 1MB (i.e. the memcached limit for bucket sizes).
|
240
|
+
#
|
241
|
+
# Accepts optional <tt>timeout</tt> and <tt>marshal</tt> arguments like <tt>set</tt>.
|
242
|
+
#
|
243
|
+
# The value is split into chunks (each smaller than or equal in size to the <tt>chunk_split_size</tt> option)
|
244
|
+
# and inserted into separate buckets. The keys are of the form: #{key}_0, #{key}_1, #{key}_2.
|
245
|
+
# The bucket referred to by the given <tt>key</tt> contains a chunk "header" with a #chunks property that equals
|
246
|
+
# the number of chunks.
|
247
|
+
#
|
248
|
+
# Note that values that fit within a single chunk _are_ still "split" - the chunk header (and the single chunk)
|
249
|
+
# is still set.
|
250
|
+
#
|
251
|
+
# WARNING: This method is non-atomic by nature, since we are really performing multiple <tt>set</tt>s serially.
|
252
|
+
def big_set(key, value, timeout=0, marshal=true)
|
253
|
+
value = marshal ? Marshal.dump(value) : value.to_s
|
254
|
+
|
255
|
+
chunk_size = options[:chunk_split_size]
|
256
|
+
chunks = (value.size/chunk_size.to_f).ceil
|
257
|
+
|
258
|
+
# Set the number of chunks (in a faux "header") in the bucket for the actual key.
|
259
|
+
set(key, OpenStruct.new(:chunks => chunks), timeout, true)
|
260
|
+
|
261
|
+
chunks.times do |chunk_num|
|
262
|
+
set("#{key}_#{chunk_num}", value[chunk_num * chunk_size, chunk_size], timeout, false)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# 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>.
|
267
|
+
def add(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
268
|
+
value = marshal ? Marshal.dump(value) : value.to_s
|
269
|
+
check_return_code(
|
270
|
+
Lib.memcached_add(@struct, key, value, ttl, flags),
|
271
|
+
key
|
272
|
+
)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Increment a key's value. Accepts a String <tt>key</tt>. Raises <b>Memcached::NotFound</b> if the key does not exist.
|
276
|
+
#
|
277
|
+
# Also accepts an optional <tt>offset</tt> paramater, which defaults to 1. <tt>offset</tt> must be an integer.
|
278
|
+
#
|
279
|
+
# 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>.
|
280
|
+
def increment(key, offset=1)
|
281
|
+
ret, value = Lib.memcached_increment(@struct, key, offset)
|
282
|
+
check_return_code(ret, key)
|
283
|
+
value
|
284
|
+
end
|
285
|
+
|
286
|
+
# Decrement a key's value. The parameters and exception behavior are the same as <tt>increment</tt>.
|
287
|
+
def decrement(key, offset=1)
|
288
|
+
ret, value = Lib.memcached_decrement(@struct, key, offset)
|
289
|
+
check_return_code(ret, key)
|
290
|
+
value
|
291
|
+
end
|
292
|
+
|
293
|
+
#:stopdoc:
|
294
|
+
alias :incr :increment
|
295
|
+
alias :decr :decrement
|
296
|
+
#:startdoc:
|
297
|
+
|
298
|
+
# 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>.
|
299
|
+
def replace(key, value, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
300
|
+
value = marshal ? Marshal.dump(value) : value.to_s
|
301
|
+
check_return_code(
|
302
|
+
Lib.memcached_replace(@struct, key, value, ttl, flags),
|
303
|
+
key
|
304
|
+
)
|
305
|
+
end
|
306
|
+
|
307
|
+
# 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.
|
308
|
+
#
|
309
|
+
# 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>.
|
310
|
+
def append(key, value)
|
311
|
+
# Requires memcached 1.2.4
|
312
|
+
check_return_code(
|
313
|
+
Lib.memcached_append(@struct, key, value.to_s, IGNORED, IGNORED),
|
314
|
+
key
|
315
|
+
)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Prepends a string to a key's value. The parameters and exception behavior are the same as <tt>append</tt>.
|
319
|
+
def prepend(key, value)
|
320
|
+
# Requires memcached 1.2.4
|
321
|
+
check_return_code(
|
322
|
+
Lib.memcached_prepend(@struct, key, value.to_s, IGNORED, IGNORED),
|
323
|
+
key
|
324
|
+
)
|
325
|
+
end
|
326
|
+
|
327
|
+
# 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.
|
328
|
+
#
|
329
|
+
# Also accepts an optional <tt>ttl</tt> value.
|
330
|
+
#
|
331
|
+
# 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.
|
332
|
+
#
|
333
|
+
def cas(key, ttl=@default_ttl, marshal=true, flags=FLAGS)
|
334
|
+
raise ClientError, "CAS not enabled for this Memcached instance" unless options[:support_cas]
|
335
|
+
|
336
|
+
value, flags, ret = Lib.memcached_get_rvalue(@struct, key)
|
337
|
+
check_return_code(ret, key)
|
338
|
+
cas = @struct.result.cas
|
339
|
+
|
340
|
+
value = Marshal.load(value) if marshal
|
341
|
+
value = yield value
|
342
|
+
value = Marshal.dump(value) if marshal
|
343
|
+
|
344
|
+
check_return_code(Lib.memcached_cas(@struct, key, value, ttl, flags, cas), key)
|
345
|
+
end
|
346
|
+
|
347
|
+
alias :compare_and_swap :cas
|
348
|
+
|
349
|
+
### Deleters
|
350
|
+
|
351
|
+
# 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.
|
352
|
+
def delete(key)
|
353
|
+
check_return_code(
|
354
|
+
Lib.memcached_delete(@struct, key, IGNORED),
|
355
|
+
key
|
356
|
+
)
|
357
|
+
end
|
358
|
+
|
359
|
+
# Flushes all key/value pairs from all the servers.
|
360
|
+
def flush
|
361
|
+
check_return_code(
|
362
|
+
Lib.memcached_flush(@struct, IGNORED)
|
363
|
+
)
|
364
|
+
end
|
365
|
+
|
366
|
+
### Getters
|
367
|
+
|
368
|
+
# Gets a key's value from the server. Accepts a String <tt>key</tt> or array of String <tt>keys</tt>.
|
369
|
+
#
|
370
|
+
# 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.
|
371
|
+
#
|
372
|
+
# 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.
|
373
|
+
#
|
374
|
+
# The multiget behavior is subject to change in the future; however, for multiple lookups, it is much faster than normal mode.
|
375
|
+
#
|
376
|
+
# 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.
|
377
|
+
#
|
378
|
+
def get(keys, marshal=true)
|
379
|
+
if keys.is_a? Array
|
380
|
+
# Multi get
|
381
|
+
ret = Lib.memcached_mget(@struct, keys);
|
382
|
+
check_return_code(ret, keys)
|
383
|
+
|
384
|
+
hash = {}
|
385
|
+
keys.each do
|
386
|
+
value, key, flags, ret = Lib.memcached_fetch_rvalue(@struct)
|
387
|
+
break if ret == Lib::MEMCACHED_END
|
388
|
+
check_return_code(ret, key)
|
389
|
+
# Assign the value
|
390
|
+
hash[key] = (marshal ? Marshal.load(value) : value)
|
391
|
+
end
|
392
|
+
hash
|
393
|
+
else
|
394
|
+
# Single get
|
395
|
+
value, flags, ret = Lib.memcached_get_rvalue(@struct, keys)
|
396
|
+
check_return_code(ret, keys)
|
397
|
+
marshal ? Marshal.load(value) : value
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# EXPERIMENTAL
|
402
|
+
#
|
403
|
+
# Gets a key's value from the server. Accepts a single String key.
|
404
|
+
#
|
405
|
+
# Accepts an optional <tt>marshal</tt> argument, which defaults to <tt>true</tt>, like <tt>get</tt>.
|
406
|
+
def big_get(key, marshal=true)
|
407
|
+
chunk_header = get(key, true)
|
408
|
+
|
409
|
+
# A valid chunk header should respond to #chunks.
|
410
|
+
return marshal ? chunk_header : get(key, marshal) unless chunk_header.respond_to?(:chunks)
|
411
|
+
|
412
|
+
chunks = chunk_header.chunks
|
413
|
+
chunk_keys = (0...chunks).map { |i| "#{key}_#{i}" }
|
414
|
+
|
415
|
+
# Use multiget #get - this returns a hash of key/value pairs.
|
416
|
+
chunky_hash_browns = get(chunk_keys, false)
|
417
|
+
|
418
|
+
# If any of the chunks are missing, the entire item is considered missing.
|
419
|
+
raise Memcached::NotFound if chunky_hash_browns.size != chunks
|
420
|
+
|
421
|
+
# Concat all the chunked values.
|
422
|
+
value = chunky_hash_browns.sort.map { |k, v| v }.join
|
423
|
+
value = Marshal.load(value) if marshal
|
424
|
+
value
|
425
|
+
end
|
426
|
+
|
427
|
+
### Information methods
|
428
|
+
|
429
|
+
# Return the server used by a particular key.
|
430
|
+
def server_by_key(key)
|
431
|
+
ret = Lib.memcached_server_by_key(@struct, key)
|
432
|
+
if ret.is_a?(Array)
|
433
|
+
string = inspect_server(ret.first)
|
434
|
+
Rlibmemcached.memcached_server_free(ret.first)
|
435
|
+
string
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
# 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.
|
440
|
+
def stats
|
441
|
+
stats = Hash.new([])
|
442
|
+
|
443
|
+
stat_struct, ret = Lib.memcached_stat(@struct, "")
|
444
|
+
check_return_code(ret)
|
445
|
+
|
446
|
+
keys, ret = Lib.memcached_stat_get_keys(@struct, stat_struct)
|
447
|
+
check_return_code(ret)
|
448
|
+
|
449
|
+
keys.each do |key|
|
450
|
+
server_structs.size.times do |index|
|
451
|
+
|
452
|
+
value, ret = Lib.memcached_stat_get_rvalue(
|
453
|
+
@struct,
|
454
|
+
Lib.memcached_select_stat_at(@struct, stat_struct, index),
|
455
|
+
key)
|
456
|
+
check_return_code(ret, key)
|
457
|
+
|
458
|
+
value = case value
|
459
|
+
when /^\d+\.\d+$/ then value.to_f
|
460
|
+
when /^\d+$/ then value.to_i
|
461
|
+
else value
|
462
|
+
end
|
463
|
+
|
464
|
+
stats[key.to_sym] += [value]
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
Lib.memcached_stat_free(@struct, stat_struct)
|
469
|
+
stats
|
470
|
+
rescue Memcached::SomeErrorsWereReported => _
|
471
|
+
e = _.class.new("Error getting stats")
|
472
|
+
e.set_backtrace(_.backtrace)
|
473
|
+
raise e
|
474
|
+
end
|
475
|
+
|
476
|
+
### Operations helpers
|
477
|
+
|
478
|
+
private
|
479
|
+
|
480
|
+
# 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.
|
481
|
+
def check_return_code(ret, key = nil) #:doc:
|
482
|
+
if ret == 0 # Memcached::Success
|
483
|
+
elsif ret == Lib::MEMCACHED_BUFFERED # Memcached::ActionQueued
|
484
|
+
elsif ret == Lib::MEMCACHED_NOTFOUND and @not_found
|
485
|
+
raise @not_found
|
486
|
+
elsif ret == Lib::MEMCACHED_NOTSTORED and @not_stored
|
487
|
+
raise @not_stored
|
488
|
+
else
|
489
|
+
message = "Key #{inspect_keys(key, (detect_failure if ret == Lib::MEMCACHED_SERVER_MARKED_DEAD)).inspect}"
|
490
|
+
if key.is_a?(String)
|
491
|
+
if ret == Lib::MEMCACHED_ERRNO
|
492
|
+
server = Lib.memcached_server_by_key(@struct, key)
|
493
|
+
errno = server.first.cached_errno
|
494
|
+
message = "Errno #{errno}: #{ERRNO_HASH[errno].inspect}. #{message}"
|
495
|
+
elsif ret == Lib::MEMCACHED_SERVER_ERROR
|
496
|
+
server = Lib.memcached_server_by_key(@struct, key)
|
497
|
+
message = "\"#{server.first.cached_server_error}\". #{message}."
|
498
|
+
end
|
499
|
+
end
|
500
|
+
raise EXCEPTIONS[ret], message
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# Turn an array of keys into a hash of keys to servers.
|
505
|
+
def inspect_keys(keys, server = nil)
|
506
|
+
Hash[*Array(keys).map do |key|
|
507
|
+
[key, server || server_by_key(key)]
|
508
|
+
end.flatten]
|
509
|
+
end
|
510
|
+
|
511
|
+
# Find which server failed most recently.
|
512
|
+
# FIXME Is this still necessary with cached_errno?
|
513
|
+
def detect_failure
|
514
|
+
time = Time.now
|
515
|
+
server = server_structs.detect do |server|
|
516
|
+
server.next_retry > time
|
517
|
+
end
|
518
|
+
inspect_server(server) if server
|
519
|
+
end
|
520
|
+
|
521
|
+
# Set the behaviors on the struct from the current options.
|
522
|
+
def set_behaviors
|
523
|
+
BEHAVIORS.keys.each do |behavior|
|
524
|
+
set_behavior(behavior, options[behavior]) if options.key?(behavior)
|
525
|
+
end
|
526
|
+
# BUG Hash must be last due to the weird Libmemcached multi-behaviors
|
527
|
+
set_behavior(:hash, options[:hash])
|
528
|
+
end
|
529
|
+
|
530
|
+
# Set the callbacks on the struct from the current options.
|
531
|
+
def set_callbacks
|
532
|
+
# Only support prefix_key for now
|
533
|
+
if options[:prefix_key]
|
534
|
+
unless options[:prefix_key].size < Lib::MEMCACHED_PREFIX_KEY_MAX_SIZE
|
535
|
+
raise ArgumentError, "Max prefix_key size is #{Lib::MEMCACHED_PREFIX_KEY_MAX_SIZE - 1}"
|
536
|
+
end
|
537
|
+
Lib.memcached_callback_set(@struct, Lib::MEMCACHED_CALLBACK_PREFIX_KEY, options[:prefix_key])
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
def is_unix_socket?(server)
|
542
|
+
server.type == Lib::MEMCACHED_CONNECTION_UNIX_SOCKET
|
543
|
+
end
|
544
|
+
|
545
|
+
# Stringify an opaque server struct
|
546
|
+
def inspect_server(server)
|
547
|
+
strings = [server.hostname]
|
548
|
+
if !is_unix_socket?(server)
|
549
|
+
strings << ":#{server.port}"
|
550
|
+
strings << ":#{server.weight}" if options[:ketama_weighted]
|
551
|
+
end
|
552
|
+
strings.join
|
553
|
+
end
|
554
|
+
end
|