fiveruns-memcache-client 1.5.0.1 → 1.5.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,15 @@
1
+ = 1.5.0.3 (FiveRuns fork)
2
+
3
+ * Integrated ITU-T CRC32 operation in native C extension for speed. Thanks to Justin Balthrop!
4
+
5
+ = 1.5.0.2 (FiveRuns fork)
6
+
7
+ * Add support for seamless failover between servers. If one server connection dies,
8
+ the client will retry the operation on another server before giving up.
9
+
10
+ * Merge Will Bryant's socket retry patch.
11
+ http://willbryant.net/software/2007/12/21/ruby-memcache-client-reconnect-and-retry
12
+
1
13
  = 1.5.0.1 (FiveRuns fork)
2
14
 
3
15
  * Fix set not handling client disconnects.
data/README.txt CHANGED
@@ -1,26 +1,21 @@
1
1
  = memcache-client
2
2
 
3
+ This is the FiveRuns fork of seattle.rb's memcache-client 1.5.0. We've fixed several bugs
4
+ which are in that version.
5
+
3
6
  Rubyforge Project:
4
7
 
5
8
  http://rubyforge.org/projects/seattlerb
6
9
 
7
- File bugs:
8
-
9
- http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
10
-
11
10
  Documentation:
12
11
 
13
12
  http://seattlerb.org/memcache-client
14
13
 
15
- == About
16
-
17
- memcache-client is a client for Danga Interactive's memcached.
18
-
19
14
  == Installing memcache-client
20
15
 
21
16
  Just install the gem:
22
17
 
23
- $ sudo gem install memcache-client
18
+ $ sudo gem install fiveruns-memcache-client --source http://gems.github.com
24
19
 
25
20
  == Using memcache-client
26
21
 
@@ -52,3 +47,8 @@ use with Rails. To use it be sure to assign your memcache connection to
52
47
  CACHE. Cache returns nil on all memcache errors so you don't have to rescue
53
48
  the errors yourself. It has #get, #put and #delete module functions.
54
49
 
50
+ === Improving Performance ===
51
+
52
+ Performing the CRC-32 ITU-T step to determine which server to use for a given key
53
+ is VERY slow in Ruby. RubyGems should compile a native library for performing this
54
+ operation when the gem is installed.
data/ext/crc32/crc32.c ADDED
@@ -0,0 +1,28 @@
1
+ #include "ruby.h"
2
+ #include "stdio.h"
3
+
4
+ static VALUE t_itu_t(VALUE self, VALUE string) {
5
+ VALUE str = StringValue(string);
6
+ int n = RSTRING(str)->len;
7
+ char* p = RSTRING(str)->ptr;
8
+ unsigned long r = 0xFFFFFFFF;
9
+ int i, j;
10
+
11
+ for (i = 0; i < n; i++) {
12
+ r = r ^ p[i];
13
+ for (j = 0; j < 8; j++) {
14
+ if ( (r & 1) != 0 ) {
15
+ r = (r >> 1) ^ 0xEDB88320;
16
+ } else {
17
+ r = r >> 1;
18
+ }
19
+ }
20
+ }
21
+ return INT2FIX(r ^ 0xFFFFFFFF);
22
+ }
23
+
24
+ VALUE cCRC32;
25
+ void Init_crc32() {
26
+ cCRC32 = rb_define_module("CRC32");
27
+ rb_define_module_function(cCRC32, "itu_t", t_itu_t, 1);
28
+ }
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("crc32")
4
+
5
+ create_makefile("crc32")
data/lib/memcache.rb CHANGED
@@ -9,25 +9,33 @@ class String
9
9
 
10
10
  ##
11
11
  # Uses the ITU-T polynomial in the CRC32 algorithm.
12
-
13
- def crc32_ITU_T
14
- n = length
15
- r = 0xFFFFFFFF
16
-
17
- n.times do |i|
18
- r ^= self[i]
19
- 8.times do
20
- if (r & 1) != 0 then
21
- r = (r>>1) ^ 0xEDB88320
22
- else
23
- r >>= 1
12
+ begin
13
+ require 'crc32'
14
+ def crc32_ITU_T
15
+ CRC32.itu_t(self)
16
+ end
17
+ rescue LoadError => e
18
+ puts "Loading with slow CRC32 ITU-T implementation: #{e.message}"
19
+
20
+ def crc32_ITU_T
21
+ n = length
22
+ r = 0xFFFFFFFF
23
+
24
+ n.times do |i|
25
+ r ^= self[i]
26
+ 8.times do
27
+ if (r & 1) != 0 then
28
+ r = (r>>1) ^ 0xEDB88320
29
+ else
30
+ r >>= 1
31
+ end
24
32
  end
25
33
  end
26
- end
27
34
 
28
- r ^ 0xFFFFFFFF
35
+ r ^ 0xFFFFFFFF
36
+ end
29
37
  end
30
-
38
+
31
39
  end
32
40
 
33
41
  ##
@@ -180,19 +188,16 @@ class MemCache
180
188
  end
181
189
 
182
190
  ##
183
- # Deceremets the value for +key+ by +amount+ and returns the new value.
191
+ # Decrements the value for +key+ by +amount+ and returns the new value.
184
192
  # +key+ must already exist. If +key+ is not an integer, it is assumed to be
185
193
  # 0. +key+ can not be decremented below 0.
186
194
 
187
195
  def decr(key, amount = 1)
188
- server, cache_key = request_setup key
189
-
190
- if @multithread then
191
- threadsafe_cache_decr server, cache_key, amount
192
- else
196
+ raise MemCacheError, "Update of readonly cache" if @readonly
197
+ with_server(key) do |server, cache_key|
193
198
  cache_decr server, cache_key, amount
194
199
  end
195
- rescue TypeError, SocketError, SystemCallError, IOError => err
200
+ rescue TypeError => err
196
201
  handle_error server, err
197
202
  end
198
203
 
@@ -201,20 +206,13 @@ class MemCache
201
206
  # unmarshalled.
202
207
 
203
208
  def get(key, raw = false)
204
- server, cache_key = request_setup key
205
-
206
- value = if @multithread then
207
- threadsafe_cache_get server, cache_key
208
- else
209
- cache_get server, cache_key
210
- end
211
-
212
- return nil if value.nil?
213
-
214
- value = Marshal.load value unless raw
215
-
216
- return value
217
- rescue TypeError, SocketError, SystemCallError, IOError => err
209
+ with_server(key) do |server, cache_key|
210
+ value = cache_get server, cache_key
211
+ return nil if value.nil?
212
+ value = Marshal.load value unless raw
213
+ return value
214
+ end
215
+ rescue TypeError => err
218
216
  handle_error server, err
219
217
  end
220
218
 
@@ -253,38 +251,31 @@ class MemCache
253
251
 
254
252
  server_keys.each do |server, keys|
255
253
  keys = keys.join ' '
256
- values = if @multithread then
257
- threadsafe_cache_get_multi server, keys
258
- else
259
- cache_get_multi server, keys
260
- end
254
+ values = cache_get_multi server, keys
261
255
  values.each do |key, value|
262
256
  results[cache_keys[key]] = Marshal.load value
263
257
  end
264
258
  end
265
259
 
266
260
  return results
267
- rescue TypeError, SocketError, SystemCallError, IOError => err
261
+ rescue TypeError => err
268
262
  handle_error server, err
269
263
  end
270
264
 
271
265
  ##
272
- # Increments the value for +key+ by +amount+ and retruns the new value.
266
+ # Increments the value for +key+ by +amount+ and returns the new value.
273
267
  # +key+ must already exist. If +key+ is not an integer, it is assumed to be
274
268
  # 0.
275
269
 
276
270
  def incr(key, amount = 1)
277
- server, cache_key = request_setup key
278
-
279
- if @multithread then
280
- threadsafe_cache_incr server, cache_key, amount
281
- else
271
+ raise MemCacheError, "Update of readonly cache" if @readonly
272
+ with_server(key) do |server, cache_key|
282
273
  cache_incr server, cache_key, amount
283
274
  end
284
- rescue TypeError, SocketError, SystemCallError, IOError => err
275
+ rescue TypeError => err
285
276
  handle_error server, err
286
277
  end
287
-
278
+
288
279
  ##
289
280
  # Add +key+ to the cache with value +value+ that expires in +expiry+
290
281
  # seconds. If +raw+ is true, +value+ will not be Marshalled.
@@ -294,30 +285,24 @@ class MemCache
294
285
 
295
286
  def set(key, value, expiry = 0, raw = false)
296
287
  raise MemCacheError, "Update of readonly cache" if @readonly
297
- server, cache_key = request_setup key
298
- socket = server.socket
288
+ with_server(key) do |server, cache_key|
299
289
 
300
- value = Marshal.dump value unless raw
301
- command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
290
+ value = Marshal.dump value unless raw
291
+ command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
302
292
 
303
- begin
304
- @mutex.lock if @multithread
305
- socket.write command
306
- result = socket.gets
307
- if result.nil?
308
- server.close
309
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
310
- end
293
+ with_socket_management(server) do |socket|
294
+ socket.write command
295
+ result = socket.gets
296
+ if result.nil?
297
+ server.close
298
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
299
+ end
311
300
 
312
- if result =~ /^SERVER_ERROR (.*)/
313
- server.close
314
- raise MemCacheError, $1.strip
315
- end
316
- rescue SocketError, SystemCallError, IOError => err
317
- server.close
318
- raise MemCacheError, err.message
319
- ensure
320
- @mutex.unlock if @multithread
301
+ if result =~ /^SERVER_ERROR (.*)/
302
+ server.close
303
+ raise MemCacheError, $1.strip
304
+ end
305
+ end
321
306
  end
322
307
  end
323
308
 
@@ -331,46 +316,28 @@ class MemCache
331
316
 
332
317
  def add(key, value, expiry = 0, raw = false)
333
318
  raise MemCacheError, "Update of readonly cache" if @readonly
334
- server, cache_key = request_setup key
335
- socket = server.socket
336
-
337
- value = Marshal.dump value unless raw
338
- command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
319
+ with_server(key) do |server, cache_key|
320
+ value = Marshal.dump value unless raw
321
+ command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
339
322
 
340
- begin
341
- @mutex.lock if @multithread
342
- socket.write command
343
- socket.gets
344
- rescue SocketError, SystemCallError, IOError => err
345
- server.close
346
- raise MemCacheError, err.message
347
- ensure
348
- @mutex.unlock if @multithread
323
+ with_socket_management(server) do |socket|
324
+ socket.write command
325
+ socket.gets
326
+ end
349
327
  end
350
328
  end
351
-
329
+
352
330
  ##
353
331
  # Removes +key+ from the cache in +expiry+ seconds.
354
332
 
355
333
  def delete(key, expiry = 0)
356
- @mutex.lock if @multithread
357
-
358
- raise MemCacheError, "No active servers" unless active?
359
- cache_key = make_cache_key key
360
- server = get_server_for_key cache_key
361
-
362
- sock = server.socket
363
- raise MemCacheError, "No connection to server" if sock.nil?
334
+ raise MemCacheError, "Update of readonly cache" if @readonly
335
+ server, cache_key = request_setup key
364
336
 
365
- begin
366
- sock.write "delete #{cache_key} #{expiry}\r\n"
367
- sock.gets
368
- rescue SocketError, SystemCallError, IOError => err
369
- server.close
370
- raise MemCacheError, err.message
337
+ with_socket_management(server) do |socket|
338
+ socket.write "delete #{cache_key} #{expiry}\r\n"
339
+ socket.gets
371
340
  end
372
- ensure
373
- @mutex.unlock if @multithread
374
341
  end
375
342
 
376
343
  ##
@@ -382,15 +349,10 @@ class MemCache
382
349
  begin
383
350
  @mutex.lock if @multithread
384
351
  @servers.each do |server|
385
- begin
386
- sock = server.socket
387
- raise MemCacheError, "No connection to server" if sock.nil?
388
- sock.write "flush_all\r\n"
389
- result = sock.gets
352
+ with_socket_management(server) do |socket|
353
+ socket.write "flush_all\r\n"
354
+ result = socket.gets
390
355
  raise MemCacheError, $2.strip if result =~ /^(SERVER_)?ERROR(.*)/
391
- rescue SocketError, SystemCallError, IOError => err
392
- server.close
393
- raise MemCacheError, err.message
394
356
  end
395
357
  end
396
358
  ensure
@@ -444,14 +406,12 @@ class MemCache
444
406
  server_stats = {}
445
407
 
446
408
  @servers.each do |server|
447
- sock = server.socket
448
- raise MemCacheError, "No connection to server" if sock.nil?
449
-
450
- value = nil
451
- begin
452
- sock.write "stats\r\n"
409
+ next unless server.alive?
410
+ with_socket_management(server) do |socket|
411
+ value = nil # TODO: why is this line here?
412
+ socket.write "stats\r\n"
453
413
  stats = {}
454
- while line = sock.gets do
414
+ while line = socket.gets do
455
415
  break if line == "END\r\n"
456
416
  if line =~ /^STAT ([\w]+) ([\w\.\:]+)/ then
457
417
  name, value = $1, $2
@@ -472,9 +432,6 @@ class MemCache
472
432
  end
473
433
  end
474
434
  server_stats["#{server.host}:#{server.port}"] = stats
475
- rescue SocketError, SystemCallError, IOError => err
476
- server.close
477
- raise MemCacheError, err.message
478
435
  end
479
436
  end
480
437
 
@@ -542,11 +499,12 @@ class MemCache
542
499
  # found.
543
500
 
544
501
  def cache_decr(server, cache_key, amount)
545
- socket = server.socket
546
- socket.write "decr #{cache_key} #{amount}\r\n"
547
- text = socket.gets
548
- return nil if text == "NOT_FOUND\r\n"
549
- return text.to_i
502
+ with_socket_management(server) do |socket|
503
+ socket.write "decr #{cache_key} #{amount}\r\n"
504
+ text = socket.gets
505
+ return nil if text == "NOT_FOUND\r\n"
506
+ return text.to_i
507
+ end
550
508
  end
551
509
 
552
510
  ##
@@ -554,50 +512,52 @@ class MemCache
554
512
  # miss.
555
513
 
556
514
  def cache_get(server, cache_key)
557
- socket = server.socket
558
- socket.write "get #{cache_key}\r\n"
559
- keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
515
+ with_socket_management(server) do |socket|
516
+ socket.write "get #{cache_key}\r\n"
517
+ keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
560
518
 
561
- if keyline.nil? then
562
- server.close
563
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
564
- end
519
+ if keyline.nil? then
520
+ server.close
521
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
522
+ end
565
523
 
566
- return nil if keyline == "END\r\n"
524
+ return nil if keyline == "END\r\n"
567
525
 
568
- unless keyline =~ /(\d+)\r/ then
569
- server.close
570
- raise MemCacheError, "unexpected response #{keyline.inspect}"
526
+ unless keyline =~ /(\d+)\r/ then
527
+ server.close
528
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
529
+ end
530
+ value = socket.read $1.to_i
531
+ socket.read 2 # "\r\n"
532
+ socket.gets # "END\r\n"
533
+ return value
571
534
  end
572
- value = socket.read $1.to_i
573
- socket.read 2 # "\r\n"
574
- socket.gets # "END\r\n"
575
- return value
576
535
  end
577
536
 
578
537
  ##
579
538
  # Fetches +cache_keys+ from +server+ using a multi-get.
580
539
 
581
540
  def cache_get_multi(server, cache_keys)
582
- values = {}
583
- socket = server.socket
584
- socket.write "get #{cache_keys}\r\n"
541
+ with_socket_management(server) do |socket|
542
+ values = {}
543
+ socket.write "get #{cache_keys}\r\n"
585
544
 
586
- while keyline = socket.gets do
587
- return values if keyline == "END\r\n"
545
+ while keyline = socket.gets do
546
+ return values if keyline == "END\r\n"
588
547
 
589
- unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
590
- server.close
591
- raise MemCacheError, "unexpected response #{keyline.inspect}"
548
+ unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
549
+ server.close
550
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
551
+ end
552
+
553
+ key, data_length = $1, $3
554
+ values[$1] = socket.read data_length.to_i
555
+ socket.read(2) # "\r\n"
592
556
  end
593
557
 
594
- key, data_length = $1, $3
595
- values[$1] = socket.read data_length.to_i
596
- socket.read(2) # "\r\n"
558
+ server.close
559
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
597
560
  end
598
-
599
- server.close
600
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
601
561
  end
602
562
 
603
563
  ##
@@ -605,17 +565,64 @@ class MemCache
605
565
  # found.
606
566
 
607
567
  def cache_incr(server, cache_key, amount)
608
- socket = server.socket
609
- socket.write "incr #{cache_key} #{amount}\r\n"
610
- text = socket.gets
611
- return nil if text == "NOT_FOUND\r\n"
612
- return text.to_i
568
+ with_socket_management(server) do |socket|
569
+ socket.write "incr #{cache_key} #{amount}\r\n"
570
+ text = socket.gets
571
+ return nil if text == "NOT_FOUND\r\n"
572
+ return text.to_i
573
+ end
574
+ end
575
+
576
+ ##
577
+ # 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.
582
+ # 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
584
+ # 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.
587
+
588
+ def with_socket_management(server, &block)
589
+ @mutex.lock if @multithread
590
+ retried = false
591
+ begin
592
+ 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.
595
+ raise IndexError, "No connection to server (#{server.status})" if socket.nil?
596
+ block.call(socket)
597
+ rescue MemCacheError, SocketError, SystemCallError, IOError => err
598
+ handle_error(server, err) if retried || socket.nil?
599
+ retried = true
600
+ retry
601
+ end
602
+ ensure
603
+ @mutex.unlock if @multithread
604
+ end
605
+
606
+ def with_server(key)
607
+ retried = false
608
+ begin
609
+ server, cache_key = request_setup(key)
610
+ yield server, cache_key
611
+ rescue IndexError => e
612
+ if !retried && @servers.size > 1
613
+ puts "Connection to server #{server.inspect} DIED! Retrying operation..."
614
+ retried = true
615
+ retry
616
+ end
617
+ handle_error(nil, e)
618
+ end
613
619
  end
614
620
 
615
621
  ##
616
622
  # Handles +error+ from +server+.
617
623
 
618
624
  def handle_error(server, error)
625
+ raise error if error.is_a?(MemCacheError)
619
626
  server.close if server
620
627
  new_error = MemCacheError.new error.message
621
628
  new_error.set_backtrace error.backtrace
@@ -630,38 +637,9 @@ class MemCache
630
637
  raise MemCacheError, 'No active servers' unless active?
631
638
  cache_key = make_cache_key key
632
639
  server = get_server_for_key cache_key
633
- raise MemCacheError, 'No connection to server' if server.socket.nil?
634
640
  return server, cache_key
635
641
  end
636
642
 
637
- def threadsafe_cache_decr(server, cache_key, amount) # :nodoc:
638
- @mutex.lock
639
- cache_decr server, cache_key, amount
640
- ensure
641
- @mutex.unlock
642
- end
643
-
644
- def threadsafe_cache_get(server, cache_key) # :nodoc:
645
- @mutex.lock
646
- cache_get server, cache_key
647
- ensure
648
- @mutex.unlock
649
- end
650
-
651
- def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc:
652
- @mutex.lock
653
- cache_get_multi socket, cache_keys
654
- ensure
655
- @mutex.unlock
656
- end
657
-
658
- def threadsafe_cache_incr(server, cache_key, amount) # :nodoc:
659
- @mutex.lock
660
- cache_incr server, cache_key, amount
661
- ensure
662
- @mutex.unlock
663
- end
664
-
665
643
  ##
666
644
  # This class represents a memcached server instance.
667
645
 
@@ -328,7 +328,7 @@ class TestMemCache < Test::Unit::TestCase
328
328
  @cache.get 'key'
329
329
  end
330
330
 
331
- assert_equal 'No connection to server', e.message
331
+ assert_match /^No connection to server/, e.message
332
332
  end
333
333
 
334
334
  def test_get_no_servers
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.1
4
+ version: 1.5.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hodel
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2008-05-14 00:00:00 -07:00
14
+ date: 2008-07-06 00:00:00 -07:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
@@ -19,8 +19,8 @@ description: A Ruby-based memcached client library
19
19
  email: mike@fiveruns.com
20
20
  executables: []
21
21
 
22
- extensions: []
23
-
22
+ extensions:
23
+ - ext/crc32/extconf.rb
24
24
  extra_rdoc_files: []
25
25
 
26
26
  files:
@@ -30,6 +30,7 @@ files:
30
30
  - Rakefile
31
31
  - lib/memcache.rb
32
32
  - lib/memcache_util.rb
33
+ - ext/crc32/crc32.c
33
34
  has_rdoc: false
34
35
  homepage: http://github.com/fiveruns/memcache-client
35
36
  post_install_message:
@@ -52,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
53
  requirements: []
53
54
 
54
55
  rubyforge_project:
55
- rubygems_version: 1.0.1
56
+ rubygems_version: 1.2.0
56
57
  signing_key:
57
58
  specification_version: 2
58
59
  summary: A Ruby-based memcached client library