fiveruns-memcache-client 1.5.0.3 → 1.5.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ = 1.5.0.4
2
+
3
+ * Get test suite working again (packagethief)
4
+ * Ruby 1.9 compatiblity fixes (packagethief, mperham)
5
+ * Consistently return server responses and check for errors (packagethief)
6
+ * Properly calculate CRC in Ruby 1.9 strings (mperham)
7
+ * Drop rspec in favor of test/unit, for 1.9 compat (mperham)
8
+
1
9
  = 1.5.0.3 (FiveRuns fork)
2
10
 
3
11
  * Integrated ITU-T CRC32 operation in native C extension for speed. Thanks to Justin Balthrop!
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  # vim: syntax=Ruby
2
2
  require 'rubygems'
3
3
  require 'rake/rdoctask'
4
- require 'spec/rake/spectask'
4
+ require 'rake/testtask'
5
5
 
6
6
  task :gem do
7
7
  sh "gem build memcache-client.gemspec"
@@ -11,14 +11,12 @@ task :install => [:gem] do
11
11
  sh "sudo gem install memcache-client-*.gem"
12
12
  end
13
13
 
14
- Spec::Rake::SpecTask.new do |t|
15
- t.ruby_opts = ['-rtest/unit']
16
- t.spec_files = FileList['test/test_*.rb']
17
- t.fail_on_error = true
18
- end
19
-
20
14
  Rake::RDocTask.new do |rd|
21
15
  rd.main = "README.rdoc"
22
16
  rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
23
17
  rd.rdoc_dir = 'doc'
24
18
  end
19
+
20
+ Rake::TestTask.new
21
+
22
+ task :default => :test
data/ext/crc32/crc32.c CHANGED
@@ -3,8 +3,18 @@
3
3
 
4
4
  static VALUE t_itu_t(VALUE self, VALUE string) {
5
5
  VALUE str = StringValue(string);
6
+ #ifdef RSTRING_LEN
7
+ int n = RSTRING_LEN(str);
8
+ #else
6
9
  int n = RSTRING(str)->len;
10
+ #endif
11
+
12
+ #ifdef RSTRING_PTR
13
+ char* p = RSTRING_PTR(str);
14
+ #else
7
15
  char* p = RSTRING(str)->ptr;
16
+ #endif
17
+
8
18
  unsigned long r = 0xFFFFFFFF;
9
19
  int i, j;
10
20
 
data/lib/memcache.rb CHANGED
@@ -18,11 +18,10 @@ class String
18
18
  puts "Loading with slow CRC32 ITU-T implementation: #{e.message}"
19
19
 
20
20
  def crc32_ITU_T
21
- n = length
22
21
  r = 0xFFFFFFFF
23
22
 
24
- n.times do |i|
25
- r ^= self[i]
23
+ each_byte do |i|
24
+ r ^= i
26
25
  8.times do
27
26
  if (r & 1) != 0 then
28
27
  r = (r>>1) ^ 0xEDB88320
@@ -100,7 +99,7 @@ class MemCache
100
99
  # Valid options for +opts+ are:
101
100
  #
102
101
  # [:namespace] Prepends this value to all keys added or retrieved.
103
- # [:readonly] Raises an exeception on cache writes when true.
102
+ # [:readonly] Raises an exception on cache writes when true.
104
103
  # [:multithread] Wraps cache access in a Mutex for thread safety.
105
104
  #
106
105
  # Other options are ignored.
@@ -163,7 +162,7 @@ class MemCache
163
162
 
164
163
  def servers=(servers)
165
164
  # Create the server objects.
166
- @servers = servers.collect do |server|
165
+ @servers = Array(servers).collect do |server|
167
166
  case server
168
167
  when String
169
168
  host, port, weight = server.split ':', 3
@@ -198,7 +197,7 @@ class MemCache
198
197
  cache_decr server, cache_key, amount
199
198
  end
200
199
  rescue TypeError => err
201
- handle_error server, err
200
+ handle_error nil, err
202
201
  end
203
202
 
204
203
  ##
@@ -213,7 +212,7 @@ class MemCache
213
212
  return value
214
213
  end
215
214
  rescue TypeError => err
216
- handle_error server, err
215
+ handle_error nil, err
217
216
  end
218
217
 
219
218
  ##
@@ -249,17 +248,17 @@ class MemCache
249
248
 
250
249
  results = {}
251
250
 
252
- server_keys.each do |server, keys|
253
- keys = keys.join ' '
254
- values = cache_get_multi server, keys
251
+ server_keys.each do |server, keys_for_server|
252
+ keys_for_server = keys_for_server.join ' '
253
+ values = cache_get_multi server, keys_for_server
255
254
  values.each do |key, value|
256
255
  results[cache_keys[key]] = Marshal.load value
257
256
  end
258
257
  end
259
258
 
260
259
  return results
261
- rescue TypeError => err
262
- handle_error server, err
260
+ rescue TypeError, IndexError => err
261
+ handle_error nil, err
263
262
  end
264
263
 
265
264
  ##
@@ -273,9 +272,9 @@ class MemCache
273
272
  cache_incr server, cache_key, amount
274
273
  end
275
274
  rescue TypeError => err
276
- handle_error server, err
275
+ handle_error nil, err
277
276
  end
278
-
277
+
279
278
  ##
280
279
  # Add +key+ to the cache with value +value+ that expires in +expiry+
281
280
  # seconds. If +raw+ is true, +value+ will not be Marshalled.
@@ -293,15 +292,14 @@ class MemCache
293
292
  with_socket_management(server) do |socket|
294
293
  socket.write command
295
294
  result = socket.gets
296
- if result.nil?
295
+ raise_on_error_response! result
296
+
297
+ if result.nil?
297
298
  server.close
298
299
  raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
299
300
  end
300
301
 
301
- if result =~ /^SERVER_ERROR (.*)/
302
- server.close
303
- raise MemCacheError, $1.strip
304
- end
302
+ result
305
303
  end
306
304
  end
307
305
  end
@@ -322,21 +320,25 @@ class MemCache
322
320
 
323
321
  with_socket_management(server) do |socket|
324
322
  socket.write command
325
- socket.gets
323
+ result = socket.gets
324
+ raise_on_error_response! result
325
+ result
326
326
  end
327
327
  end
328
328
  end
329
-
329
+
330
330
  ##
331
331
  # Removes +key+ from the cache in +expiry+ seconds.
332
332
 
333
333
  def delete(key, expiry = 0)
334
334
  raise MemCacheError, "Update of readonly cache" if @readonly
335
- server, cache_key = request_setup key
336
-
337
- with_socket_management(server) do |socket|
338
- socket.write "delete #{cache_key} #{expiry}\r\n"
339
- socket.gets
335
+ with_server(key) do |server, cache_key|
336
+ with_socket_management(server) do |socket|
337
+ socket.write "delete #{cache_key} #{expiry}\r\n"
338
+ result = socket.gets
339
+ raise_on_error_response! result
340
+ result
341
+ end
340
342
  end
341
343
  end
342
344
 
@@ -346,15 +348,19 @@ class MemCache
346
348
  def flush_all
347
349
  raise MemCacheError, 'No active servers' unless active?
348
350
  raise MemCacheError, "Update of readonly cache" if @readonly
351
+
349
352
  begin
350
353
  @mutex.lock if @multithread
351
354
  @servers.each do |server|
352
355
  with_socket_management(server) do |socket|
353
356
  socket.write "flush_all\r\n"
354
357
  result = socket.gets
355
- raise MemCacheError, $2.strip if result =~ /^(SERVER_)?ERROR(.*)/
358
+ raise_on_error_response! result
359
+ result
356
360
  end
357
361
  end
362
+ rescue IndexError => err
363
+ handle_error nil, err
358
364
  ensure
359
365
  @mutex.unlock if @multithread
360
366
  end
@@ -407,13 +413,15 @@ class MemCache
407
413
 
408
414
  @servers.each do |server|
409
415
  next unless server.alive?
416
+
410
417
  with_socket_management(server) do |socket|
411
- value = nil # TODO: why is this line here?
418
+ value = nil
412
419
  socket.write "stats\r\n"
413
420
  stats = {}
414
421
  while line = socket.gets do
422
+ raise_on_error_response! line
415
423
  break if line == "END\r\n"
416
- if line =~ /^STAT ([\w]+) ([\w\.\:]+)/ then
424
+ if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then
417
425
  name, value = $1, $2
418
426
  stats[name] = case name
419
427
  when 'version'
@@ -423,7 +431,7 @@ class MemCache
423
431
  microseconds ||= 0
424
432
  Float(seconds) + (Float(microseconds) / 1_000_000)
425
433
  else
426
- if value =~ /^\d+$/ then
434
+ if value =~ /\A\d+\Z/ then
427
435
  value.to_i
428
436
  else
429
437
  value
@@ -435,6 +443,7 @@ class MemCache
435
443
  end
436
444
  end
437
445
 
446
+ raise MemCacheError, "No active servers" if server_stats.empty?
438
447
  server_stats
439
448
  end
440
449
 
@@ -478,7 +487,7 @@ class MemCache
478
487
  hkey = hash_for key
479
488
 
480
489
  20.times do |try|
481
- server = @buckets[hkey % @buckets.nitems]
490
+ server = @buckets[hkey % @buckets.compact.size]
482
491
  return server if server.alive?
483
492
  hkey += hash_for "#{try}#{key}"
484
493
  end
@@ -502,6 +511,7 @@ class MemCache
502
511
  with_socket_management(server) do |socket|
503
512
  socket.write "decr #{cache_key} #{amount}\r\n"
504
513
  text = socket.gets
514
+ raise_on_error_response! text
505
515
  return nil if text == "NOT_FOUND\r\n"
506
516
  return text.to_i
507
517
  end
@@ -518,9 +528,10 @@ class MemCache
518
528
 
519
529
  if keyline.nil? then
520
530
  server.close
521
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
531
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
522
532
  end
523
533
 
534
+ raise_on_error_response! keyline
524
535
  return nil if keyline == "END\r\n"
525
536
 
526
537
  unless keyline =~ /(\d+)\r/ then
@@ -544,8 +555,9 @@ class MemCache
544
555
 
545
556
  while keyline = socket.gets do
546
557
  return values if keyline == "END\r\n"
558
+ raise_on_error_response! keyline
547
559
 
548
- unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
560
+ unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then
549
561
  server.close
550
562
  raise MemCacheError, "unexpected response #{keyline.inspect}"
551
563
  end
@@ -568,31 +580,36 @@ class MemCache
568
580
  with_socket_management(server) do |socket|
569
581
  socket.write "incr #{cache_key} #{amount}\r\n"
570
582
  text = socket.gets
583
+ raise_on_error_response! text
571
584
  return nil if text == "NOT_FOUND\r\n"
572
585
  return text.to_i
573
586
  end
574
587
  end
575
-
588
+
576
589
  ##
577
590
  # Gets or creates a socket connected to the given server, and yields it
578
- # to the block. If a socket error (SocketError, SystemCallError, IOError)
579
- # or protocol error (MemCacheError) is raised by the block, closes the
580
- # socket, attempts to connect again, and retries the block (once). If
581
- # an error is again raised, reraises it as MemCacheError.
591
+ # to the block, wrapped in a mutex synchronization if @multithread is true.
592
+ #
593
+ # If a socket error (SocketError, SystemCallError, IOError) or protocol error
594
+ # (MemCacheError) is raised by the block, closes the socket, attempts to
595
+ # connect again, and retries the block (once). If an error is again raised,
596
+ # reraises it as MemCacheError.
597
+ #
582
598
  # If unable to connect to the server (or if in the reconnect wait period),
583
- # raises MemCacheError - note that the socket connect code marks a server
599
+ # raises MemCacheError. Note that the socket connect code marks a server
584
600
  # dead for a timeout period, so retrying does not apply to connection attempt
585
- # failures (but does still apply to unexpectedly lost connections etc.).
586
- # Wraps the whole lot in mutex synchronization if @multithread is true.
601
+ # failures (but does still apply to unexpectedly lost connections etc.).
587
602
 
588
603
  def with_socket_management(server, &block)
589
604
  @mutex.lock if @multithread
590
605
  retried = false
591
606
  begin
592
607
  socket = server.socket
593
- # Raise an IndexError to show this server is out of whack.
594
- # We'll catch it in higher-level code and attempt to restart the operation.
608
+
609
+ # Raise an IndexError to show this server is out of whack. If were inside
610
+ # a with_server block, we'll catch it and attempt to restart the operation.
595
611
  raise IndexError, "No connection to server (#{server.status})" if socket.nil?
612
+
596
613
  block.call(socket)
597
614
  rescue MemCacheError, SocketError, SystemCallError, IOError => err
598
615
  handle_error(server, err) if retried || socket.nil?
@@ -610,7 +627,7 @@ class MemCache
610
627
  yield server, cache_key
611
628
  rescue IndexError => e
612
629
  if !retried && @servers.size > 1
613
- puts "Connection to server #{server.inspect} DIED! Retrying operation..."
630
+ puts "Connection to server #{server.inspect} DIED! Retrying operation..."
614
631
  retried = true
615
632
  retry
616
633
  end
@@ -640,6 +657,12 @@ class MemCache
640
657
  return server, cache_key
641
658
  end
642
659
 
660
+ def raise_on_error_response!(response)
661
+ if response =~ /\A(?:CLIENT_|SERVER_)?ERROR(.*)/
662
+ raise MemCacheError, $1.strip
663
+ end
664
+ end
665
+
643
666
  ##
644
667
  # This class represents a memcached server instance.
645
668
 
@@ -1,11 +1,10 @@
1
+ # encoding: utf-8
1
2
  require 'stringio'
2
3
  require 'test/unit'
3
- require 'rubygems'
4
- require 'test/zentest_assertions'
5
4
 
6
5
  $TESTING = true
7
6
 
8
- require 'memcache'
7
+ require File.dirname(__FILE__) + '/../lib/memcache'
9
8
 
10
9
  class MemCache
11
10
 
@@ -86,7 +85,9 @@ class TestMemCache < Test::Unit::TestCase
86
85
 
87
86
  def test_cache_get_bad_state
88
87
  server = FakeServer.new
89
- server.socket.data.write "bogus response\r\n"
88
+
89
+ # Write two messages to the socket to test failover
90
+ server.socket.data.write "bogus response\r\nbogus response\r\n"
90
91
  server.socket.data.rewind
91
92
 
92
93
  @cache.servers = []
@@ -96,12 +97,11 @@ class TestMemCache < Test::Unit::TestCase
96
97
  @cache.cache_get(server, 'my_namespace:key')
97
98
  end
98
99
 
99
- assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
100
+ assert_match /#{Regexp.quote 'unexpected response "bogus response\r\n"'}/, e.message
100
101
 
101
- deny server.alive?
102
+ assert !server.alive?
102
103
 
103
- assert_equal "get my_namespace:key\r\n",
104
- server.socket.written.string
104
+ assert_match /get my_namespace:key\r\n/, server.socket.written.string
105
105
  end
106
106
 
107
107
  def test_cache_get_miss
@@ -145,7 +145,9 @@ class TestMemCache < Test::Unit::TestCase
145
145
 
146
146
  def test_cache_get_multi_bad_state
147
147
  server = FakeServer.new
148
- server.socket.data.write "bogus response\r\n"
148
+
149
+ # Write two messages to the socket to test failover
150
+ server.socket.data.write "bogus response\r\nbogus response\r\n"
149
151
  server.socket.data.rewind
150
152
 
151
153
  @cache.servers = []
@@ -155,17 +157,22 @@ class TestMemCache < Test::Unit::TestCase
155
157
  @cache.cache_get_multi server, 'my_namespace:key'
156
158
  end
157
159
 
158
- assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
160
+ assert_match /#{Regexp.quote 'unexpected response "bogus response\r\n"'}/, e.message
159
161
 
160
- deny server.alive?
162
+ assert !server.alive?
161
163
 
162
- assert_equal "get my_namespace:key\r\n",
163
- server.socket.written.string
164
+ assert_match /get my_namespace:key\r\n/, server.socket.written.string
164
165
  end
165
166
 
166
167
  def test_crc32_ITU_T
167
168
  assert_equal 0, ''.crc32_ITU_T
168
- assert_equal 1260851911, 'my_namespace:key'.crc32_ITU_T
169
+ # First value is the fast C version, last value is the pure Ruby version
170
+ assert_in [-886631737, 1260851911], 'my_namespace:key'.crc32_ITU_T
171
+ assert_in [-224284233, 870540390], 'my_name√space:key'.crc32_ITU_T
172
+ end
173
+
174
+ def assert_in(possible_values, value)
175
+ assert possible_values.include?(value), "#{possible_values.inspect} should contain #{value}"
169
176
  end
170
177
 
171
178
  def test_initialize
@@ -216,7 +223,7 @@ class TestMemCache < Test::Unit::TestCase
216
223
  assert_equal 'my_namespace', cache.namespace
217
224
  assert_equal true, cache.readonly?
218
225
  assert_equal false, cache.servers.empty?
219
- deny_empty cache.instance_variable_get(:@buckets)
226
+ assert !cache.instance_variable_get(:@buckets).empty?
220
227
  end
221
228
 
222
229
  def test_initialize_too_many_args
@@ -503,7 +510,9 @@ class TestMemCache < Test::Unit::TestCase
503
510
 
504
511
  @cache.set 'key', 'value'
505
512
 
506
- expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
513
+ dumped = Marshal.dump('value')
514
+ expected = "set my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
515
+ # expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
507
516
  assert_equal expected, server.socket.written.string
508
517
  end
509
518
 
@@ -516,7 +525,8 @@ class TestMemCache < Test::Unit::TestCase
516
525
 
517
526
  @cache.set 'key', 'value', 5
518
527
 
519
- expected = "set my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
528
+ dumped = Marshal.dump('value')
529
+ expected = "set my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
520
530
  assert_equal expected, server.socket.written.string
521
531
  end
522
532
 
@@ -545,8 +555,11 @@ class TestMemCache < Test::Unit::TestCase
545
555
 
546
556
  def test_set_too_big
547
557
  server = FakeServer.new
548
- server.socket.data.write "SERVER_ERROR object too large for cache\r\n"
558
+
559
+ # Write two messages to the socket to test failover
560
+ server.socket.data.write "SERVER_ERROR\r\nSERVER_ERROR object too large for cache\r\n"
549
561
  server.socket.data.rewind
562
+
550
563
  @cache.servers = []
551
564
  @cache.servers << server
552
565
 
@@ -554,7 +567,7 @@ class TestMemCache < Test::Unit::TestCase
554
567
  @cache.set 'key', 'v'
555
568
  end
556
569
 
557
- assert_equal 'object too large for cache', e.message
570
+ assert_match /object too large for cache/, e.message
558
571
  end
559
572
 
560
573
  def test_add
@@ -565,8 +578,10 @@ class TestMemCache < Test::Unit::TestCase
565
578
  @cache.servers << server
566
579
 
567
580
  @cache.add 'key', 'value'
581
+
582
+ dumped = Marshal.dump('value')
568
583
 
569
- expected = "add my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
584
+ expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
570
585
  assert_equal expected, server.socket.written.string
571
586
  end
572
587
 
@@ -579,7 +594,8 @@ class TestMemCache < Test::Unit::TestCase
579
594
 
580
595
  @cache.add 'key', 'value'
581
596
 
582
- expected = "add my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
597
+ dumped = Marshal.dump('value')
598
+ expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
583
599
  assert_equal expected, server.socket.written.string
584
600
  end
585
601
 
@@ -592,7 +608,8 @@ class TestMemCache < Test::Unit::TestCase
592
608
 
593
609
  @cache.add 'key', 'value', 5
594
610
 
595
- expected = "add my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
611
+ dumped = Marshal.dump('value')
612
+ expected = "add my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
596
613
  assert_equal expected, server.socket.written.string
597
614
  end
598
615
 
@@ -655,11 +672,12 @@ class TestMemCache < Test::Unit::TestCase
655
672
 
656
673
  def test_flush_all_failure
657
674
  socket = FakeSocket.new
658
- socket.data.write "ERROR\r\n"
675
+
676
+ # Write two messages to the socket to test failover
677
+ socket.data.write "ERROR\r\nERROR\r\n"
659
678
  socket.data.rewind
679
+
660
680
  server = FakeServer.new socket
661
- def server.host() "localhost"; end
662
- def server.port() 11211; end
663
681
 
664
682
  @cache.servers = []
665
683
  @cache.servers << server
@@ -668,7 +686,7 @@ class TestMemCache < Test::Unit::TestCase
668
686
  @cache.flush_all
669
687
  end
670
688
 
671
- assert_equal "flush_all\r\n", socket.written.string
689
+ assert_match /flush_all\r\n/, socket.written.string
672
690
  end
673
691
 
674
692
  def test_stats
@@ -697,24 +715,44 @@ class TestMemCache < Test::Unit::TestCase
697
715
  cache = MemCache.new :multithread => true,
698
716
  :namespace => 'my_namespace',
699
717
  :readonly => false
700
- server = util_setup_server cache, 'example.com', "OK\r\n"
701
- cache.instance_variable_set :@servers, [server]
718
+
719
+ server = FakeServer.new
720
+ server.socket.data.write "STORED\r\n"
721
+ server.socket.data.rewind
722
+
723
+ cache.servers = []
724
+ cache.servers << server
725
+
726
+ assert cache.multithread
702
727
 
703
728
  assert_nothing_raised do
704
729
  cache.set "test", "test value"
705
730
  end
731
+
732
+ # TODO Fails in 1.9
733
+ assert_match /set my_namespace:test.*\r\n.*test value.*\r\n/, server.socket.written.string
706
734
  end
707
735
 
708
736
  def test_basic_unthreaded_operations_should_work
709
737
  cache = MemCache.new :multithread => false,
710
738
  :namespace => 'my_namespace',
711
739
  :readonly => false
712
- server = util_setup_server cache, 'example.com', "OK\r\n"
713
- cache.instance_variable_set :@servers, [server]
740
+
741
+ server = FakeServer.new
742
+ server.socket.data.write "STORED\r\n"
743
+ server.socket.data.rewind
744
+
745
+ cache.servers = []
746
+ cache.servers << server
747
+
748
+ assert !cache.multithread
714
749
 
715
750
  assert_nothing_raised do
716
751
  cache.set "test", "test value"
717
752
  end
753
+
754
+ # TODO Fails in 1.9
755
+ assert_match /set my_namespace:test.*\r\n.*test value\r\n/, server.socket.written.string
718
756
  end
719
757
 
720
758
  def util_setup_fake_server
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiveruns-memcache-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0.3
4
+ version: 1.5.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hodel
@@ -11,12 +11,12 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2008-07-06 00:00:00 -07:00
14
+ date: 2008-10-27 00:00:00 -07:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
18
18
  description: A Ruby-based memcached client library
19
- email: mike@fiveruns.com
19
+ email: mperham@gmail.com
20
20
  executables: []
21
21
 
22
22
  extensions: