Skip to content
Browse files

Add retryable classes

  • Loading branch information...
1 parent a4ce774 commit d24d218913bd574550cbf0e192b84d0cbf3b4535 @vincentp vincentp committed Jan 23, 2012
View
3 lib/massive_record/adapters/thrift/adapter.rb
@@ -22,4 +22,5 @@ module Thrift
require 'massive_record/adapters/thrift/connection'
require 'massive_record/adapters/thrift/row'
require 'massive_record/adapters/thrift/scanner'
-require 'massive_record/adapters/thrift/table'
+require 'massive_record/adapters/thrift/table'
+require 'massive_record/adapters/thrift/retryable'
View
16 lib/massive_record/adapters/thrift/retryable.rb
@@ -0,0 +1,16 @@
+require 'massive_record/wrapper/retryable'
+
+module MassiveRecord
+ module Adapters
+ module Thrift
+ class Retryable < MassiveRecord::Wrapper::Retryable
+
+ def initialize(opts = {}, &block)
+ opts[:on] ||= Apache::Hadoop::Hbase::Thrift::IOError
+ super
+ end
+
+ end
+ end
+ end
+end
View
1 lib/massive_record/wrapper/base.rb
@@ -5,6 +5,7 @@
require 'massive_record/wrapper/tables_collection'
require 'massive_record/wrapper/column_families_collection'
require 'massive_record/wrapper/cell'
+require 'massive_record/wrapper/retryable'
module MassiveRecord
module Wrapper
View
66 lib/massive_record/wrapper/retryable.rb
@@ -0,0 +1,66 @@
+#
+# Some application like a Rails website will reload the environment and initiate
+# a new connection if HBase / Thrift is going down.
+# In the case of a plain Ruby application running as a daemon, the same error could
+# shutdown your application. This class helps you to keep trying if you loose the
+# connection with Thrift.
+# The exception Apache::Hadoop::Hbase::Thrift::IOError will be catched and the
+# block inside will be tried again.
+#
+# Retryable.new do
+# message = Message.new
+# message.save
+# end
+#
+module MassiveRecord
+ module Wrapper
+ class Retryable
+
+ attr_accessor :exception_to_retry, :max_retry_count, :current_retry_count, :sleep_in_seconds, :logger
+
+ #
+ # Options:
+ # on => Class of the Exception, Apache::Hadoop::Hbase::Thrift::IOError by default
+ # retry => Maximum amount of time the code is trying to run
+ # logger => Initialized Ruby Logger object
+ #
+ def initialize(opts = {}, &block)
+ raise "The Retryable class needs a block to be initialized." unless block_given?
+
+ @exception_to_retry = opts[:on] || Exception
+ @max_retry_count = opts[:retry] || 50
+ @current_retry_count = 0
+ @sleep_in_seconds = 2
+ @logger = opts[:logger]
+
+ begin
+ return yield
+ rescue exception_to_retry
+ self.current_retry_count += 1
+ if current_retry_count <= max_retry_count
+ sleep_before_retry
+ retry
+ end
+ end
+
+ yield
+ end
+
+ #
+ # The sleeping period is increasing exponentially
+ # 1 try : 2 seconds
+ # 2 tries : 4 seconds
+ # 3 tries : 8 seconds
+ # 4 tried : 16 seconds
+ # ...
+ #
+ def sleep_before_retry
+ time = sleep_in_seconds ** current_retry_count
+ time = 3600 if time > 3600
+ logger.info "Exception < #{exception_to_retry.to_s} > raised... waiting #{time} seconds before retry." if logger
+ sleep(time)
+ end
+
+ end
+ end
+end
View
13 spec/adapter/thrift/retryable_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe "Retryable" do
+
+ let(:retryable) { MassiveRecord::Adapters::Thrift::Retryable.new { } }
+
+ describe "defaults" do
+ it "should default the exception to retry to Apache::Hadoop::Hbase::Thrift::IOError" do
+ retryable.exception_to_retry.should == Apache::Hadoop::Hbase::Thrift::IOError
+ end
+ end
+
+end
View
83 spec/wrapper/retryable_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe "Retryable" do
+
+ let(:retryable) { MassiveRecord::Wrapper::Retryable.new { } }
+
+ describe "rescue" do
+ it "should only retry a specified amount of times" do
+ MassiveRecord::Wrapper::Retryable.any_instance.should_receive(:sleep_before_retry).exactly(2)
+ begin
+ MassiveRecord::Wrapper::Retryable.new(:retry => 2, :on => Exception) { raise "Bouh!" }
+ rescue
+ end
+ end
+
+ it "should only rescue a given exception" do
+ lambda { MassiveRecord::Wrapper::Retryable.new(:on => LoadError) { raise StandardError.new("Bouh!") } }.should raise_error(StandardError)
+ end
+ end
+
+ describe "block" do
+ it "should raise an error if no block is passed to the initializer" do
+ lambda { MassiveRecord::Wrapper::Retryable.new }.should raise_error(RuntimeError)
+ end
+
+ it "should process a block without exception" do
+ city_name = "Paris"
+
+ city_name.should == "Paris"
+ MassiveRecord::Wrapper::Retryable.new { city_name = "London" }.should be_a_kind_of(MassiveRecord::Wrapper::Retryable)
+ city_name.should == "London"
+ end
+
+ it "should retry a block until it works" do
+ MassiveRecord::Wrapper::Retryable.any_instance.should_receive(:sleep_before_retry).exactly(2)
+ counter = 0
+ MassiveRecord::Wrapper::Retryable.new { counter += 1; raise Exception if counter < 3 }
+ end
+ end
+
+ describe "defaults" do
+ it "should default the exception to retry to Exception" do
+ retryable.exception_to_retry.should == Exception
+ end
+
+ it "should default the maximum amount of retries to 50" do
+ retryable.max_retry_count.should == 50
+ end
+
+ it "should default the retry count to 0" do
+ retryable.current_retry_count.should == 0
+ end
+
+ it "should default the sleeping time to 2 seconds" do
+ retryable.sleep_in_seconds.should == 2
+ end
+ end
+
+ describe "sleeping time" do
+ before do
+ retryable.current_retry_count = 1
+ retryable.sleep_in_seconds = 2
+ end
+
+ it "should sleep during which is the ** value of the sleeping time and the retry count" do
+ retryable.should_receive(:sleep).with(2)
+ retryable.send(:sleep_before_retry)
+ end
+
+ it "should log the status of the retry" do
+ retryable.logger = Object.new
+ retryable.logger.should_receive(:info).with("Exception < Exception > raised... waiting 2 seconds before retry.")
+ retryable.send(:sleep_before_retry)
+ end
+
+ it "should sleep a maximum of 1 hour" do
+ retryable.should_receive(:sleep).with(3600)
+ retryable.current_retry_count = 1000000
+ retryable.send(:sleep_before_retry)
+ end
+ end
+
+end

0 comments on commit d24d218

Please sign in to comment.
Something went wrong with that request. Please try again.