diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf08465 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.*.swp +pkg diff --git a/AUTHOR b/AUTHORS similarity index 100% rename from AUTHOR rename to AUTHORS diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..1934d03 --- /dev/null +++ b/Gemfile @@ -0,0 +1,12 @@ +source "http://rubygems.org" + +# Add dependencies to develop your gem here. +# Include everything needed to run rake, tests, features, etc. +group :development do + gem "shoulda" + gem "yard" + gem "rdoc" + gem "bundler" + gem "jeweler" + gem "simplecov" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..4f0c120 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,46 @@ +GEM + remote: http://rubygems.org/ + specs: + activesupport (3.2.13) + i18n (= 0.6.1) + multi_json (~> 1.0) + bourne (1.4.0) + mocha (~> 0.13.2) + git (1.2.5) + i18n (0.6.1) + jeweler (1.8.4) + bundler (~> 1.0) + git (>= 1.2.5) + rake + rdoc + json (1.7.7) + metaclass (0.0.1) + mocha (0.13.3) + metaclass (~> 0.0.1) + multi_json (1.7.2) + rake (10.0.4) + rdoc (4.0.1) + json (~> 1.4) + shoulda (3.4.0) + shoulda-context (~> 1.0, >= 1.0.1) + shoulda-matchers (~> 1.0, >= 1.4.1) + shoulda-context (1.1.0) + shoulda-matchers (1.5.6) + activesupport (>= 3.0.0) + bourne (~> 1.3) + simplecov (0.7.1) + multi_json (~> 1.0) + simplecov-html (~> 0.7.1) + simplecov-html (0.7.1) + yard (0.8.5.2) + +PLATFORMS + ruby + +DEPENDENCIES + bundler + jeweler + rdoc + shoulda + simplecov + yard diff --git a/Rakefile b/Rakefile index 00f3418..06cfb4f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,57 +1,49 @@ +# encoding: utf-8 + require 'rubygems' +require 'bundler' +begin + Bundler.setup(:default, :development) +rescue Bundler::BundlerError => e + $stderr.puts e.message + $stderr.puts "Run `bundle install` to install missing gems" + exit e.status_code +end require 'rake' -begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "robustserver" - gem.summary = %Q{Robust Server} - gem.description = %Q{Protects your Server against SIGS and unplaned exceptions} - gem.email = "Denis.Knauf@gmail.com" - gem.homepage = "http://github.com/DenisKnauf/robustserver" - gem.authors = ["Denis Knauf"] - gem.files = ["AUTHORS", "README.md", "VERSION", "lib/**/*.rb", "test/**/*.rb"] - gem.require_paths = ["lib"] - end - Jeweler::GemcutterTasks.new -rescue LoadError - puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" +require 'jeweler' +Jeweler::Tasks.new do |gem| + # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options + gem.name = "robustserver" + gem.summary = %Q{Robust Server} + gem.description = %Q{Protects your Server against SIGS and rescues all exceptions.} + gem.email = "Denis.Knauf@gmail.com" + gem.homepage = "http://github.com/DenisKnauf/robustserver" + gem.authors = ["Denis Knauf"] + gem.files = %w[AUTHORS README.md VERSION lib/**/*.rb test/**/*.rb] + gem.require_paths = %w[lib] + # dependencies defined in Gemfile end +Jeweler::RubygemsDotOrgTasks.new require 'rake/testtask' Rake::TestTask.new(:test) do |test| - test.libs << 'lib' << 'test' << 'ext' - test.pattern = 'test/**/*_test.rb' + test.libs << 'lib' << 'test' + test.pattern = 'test/**/test_*.rb' test.verbose = true end -begin - require 'rcov/rcovtask' - Rcov::RcovTask.new do |test| - test.libs << 'test' - test.pattern = 'test/**/*_test.rb' - test.verbose = true - end -rescue LoadError - task :rcov do - abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" - end +=begin +require 'rcov/rcovtask' +Rcov::RcovTask.new do |test| + test.libs << 'test' + test.pattern = 'test/**/test_*.rb' + test.verbose = true + test.rcov_opts << '--exclude "gems/*"' end - -task :test => :check_dependencies +=end task :default => :test -require 'rake/rdoctask' -Rake::RDocTask.new do |rdoc| - if File.exist?('VERSION') - version = File.read('VERSION') - else - version = "" - end - - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "sbdb #{version}" - rdoc.rdoc_files.include('README*') - rdoc.rdoc_files.include('lib/**/*.rb') -end +require 'yard' +YARD::Rake::YardocTask.new diff --git a/VERSION b/VERSION index 77d6f4c..6812f81 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.0 +0.0.3 \ No newline at end of file diff --git a/lib/robustserver.rb b/lib/robustserver.rb index 8a63a4d..91fc1d9 100644 --- a/lib/robustserver.rb +++ b/lib/robustserver.rb @@ -4,6 +4,7 @@ def Signal.signame s when String then s when Symbol then s.to_s when Fixnum then list.invert[s] + else raise ArgumentError, "String, Symbol or Fixnum expected, not #{s.class}" end end @@ -12,6 +13,7 @@ def Signal.sig s when Fixnum then s when String then list[s] when Symbol then list[s.to_s] + else raise ArgumentError, "String, Symbol or Fixnum expected, not #{s.class}" end end @@ -25,25 +27,65 @@ def Signal.[] s when String then list[s] when Symbol then list[s.to_s] when Fixnum then list.invert[s] - else raise ArgumentError + else raise ArgumentError, "String, Symbol or Fixnum expected, not #{s.class}" end end +# Description +# =========== +# +# Counts retries ot something. If the retries are to often in a short time, +# you shouldn't retry again. +# +# Examples +# ======== +# +# Strings aren't Integers and 2*"Text" will raise TypeError. +# +# retries = Retry.new 5, 1 +# begin +# array_of_ints_and_some_strings.each do |i| +# puts 2*i +# end +# rescue TypeError +# retries.retry? and retry +# raise $! +# end +# +# Retry.new( 10, 30).run( ConnectionLost) do +# try_to_connect_to_db +# try_query +# end class Retries attr_accessor :max, :range attr_reader :count, :last - def initialize max = 10, range = 10 - @max, @range, @count, @last = max, range, 0, Time.now + # max: How many retries in range-time are allowed maximal. + # range: In which time-range are these retries are allowed + def initialize max = nil, range = nil + @max, @range, @count, @last = max || 10, range || 10, 0, Time.now end + # Counts retries on every call. + # If these retries are to often - max times in range - it will return false + # else true. + # Now you can say: "I give up, to many retries, it seems it doesn't work." def retry? @count = @last + @range > Time.now ? @count + 1 : 1 @last = Time.now @count < @max end - def run ex, &e + # Automatical retrieing on raised exceptions in block. + # ex: Your expected Esception you will rescue. Default: Object, so realy everything. + # + # Example: + # Retries.new( 10, 30).run ArgumentError do something_do_which_could_raise_exception ArgumentError end + # + # This will retry maximal 10 times in 30 seconds to Call this block. But only rescues ArgumentError! + # Every other Error it will ignore and throws Exception. No retry. + def run ex = nil, &e + ex ||= Object begin e.call *args rescue ex retries.retry? and retry @@ -51,17 +93,33 @@ class Retries end end +# Easy problem-handler for your Server. +# +# Problem: Your process should not crash, it should be available for anytime. +# If an exception will be raised, you have to handle it, or it will be shutdown abnormaly. +# Or if a signal tries to "kill" your program, your program will shutdown abnormaly, too. +# +# RobustServer handles any exception / kill, logs it and prevents the server to shutting down. +# +# For implementation a server, create a subclass of RobustServer and implement the *#run*-method. +# Anytime this *#run*-method returns or an exception will be raised, it will be rescued and run recalled. +# For initialization, you can override **#initialize**, but don't forget to call **super**. class RobustServer + class UnimplementedRun sh, Signal[:HUP] => nil, Signal[:TERM] => sh, Signal[:KILL] => sh, Signal[:USR1] => nil, Signal[:USR2] => nil } + @signals, @output = [], $stderr end def trapping @@ -69,23 +127,42 @@ class RobustServer end def signal_handler s - @signal = s + output.puts [:signal, s, Signal[s]].inspect + @signals.push s unless @signals.include? s end def main max = nil, range = nil retries = Retries.new max, range trapping - $stderr.puts "Arbeit wird nun aufgenommen..." + output.puts "Running...." begin self.run - rescue SystemExit, Interrupt, SignalException - $stderr.puts "Das Beenden des Programms wurde angefordert. #{$!}" + rescue UnimplementedRun + output.puts $!.message + exit 1 + rescue SystemExit + output.puts "Server interrupted by signal: #$!" + raise + rescue Interrupt, SignalException + output.puts "Server interrupted by signal: #$!" + exit 0 rescue Object - $stderr.puts [:rescue, $!, $!.class, $!.backtrace].inspect + output.puts [:rescue, $!, $!.class, $!.backtrace].inspect retry if retries.retry? - $stderr.print "Zuviele Fehler in zu kurzer Zeit. Ich gebe auf und " + output.print "Too many errors in too short time. Give up: " + exit 2 end + ensure + output.puts "Disregarded signals: #{@signals.map(&Signal.method(:[])).join( ', ')}" unless @signals.empty? trapping - $stderr.puts "Beende mich selbst." + self.at_exit + output.puts "Shutdown." + end + + def run + raise UnimplementedRun, "Unimplemented #{self.class.name}#run." + end + + def at_exit end end diff --git a/robustserver.gemspec b/robustserver.gemspec index e6892d2..7ffa2ee 100644 --- a/robustserver.gemspec +++ b/robustserver.gemspec @@ -1,40 +1,57 @@ # Generated by jeweler # DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- Gem::Specification.new do |s| - s.name = %q{robustserver} - s.version = "0.0.0" + s.name = "robustserver" + s.version = "0.0.3" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Denis Knauf"] - s.date = %q{2010-03-02} - s.description = %q{Protects your Server against SIGS and unplaned exceptions} - s.email = %q{Denis.Knauf@gmail.com} + s.date = "2013-04-15" + s.description = "Protects your Server against SIGS and rescues all exceptions." + s.email = "Denis.Knauf@gmail.com" s.extra_rdoc_files = [ "LICENSE", - "README.md" + "README.md" ] s.files = [ + "AUTHORS", "README.md", - "VERSION", - "lib/robustserver.rb" + "VERSION", + "lib/robustserver.rb" ] - s.homepage = %q{http://github.com/DenisKnauf/robustserver} - s.rdoc_options = ["--charset=UTF-8"] + s.homepage = "http://github.com/DenisKnauf/robustserver" s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.5} - s.summary = %q{Robust Server} + s.rubygems_version = "1.8.23" + s.summary = "Robust Server" if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.specification_version = 3 - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) end else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) end end