diff --git a/.gitignore b/.gitignore index aee8a31..91ddf1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,22 @@ # See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal /db/*.local /db/database.yml # Ignore all logfiles and tempfiles. /log/*.log /tmp *.swp config/database.yml +config/beanstalk.yml .ruby-version diff --git a/Gemfile b/Gemfile index c620ac1..7607996 100644 --- a/Gemfile +++ b/Gemfile @@ -1,36 +1,37 @@ source 'https://rubygems.org' group :development, :test do gem 'pry-byebug' end group :development do gem 'rubocop', '0.35', require: false gem 'guard-minitest', require: false gem 'guard', require: false gem 'capistrano', '3.2.1', require: false # pkg:capistrano end # Lock jessie versions # gem 'rails', '4.1.8' gem 'i18n', '0.6.9' gem 'json', '1.8.1' gem 'mail', '2.6.1' gem 'mime-types', '1.25' gem 'minitest', '5.4.2' gem 'rack', '1.5.2' gem 'rack-test', '0.6.2' gem 'rake', '10.3.2' gem 'sprockets', '2.12.3' gem 'sprockets-rails', '2.1.3' gem 'thread_safe', '0.3.3' gem 'tzinfo', '1.1.0' gem 'mysql2', '0.3.16' gem 'jquery-rails', '3.1.2' +gem 'beaneater', '1.0.0' group :test do gem 'factory_girl_rails', '4.4.1' # pkg:ruby-factory-girl-rails end diff --git a/Gemfile.lock b/Gemfile.lock index 7227070..2088345 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,174 +1,176 @@ GEM remote: https://rubygems.org/ specs: actionmailer (4.1.8) actionpack (= 4.1.8) actionview (= 4.1.8) mail (~> 2.5, >= 2.5.4) actionpack (4.1.8) actionview (= 4.1.8) activesupport (= 4.1.8) rack (~> 1.5.2) rack-test (~> 0.6.2) actionview (4.1.8) activesupport (= 4.1.8) builder (~> 3.1) erubis (~> 2.7.0) activemodel (4.1.8) activesupport (= 4.1.8) builder (~> 3.1) activerecord (4.1.8) activemodel (= 4.1.8) activesupport (= 4.1.8) arel (~> 5.0.0) activesupport (4.1.8) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.1) tzinfo (~> 1.1) arel (5.0.1.20140414130214) ast (2.1.0) astrolabe (1.3.1) parser (~> 2.2) + beaneater (1.0.0) builder (3.2.2) byebug (4.0.5) columnize (= 0.9.0) capistrano (3.2.1) i18n rake (>= 10.0.0) sshkit (~> 1.3) coderay (1.1.0) colorize (0.7.7) columnize (0.9.0) erubis (2.7.0) factory_girl (4.4.0) activesupport (>= 3.0.0) factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) ffi (1.9.10) formatador (0.2.5) guard (2.13.0) formatador (>= 0.2.4) listen (>= 2.7, <= 4.0) lumberjack (~> 1.0) nenv (~> 0.1) notiffany (~> 0.0) pry (>= 0.9.12) shellany (~> 0.0) thor (>= 0.18.1) guard-compat (1.2.1) guard-minitest (2.4.4) guard-compat (~> 1.2) minitest (>= 3.0) hike (1.2.3) i18n (0.6.9) jquery-rails (3.1.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.1) listen (3.0.3) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) lumberjack (1.0.9) mail (2.6.1) mime-types (>= 1.16, < 3) method_source (0.8.2) mime-types (1.25) minitest (5.4.2) multi_json (1.11.2) mysql2 (0.3.16) nenv (0.2.0) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (2.9.2) notiffany (0.0.8) nenv (~> 0.1) shellany (~> 0.0) parser (2.2.3.0) ast (>= 1.1, < 3.0) powerpack (0.1.1) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) pry-byebug (3.1.0) byebug (~> 4.0) pry (~> 0.10) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) rails (4.1.8) actionmailer (= 4.1.8) actionpack (= 4.1.8) actionview (= 4.1.8) activemodel (= 4.1.8) activerecord (= 4.1.8) activesupport (= 4.1.8) bundler (>= 1.3.0, < 2.0) railties (= 4.1.8) sprockets-rails (~> 2.0) railties (4.1.8) actionpack (= 4.1.8) activesupport (= 4.1.8) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.0.0) rake (10.3.2) rb-fsevent (0.9.6) rb-inotify (0.9.5) ffi (>= 0.5.0) rubocop (0.35.0) astrolabe (~> 1.3) parser (>= 2.2.3.0, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) ruby-progressbar (1.7.5) shellany (0.0.1) slop (3.6.0) sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) sprockets-rails (2.1.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (~> 2.8) sshkit (1.7.1) colorize (>= 0.7.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) thor (0.19.1) thread_safe (0.3.3) tilt (1.4.1) tzinfo (1.1.0) thread_safe (~> 0.1) PLATFORMS ruby DEPENDENCIES + beaneater (= 1.0.0) capistrano (= 3.2.1) factory_girl_rails (= 4.4.1) guard guard-minitest i18n (= 0.6.9) jquery-rails (= 3.1.2) json (= 1.8.1) mail (= 2.6.1) mime-types (= 1.25) minitest (= 5.4.2) mysql2 (= 0.3.16) pry-byebug rack (= 1.5.2) rack-test (= 0.6.2) rails (= 4.1.8) rake (= 10.3.2) rubocop (= 0.35) sprockets (= 2.12.3) sprockets-rails (= 2.1.3) thread_safe (= 0.3.3) tzinfo (= 1.1.0) diff --git a/config/application.rb b/config/application.rb index 95a699b..788b831 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,34 +1,40 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' # Production doesn't use bundler # you've limited to :test, :development, or :production. if ENV['RAILS_ENV'] != 'production' Bundler.require(*Rails.groups) else # Dependencies to load before starting rails in production require 'jquery-rails' end module Base class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Store/Read localtime from the database config.active_record.default_timezone = :local # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de config.autoload_paths << Rails.root.join('lib') config.x = {} end def self.settings Application.config.x end + + def self.bean + @bean ||= Bean::Client.new( + YAML.load_file(Rails.root.join('config', 'beanstalk.yml'))[Rails.env].symbolize_keys[:host] + ) + end end diff --git a/config/beanstalk.yml.base b/config/beanstalk.yml.base new file mode 100644 index 0000000..0800337 --- /dev/null +++ b/config/beanstalk.yml.base @@ -0,0 +1,5 @@ +development: + host: '127.0.0.1:11300' + +test: + host: '127.0.0.1:11300' diff --git a/config/initializers/beanstalk.rb b/config/initializers/beanstalk.rb new file mode 100644 index 0000000..9a93da5 --- /dev/null +++ b/config/initializers/beanstalk.rb @@ -0,0 +1,7 @@ +Beaneater.configure do |config| + config.default_put_ttr = 10.minutes + + config.job_parser = ->(body) { ActiveSupport::JSON.decode(body).symbolize_keys! } + config.job_serializer = ->(body) { ActiveSupport::JSON.encode(body) } +end + diff --git a/lib/bean/client.rb b/lib/bean/client.rb new file mode 100644 index 0000000..cbcb04f --- /dev/null +++ b/lib/bean/client.rb @@ -0,0 +1,41 @@ +module Bean + class Client + def initialize(host) + @host = host + end + + def put(body) + client.tubes['default'].put(body) + rescue Beaneater::NotConnected + reconnect! + end + + def reserve(*args) + client.tubes.reserve(*args) + end + + def reconnect!(retries = 3, sleep_time = 0.5) + client! + rescue Beaneater::NotConnected => exception + retries -= 1 + raise exception if retries.zero? + sleep(sleep_time) + retry + end + + private + + def client + @client ||= client! + end + + def client! + @client.close if @client # rescue nil + @client = connect + end + + def connect + Beaneater.new(@host) + end + end +end diff --git a/lib/bean/worker.rb b/lib/bean/worker.rb new file mode 100644 index 0000000..edbb067 --- /dev/null +++ b/lib/bean/worker.rb @@ -0,0 +1,70 @@ +require 'singleton' + +module Bean + class Worker + include Singleton + + TIMEOUT = 5 + + attr_accessor :job + + def self.work + instance.work + end + + def work + register_signals + watch + rescue Beaneater::NotConnected + Base.beanstalk_reconnect! + end + + def stop + if job.nil? + exit + else + @stop = true + end + end + + def stop? # rubocop:disable Style/TrivialAccessors + @stop + end + + private + + def register_signals + trap('INT') { stop } + trap('TERM') { stop } + end + + def watch + loop do + procline('watching') + break if stop? + + process_job + end + rescue Beaneater::TimedOutError + retry + end + + def process_job + self.job = Base.bean.reserve(TIMEOUT) + log_job + + job.delete + ensure + self.job = nil + end + + def log_job + procline("working on jobid=#{job.id} #{job.body}") + Rails.logger.warn(job_id: job.id, job_body: job.body.to_s) + end + + def procline(line) + $0 = "bean-#{line}" + end + end +end diff --git a/lib/tasks/bean.rake b/lib/tasks/bean.rake new file mode 100644 index 0000000..2829e13 --- /dev/null +++ b/lib/tasks/bean.rake @@ -0,0 +1,6 @@ +namespace :bean do + desc 'Start beanstalk worker' + task work: :environment do + Bean::Worker.work + end +end