memcache 1.2.0 → 1.2.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.
data/lib/memcache.rb CHANGED
@@ -1,43 +1,82 @@
1
1
  require 'zlib'
2
2
 
3
3
  $:.unshift(File.dirname(__FILE__))
4
+ require 'memcache/base'
4
5
  require 'memcache/server'
5
6
  require 'memcache/local_server'
6
- require 'memcache/segmented_server'
7
+ begin
8
+ require 'memcache/native_server'
9
+ rescue LoadError => e
10
+ puts "memcache is not using native bindings. for faster performance, compile extensions by hand or install as a local gem."
11
+ end
12
+ require 'memcache/segmented'
7
13
 
8
14
  class Memcache
9
15
  DEFAULT_EXPIRY = 0
10
16
  LOCK_TIMEOUT = 5
11
17
  WRITE_LOCK_WAIT = 1
12
18
 
13
- attr_reader :default_expiry, :default_namespace, :servers
19
+ attr_reader :default_expiry, :namespace, :servers, :backup
20
+
21
+ class Error < StandardError; end
22
+ class ConnectionError < Error
23
+ def initialize(e)
24
+ if e.kind_of?(String)
25
+ super
26
+ else
27
+ super("(#{e.class}) #{e.message}")
28
+ set_backtrace(e.backtrace)
29
+ end
30
+ end
31
+ end
32
+ class ServerError < Error; end
33
+ class ClientError < Error; end
14
34
 
15
35
  def initialize(opts)
16
- @default_expiry = opts[:default_expiry] || DEFAULT_EXPIRY
17
- @default_namespace = opts[:namespace]
18
- default_server = opts[:segment_large_values] ? SegmentedServer : Server
19
-
20
- @servers = (opts[:servers] || [ opts[:server] ]).collect do |server|
21
- case server
22
- when Hash
23
- server = default_server.new(opts.merge(server))
24
- when String
25
- host, port = server.split(':')
26
- server = default_server.new(opts.merge(:host => host, :port => port))
27
- when Class
28
- server = server.new
29
- when :local
30
- server = Memcache::LocalServer.new
36
+ @default_expiry = opts[:default_expiry] || DEFAULT_EXPIRY
37
+ @backup = opts[:backup] # for multi-level caches
38
+ @hash_with_prefix = opts[:hash_with_prefix].nil? ? true : opts[:hash_with_prefix]
39
+
40
+ if opts[:native]
41
+ native_opts = {}
42
+ native_opts[:servers] = (opts[:servers] || [ opts[:server] ]).collect do |server|
43
+ server.is_a?(Hash) ? "#{server[:host]}:#{server[:port]}" : server
44
+ end
45
+ native_opts[:hash] = opts[:hash] || :crc
46
+ native_opts[:hash_with_prefix] = @hash_with_prefix
47
+ native_opts[:binary] = opts[:binary]
48
+
49
+ server_class = opts[:segment_large_values] ? SegmentedNativeServer : NativeServer
50
+ @servers = [server_class.new(native_opts)]
51
+ else
52
+ raise "only CRC hashing is supported unless :native => true" if opts[:hash] and opts[:hash] != :crc
53
+
54
+ server_class = opts[:segment_large_values] ? SegmentedServer : Server
55
+ @servers = (opts[:servers] || [ opts[:server] ]).collect do |server|
56
+ case server
57
+ when Hash
58
+ server = server_class.new(opts.merge(server))
59
+ when String
60
+ host, port = server.split(':')
61
+ server = server_class.new(opts.merge(:host => host, :port => port))
62
+ when Class
63
+ server = server.new
64
+ when :local
65
+ server = Memcache::LocalServer.new
66
+ end
67
+ server
31
68
  end
32
- server
33
69
  end
70
+
71
+ @server = @servers.first if @servers.size == 1 and @backup.nil?
72
+ self.namespace = opts[:namespace] if opts[:namespace]
34
73
  end
35
74
 
36
75
  def clone
37
76
  self.class.new(
38
- :default_expiry => default_expiry,
39
- :default_namespace => default_namespace,
40
- :servers => servers.collect {|s| s.clone}
77
+ :default_expiry => default_expiry,
78
+ :namespace => namespace,
79
+ :servers => servers.collect {|s| s.clone}
41
80
  )
42
81
  end
43
82
 
@@ -45,23 +84,21 @@ class Memcache
45
84
  "<Memcache: %d servers, ns: %p>" % [@servers.length, namespace]
46
85
  end
47
86
 
48
- def namespace
49
- @namespace || default_namespace
50
- end
51
-
52
87
  def namespace=(namespace)
53
- if default_namespace == namespace
54
- @namespace = nil
55
- else
56
- @namespace = namespace
88
+ @namespace = namespace
89
+ prefix = "#{namespace}:"
90
+ servers.each do |server|
91
+ server.prefix = prefix
57
92
  end
93
+ backup.namespace = @namespace if backup
94
+ @namespace
58
95
  end
59
96
 
60
97
  def in_namespace(namespace)
61
98
  # Temporarily change the namespace for convenience.
62
99
  begin
63
- old_namespace = self.namespace
64
- self.namespace = "#{old_namespace}#{namespace}"
100
+ old_namespace = self.namespace
101
+ self.namespace = "#{old_namespace}:#{namespace}"
65
102
  yield
66
103
  ensure
67
104
  self.namespace = old_namespace
@@ -69,19 +106,21 @@ class Memcache
69
106
  end
70
107
 
71
108
  def get(keys, opts = {})
72
- raise 'opts must be hash' unless opts.kind_of?(Hash)
109
+ raise 'opts must be hash' unless opts.instance_of?(Hash)
73
110
 
74
- if keys.kind_of?(Array)
111
+ if keys.instance_of?(Array)
112
+ keys.collect! {|key| key.to_s}
75
113
  multi_get(keys, opts)
76
114
  else
77
- key = cache_key(keys)
78
-
115
+ key = keys.to_s
79
116
  if opts[:expiry]
80
117
  value = server(key).gets(key)
81
- server(key).cas(key, value, value.memcache_cas, opts[:expiry]) if value
118
+ cas(key, value, :raw => true, :cas => value.memcache_cas, :expiry => opts[:expiry]) if value
82
119
  else
83
120
  value = server(key).get(key, opts[:cas])
84
121
  end
122
+
123
+ return backup.get(key, opts) if backup and value.nil?
85
124
  opts[:raw] ? value : unmarshal(value, key)
86
125
  end
87
126
  end
@@ -92,10 +131,11 @@ class Memcache
92
131
 
93
132
  def set(key, value, opts = {})
94
133
  opts = compatible_opts(opts)
134
+ key = key.to_s
135
+ backup.set(key, value, opts) if backup
95
136
 
96
137
  expiry = opts[:expiry] || default_expiry
97
138
  flags = opts[:flags] || 0
98
- key = cache_key(key)
99
139
  data = marshal(value, opts)
100
140
  server(key).set(key, data, expiry, flags)
101
141
  value
@@ -107,60 +147,67 @@ class Memcache
107
147
 
108
148
  def add(key, value, opts = {})
109
149
  opts = compatible_opts(opts)
150
+ key = key.to_s
151
+ backup.add(key, value, opts) if backup
110
152
 
111
153
  expiry = opts[:expiry] || default_expiry
112
154
  flags = opts[:flags] || 0
113
- key = cache_key(key)
114
155
  data = marshal(value, opts)
115
156
  server(key).add(key, data, expiry, flags) && value
116
157
  end
117
158
 
118
159
  def replace(key, value, opts = {})
119
160
  opts = compatible_opts(opts)
161
+ key = key.to_s
162
+ backup.replace(key, value, opts) if backup
120
163
 
121
164
  expiry = opts[:expiry] || default_expiry
122
165
  flags = opts[:flags] || 0
123
- key = cache_key(key)
124
166
  data = marshal(value, opts)
125
167
  server(key).replace(key, data, expiry, flags) && value
126
168
  end
127
169
 
128
- def cas(key, value, opts = {})
129
- raise 'opts must be hash' unless opts.kind_of?(Hash)
170
+ def cas(key, value, opts)
171
+ raise 'opts must be hash' unless opts.instance_of?(Hash)
172
+ key = key.to_s
173
+ backup.cas(key, value, opts) if backup
130
174
 
131
175
  expiry = opts[:expiry] || default_expiry
132
176
  flags = opts[:flags] || 0
133
- key = cache_key(key)
134
177
  data = marshal(value, opts)
135
178
  server(key).cas(key, data, opts[:cas], expiry, flags) && value
136
179
  end
137
180
 
138
181
  def append(key, value)
139
- key = cache_key(key)
182
+ key = key.to_s
183
+ backup.append(key, value) if backup
140
184
  server(key).append(key, value)
141
185
  end
142
186
 
143
187
  def prepend(key, value)
144
- key = cache_key(key)
188
+ key = key.to_s
189
+ backup.prepend(key, value) if backup
145
190
  server(key).prepend(key, value)
146
191
  end
147
192
 
148
193
  def count(key)
149
- key = cache_key(key)
150
- server(key).get(key).to_i
194
+ get(key, :raw => true).to_i
151
195
  end
152
196
 
153
197
  def incr(key, amount = 1)
154
- key = cache_key(key)
198
+ key = key.to_s
199
+ backup.incr(key, amount) if backup
155
200
  server(key).incr(key, amount)
156
201
  end
157
202
 
158
203
  def decr(key, amount = 1)
159
- key = cache_key(key)
204
+ key = key.to_s
205
+ backup.decr(key, amount) if backup
160
206
  server(key).decr(key, amount)
161
207
  end
162
208
 
163
209
  def update(key, opts = {})
210
+ key = key.to_s
164
211
  value = get(key, :cas => true)
165
212
  if value
166
213
  cas(key, yield(value), opts.merge!(:cas => value.memcache_cas))
@@ -171,6 +218,7 @@ class Memcache
171
218
 
172
219
  def get_or_add(key, *args)
173
220
  # Pseudo-atomic get and update.
221
+ key = key.to_s
174
222
  if block_given?
175
223
  opts = args[0] || {}
176
224
  get(key) || add(key, yield, opts) || get(key)
@@ -181,6 +229,7 @@ class Memcache
181
229
  end
182
230
 
183
231
  def get_or_set(key, *args)
232
+ key = key.to_s
184
233
  if block_given?
185
234
  opts = args[0] || {}
186
235
  get(key) || set(key, yield, opts)
@@ -192,8 +241,7 @@ class Memcache
192
241
 
193
242
  def get_some(keys, opts = {})
194
243
  keys = keys.collect {|key| key.to_s}
195
-
196
- records = opts[:disable] ? {} : self.get(keys, opts)
244
+ records = opts[:disable] ? {} : self.multi_get(keys, opts)
197
245
  if opts[:validation]
198
246
  records.delete_if do |key, value|
199
247
  not opts[:validation].call(key, value)
@@ -239,7 +287,8 @@ class Memcache
239
287
  end
240
288
 
241
289
  def delete(key)
242
- key = cache_key(key)
290
+ key = key.to_s
291
+ backup.delete(key) if backup
243
292
  server(key).delete(key)
244
293
  end
245
294
 
@@ -270,7 +319,7 @@ class Memcache
270
319
  stats
271
320
  end
272
321
  end
273
-
322
+
274
323
  alias clear flush_all
275
324
 
276
325
  def [](key)
@@ -307,50 +356,49 @@ protected
307
356
 
308
357
  def compatible_opts(opts)
309
358
  # Support passing expiry instead of opts. This may be deprecated in the future.
310
- opts.kind_of?(Hash) ? opts : {:expiry => opts}
359
+ opts.instance_of?(Hash) ? opts : {:expiry => opts}
311
360
  end
312
361
 
313
362
  def multi_get(keys, opts = {})
314
363
  return {} if keys.empty?
315
-
316
- key_to_input_key = {}
317
- keys_by_server = Hash.new { |h,k| h[k] = [] }
318
-
319
- # Store keys by servers. Also store a mapping from cache key to input key.
320
- keys.each do |input_key|
321
- key = cache_key(input_key)
322
- server = server(key)
323
- key_to_input_key[key] = input_key.to_s
324
- keys_by_server[server] << key
325
- end
326
-
327
- # Fetch and combine the results. Also, map the cache keys back to the input keys.
364
+
328
365
  results = {}
329
- keys_by_server.each do |server, keys|
366
+ fetch_results = lambda do |server, keys|
330
367
  server.get(keys, opts[:cas]).each do |key, value|
331
- input_key = key_to_input_key[key]
332
- results[input_key] = opts[:raw] ? value : unmarshal(value, key)
368
+ results[key] = opts[:raw] ? value : unmarshal(value, key)
333
369
  end
334
370
  end
335
- results
336
- end
337
371
 
338
- def cache_key(key)
339
- safe_key = key ? key.to_s.gsub(/%/, '%%').gsub(/ /, '%s') : key
340
- if namespace.nil? then
341
- safe_key
372
+ if @server
373
+ fetch_results.call(@server, keys)
342
374
  else
343
- "#{namespace}:#{safe_key}"
375
+ keys_by_server = Hash.new { |h,k| h[k] = [] }
376
+
377
+ # Store keys by servers.
378
+ keys.each do |key|
379
+ keys_by_server[server(key)] << key
380
+ end
381
+
382
+ # Fetch and combine the results.
383
+ keys_by_server.each do |server, server_keys|
384
+ fetch_results.call(server, server_keys)
385
+ end
344
386
  end
387
+
388
+ if backup
389
+ missing_keys = keys - results.keys
390
+ results.merge!(backup.get(missing_keys, opts)) if missing_keys.any?
391
+ end
392
+ results
345
393
  end
346
-
394
+
347
395
  def marshal(value, opts = {})
348
396
  opts[:raw] ? value : Marshal.dump(value)
349
397
  end
350
398
 
351
399
  def unmarshal(value, key = nil)
352
400
  return value if value.nil?
353
-
401
+
354
402
  object = Marshal.load(value)
355
403
  object.memcache_flags = value.memcache_flags
356
404
  object.memcache_cas = value.memcache_cas
@@ -361,9 +409,9 @@ protected
361
409
  end
362
410
 
363
411
  def server(key)
364
- raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
365
- return servers.first if servers.length == 1
412
+ return @server if @server
366
413
 
414
+ key = "#{namespace}:#{key}" if @hash_with_prefix
367
415
  hash = (Zlib.crc32(key) >> 16) & 0x7fff
368
416
  servers[hash % servers.length]
369
417
  end
@@ -376,7 +424,7 @@ protected
376
424
  @cache_by_scope[:default] = Memcache.new(:server => Memcache::LocalServer)
377
425
  @fallback = :default
378
426
  end
379
-
427
+
380
428
  def include?(scope)
381
429
  @cache_by_scope.include?(scope.to_sym)
382
430
  end
@@ -388,7 +436,7 @@ protected
388
436
  def [](scope)
389
437
  @cache_by_scope[scope.to_sym] || @cache_by_scope[fallback]
390
438
  end
391
-
439
+
392
440
  def []=(scope, cache)
393
441
  @cache_by_scope[scope.to_sym] = cache
394
442
  end
@@ -397,7 +445,7 @@ protected
397
445
  @cache_by_scope.values.each {|c| c.reset}
398
446
  end
399
447
  end
400
-
448
+
401
449
  def self.pool
402
450
  @@cache_pool ||= Pool.new
403
451
  end
@@ -1,9 +1,9 @@
1
1
  require 'test/unit'
2
- require File.dirname(__FILE__) + '/../lib/memcache/local_server'
3
2
  require File.dirname(__FILE__) + '/memcache_server_test_helper'
4
3
 
5
4
  class MemcacheLocalServerTest < Test::Unit::TestCase
6
5
  include MemcacheServerTestHelper
6
+ with_prefixes nil, "foo:", "bar:"
7
7
 
8
8
  def setup
9
9
  @memcache = Memcache::LocalServer.new
@@ -0,0 +1,31 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/memcache_server_test_helper'
3
+
4
+ class MemcacheNativeServerTest < Test::Unit::TestCase
5
+ include MemcacheServerTestHelper
6
+ include MemcacheServerTestHelper::AdvancedMethods
7
+ with_prefixes nil, "foo:", "bar:"
8
+
9
+ PORTS = [11212, 11213, 11214]
10
+ def setup
11
+ init_memcache(*PORTS) do
12
+ Memcache::NativeServer.new(:servers => PORTS.collect {|p| "localhost:#{p}"})
13
+ end
14
+ end
15
+
16
+ def test_server_down
17
+ m = Memcache::NativeServer.new(:servers => ["localhost:9998"])
18
+
19
+ assert_equal nil, m.get('foo')
20
+ assert_raise(Memcache::Error) do
21
+ m.set('foo', 'foo')
22
+ end
23
+ end
24
+
25
+ def test_close
26
+ m.close
27
+
28
+ m.set('foo', 'foo')
29
+ assert_equal 'foo', m.get('foo')
30
+ end
31
+ end
@@ -12,7 +12,7 @@ class MemcacheNullServerTest < Test::Unit::TestCase
12
12
 
13
13
  def test_set_and_get
14
14
  m.set(2, 'foo', 0)
15
-
15
+
16
16
  assert_equal nil, m.get('2')
17
17
  assert_equal nil, m.get('2')
18
18
  end
@@ -34,32 +34,40 @@ class MemcacheNullServerTest < Test::Unit::TestCase
34
34
  def test_multi_get
35
35
  m.set(2, '1,2,3')
36
36
  m.set(3, '4,5')
37
-
37
+
38
38
  assert_equal Hash.new, m.get([2,3])
39
39
  end
40
-
40
+
41
41
  def test_delete
42
42
  m.set(2, '1,2,3')
43
-
43
+
44
44
  assert_equal nil, m.get(2)
45
45
 
46
46
  m.delete(2)
47
-
47
+
48
48
  assert_equal nil, m.get(2)
49
49
  end
50
50
 
51
51
  def test_flush_all
52
52
  m.set(2, 'bar')
53
-
53
+
54
54
  assert_equal nil, m.get(2)
55
55
 
56
56
  m.flush_all
57
-
57
+
58
58
  assert_equal nil, m.get(2)
59
59
  end
60
-
60
+
61
61
  def test_expiry
62
62
  m.add('test', '1', 1)
63
63
  assert_equal nil, m.get('test')
64
64
  end
65
+
66
+ def test_prefix
67
+ assert_equal "foo", m.prefix = "foo"
68
+ assert_equal "foo", m.prefix
69
+
70
+ assert_equal nil, m.prefix = nil
71
+ assert_equal nil, m.prefix
72
+ end
65
73
  end
@@ -0,0 +1,18 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/memcache_server_test_helper'
3
+
4
+ $VERBOSE = nil
5
+ Memcache::Segmented.const_set('MAX_SIZE', 3)
6
+
7
+ class MemcacheSegmentedNativeServerTest < Test::Unit::TestCase
8
+ include MemcacheServerTestHelper
9
+ include MemcacheServerTestHelper::AdvancedMethods
10
+ with_prefixes nil, "foo:", "bar:"
11
+
12
+ PORTS = [11212, 11213, 11214]
13
+ def setup
14
+ init_memcache(*PORTS) do
15
+ Memcache::SegmentedNativeServer.new(:servers => PORTS.collect {|p| "localhost:#{p}"})
16
+ end
17
+ end
18
+ end
@@ -2,20 +2,17 @@ require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/memcache_server_test_helper'
3
3
 
4
4
  $VERBOSE = nil
5
- Memcache::SegmentedServer.const_set('MAX_SIZE', 3)
5
+ Memcache::Segmented.const_set('MAX_SIZE', 3)
6
6
 
7
7
  class MemcacheSegmentedServerTest < Test::Unit::TestCase
8
8
  include MemcacheServerTestHelper
9
9
  include MemcacheServerTestHelper::AdvancedMethods
10
- PORT = 11212
10
+ with_prefixes nil, "foo:", "bar:"
11
11
 
12
+ PORT = 11212
12
13
  def setup
13
- start_memcache(PORT)
14
- @memcache = Memcache::SegmentedServer.new(:host => 'localhost', :port => PORT)
15
-
16
- end
17
-
18
- def teardown
19
- stop_memcache(PORT)
14
+ init_memcache(PORT) do
15
+ Memcache::SegmentedServer.new(:host => 'localhost', :port => PORT)
16
+ end
20
17
  end
21
18
  end
@@ -4,15 +4,13 @@ require File.dirname(__FILE__) + '/memcache_server_test_helper'
4
4
  class MemcacheServerTest < Test::Unit::TestCase
5
5
  include MemcacheServerTestHelper
6
6
  include MemcacheServerTestHelper::AdvancedMethods
7
- PORT = 11212
7
+ with_prefixes nil, "foo:", "bar:"
8
8
 
9
+ PORT = 11212
9
10
  def setup
10
- start_memcache(PORT)
11
- @memcache = Memcache::Server.new(:host => 'localhost', :port => PORT)
12
- end
13
-
14
- def teardown
15
- stop_memcache(PORT)
11
+ init_memcache(PORT) do
12
+ Memcache::Server.new(:host => 'localhost', :port => PORT)
13
+ end
16
14
  end
17
15
 
18
16
  def test_stats
@@ -21,11 +19,11 @@ class MemcacheServerTest < Test::Unit::TestCase
21
19
  m.get('bar')
22
20
 
23
21
  stats = m.stats
24
- assert_equal 2, stats['cmd_get']
25
- assert_equal 1, stats['cmd_set']
26
- assert_equal 1, stats['curr_items']
22
+ assert stats['cmd_get'] > 0
23
+ assert stats['cmd_set'] > 0
24
+ assert stats['curr_items'] > 0
27
25
  end
28
-
26
+
29
27
  def test_clone
30
28
  m.set('foo', 1)
31
29
  c = m.clone