fiveruns-memcache-client 1.5.0.3 → 1.5.0.4

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/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: