insque 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in insque.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 gropher
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Insque
2
+
3
+ Instant queue. Background processing and message driven communication tool. Faster and simplier alternative to Resque.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'insque', :git => 'https://github.com/Gropher/Insque.git'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install insque
18
+
19
+ ## Usage
20
+
21
+ At first you need to generate initializer and redis config file. Pass your sender name as parameter.
22
+ Sender name is the unique identifier of your instance of insque. You can use several instances of insque to create message driven communication system
23
+
24
+ $ rails g insque:initializer somesender
25
+
26
+ ### Sending
27
+
28
+ To broadcast message use:
29
+ ```ruby
30
+ Insque.broadcast :message_name, {:params => {:some => :params}}
31
+ ```
32
+ There is an easy way to use insque as background jobs processing. You can use `send_later` method to call any method of your rails models in background
33
+ . You still need listener running to make this work.
34
+ ```ruby
35
+ @model = MyModel.first
36
+ @model.send_later :mymethod, 'some', 'params'
37
+ ```
38
+
39
+ ### Recieving
40
+
41
+ To start recieving messages you need to:
42
+
43
+ 1. Create handler method in Insque module. First part of handler name is the name of the message sender.
44
+ ```ruby
45
+ def somesender_message_name message
46
+ #TODO: Handle message somehow
47
+ end
48
+ ```
49
+
50
+ 2. Call `listen` method in some background process or rake task:
51
+ ```ruby
52
+ Insque.listen
53
+ ```
54
+
55
+ or just run `bundle exec rake insque:listener` from your console.
56
+
57
+ 3. Call `janitor` method in some background process or rake task. Janitor will reissue failed messages or report error if message fails again. Janitor treats message as failed if it was not processed for an hour after broadcast or reissue.
58
+ ```ruby
59
+ Insque.janitor
60
+ ```
61
+
62
+ or just run `bundle exec rake insque:janitor` from your console.
63
+
64
+ ### Daemonizing
65
+
66
+ If you want to run insque listener as a daemon consider using [foreman](https://github.com/ddollar/foreman) for this.
67
+
68
+ Add foreman to your `Gemfile`:
69
+
70
+ gem 'foreman-capistrano'
71
+
72
+ Install it:
73
+
74
+ $ bundle install
75
+
76
+ Create `Procfile`:
77
+
78
+ listener: bundle exec rake insque:listener
79
+ janitor: bundle exec rake insque:janitor
80
+
81
+ Run foreman from your console:
82
+
83
+ $ bundle exec foreman start
84
+
85
+ For production use modify your capistrano deploy script somewhat like this:
86
+
87
+ set :default_environment, {'PATH' => "/sbin:$PATH"} # Optional. Useful if you get errors because start or restart command not found
88
+ set :foreman_concurrency, "\"listener=4,janitor=2\"" # How many processes of each type do you want
89
+ require 'foreman/capistrano'
90
+
91
+ after "deploy", "foreman:export" # Export Procfile as a set of upstart jobs
92
+ after "deploy", "foreman:restart" # You will need upstart installed on your server for this to work.
93
+
94
+ ## Contributing
95
+
96
+ 1. Fork it
97
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
98
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
99
+ 4. Push to the branch (`git push origin my-new-feature`)
100
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
data/insque.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/insque/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["gropher"]
6
+ gem.email = ["grophen@gmail.com"]
7
+ gem.description = "Instant queue. Background processing and message driven communication tool. Faster and simplier alternative to Resque."
8
+ gem.summary = "Instant queue. Background processing and message driven communication tool. Faster and simplier alternative to Resque."
9
+ gem.homepage = "https://github.com/Gropher/Insque"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "insque"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Insque::VERSION
17
+
18
+ gem.add_dependency('redis', '>= 2.2.2' )
19
+ end
@@ -0,0 +1,18 @@
1
+ module Insque
2
+ module Generators
3
+ class InitializerGenerator < ::Rails::Generators::Base
4
+ argument :sender_name, :type => :string
5
+
6
+ desc 'Create a sample Insque initializer and redis config file'
7
+
8
+ def self.source_root
9
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
10
+ end
11
+
12
+ def create_initializer
13
+ template 'insque.erb', 'config/initializers/insque.rb'
14
+ template 'redis.yml', 'config/redis.yml'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ Insque.sender = '<%= sender_name %>'
2
+ REDIS_CONFIG = YAML.load(File.open("#{Rails.root}/config/redis.yml"))[Rails.env]
3
+ $redis = Redis.new(:host => REDIS_CONFIG["host"], :port => REDIS_CONFIG["port"])
4
+ Insque.redis = $redis
5
+ Insque.debug = true
6
+ if defined?(PhusionPassenger)
7
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
8
+ if forked
9
+ $redis = Redis.new(:host => REDIS_CONFIG["host"], :port => REDIS_CONFIG["port"])
10
+ Insque.redis = $redis
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ development:
2
+ host: localhost
3
+ port: 6379
4
+ production:
5
+ host: localhost
6
+ port: 6379
7
+ test:
8
+ host: localhost
9
+ port: 6379
@@ -0,0 +1,8 @@
1
+ require 'insque'
2
+ require 'rails'
3
+
4
+ module Insque
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks { load 'tasks/insque.rake' }
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Insque
2
+ VERSION = "0.3.0"
3
+ end
data/lib/insque.rb ADDED
@@ -0,0 +1,122 @@
1
+ require "insque/version"
2
+ require "redis"
3
+ require "insque/railtie" if defined?(Rails)
4
+
5
+ module Insque
6
+ def self.debug= debug
7
+ @debug = debug
8
+ end
9
+
10
+ def self.redis= redis
11
+ @redis = redis
12
+ @redis.select 7
13
+ end
14
+
15
+ def self.sender= sender
16
+ @sender = sender
17
+ @inbox = "insque_inbox_#{sender}"
18
+ @processing = "insque_processing_#{sender}"
19
+ create_send_later_handler
20
+ end
21
+
22
+ def self.broadcast message, params = nil, recipient = :any
23
+ keys = []
24
+ case recipient
25
+ when :any
26
+ keys = @redis.smembers 'insque_inboxes'
27
+ when :self
28
+ keys = [@inbox]
29
+ else
30
+ keys = recipient.is_a?(Array) ? recipient : [recipient]
31
+ end
32
+ value = { :message => "#{@sender}_#{message}", :params => params, :broadcasted_at => Time.now.utc }
33
+ log "SENDING: #{value.to_json} TO #{keys.to_json}" if @debug
34
+ @redis.multi do |r|
35
+ keys.each {|k| r.lpush k, value.to_json}
36
+ end
37
+ end
38
+
39
+ def self.listen worker_name=''
40
+ @redis.sadd 'insque_inboxes', @inbox
41
+ log "#{worker_name} START LISTENING #{@inbox}"
42
+ loop do
43
+ message = @redis.brpoplpush(@inbox, @processing, 0)
44
+ log "#{worker_name} RECEIVING: #{message}" if @debug
45
+ begin
46
+ parsed_message = JSON.parse message
47
+ send(parsed_message['message'], parsed_message) if self.respond_to? parsed_message['message']
48
+ rescue => e
49
+ log "#{worker_name} ========== BROKEN_MESSAGE: #{message} =========="
50
+ log e.inspect
51
+ log e.backtrace
52
+ end
53
+ @redis.lrem @processing, 0, message
54
+ end
55
+ end
56
+
57
+ def self.janitor
58
+ loop do
59
+ @redis.watch @processing
60
+ errors = []
61
+ restart = []
62
+ delete = []
63
+ @redis.lrange(@processing, 0, -1).each do |m|
64
+ begin
65
+ parsed_message = JSON.parse(m)
66
+ if parsed_message['restarted_at'] && DateTime.parse(parsed_message['restarted_at']) < 1.hour.ago.utc
67
+ errors << parsed_message
68
+ delete << m
69
+ elsif DateTime.parse(parsed_message['broadcasted_at']) < 1.hour.ago.utc
70
+ restart << parsed_message.merge(:restarted_at => Time.now.utc)
71
+ delete << m
72
+ end
73
+ rescue
74
+ log "========== JANITOR_BROKEN_MESSAGE: #{m} =========="
75
+ end
76
+ end
77
+ result = @redis.multi do |r|
78
+ restart.each {|m| r.lpush @inbox, m.to_json }
79
+ delete.each {|m| r.lrem @processing, 0, m }
80
+ end
81
+ if result
82
+ errors.each {|m| log "ERROR: #{m.to_json}" }
83
+ restart.each {|m| log "RESTART: #{m.to_json}" }
84
+ log "CLEANING SUCCESSFULL"
85
+ else
86
+ log "CLEANING FAILED"
87
+ end
88
+ sleep(Random.rand * 10)
89
+ end
90
+ end
91
+
92
+ private
93
+ def self.log message
94
+ print "#{Time.now.utc} #{message}\n"
95
+ STDOUT.flush if @debug
96
+ end
97
+
98
+ def self.create_send_later_handler
99
+ define_singleton_method("#{@sender}_send_later") do |msg|
100
+ Kernel.const_get(msg['params']['class']).find(msg['params']['id']).send(msg['params']['method'], *msg['params']['args'])
101
+ end
102
+ end
103
+ end
104
+
105
+ class ActiveRecord::Base
106
+ def send_later(method, *args)
107
+ Insque.broadcast :send_later, {:class => self.class.name, :id => id, :method => method, :args => args }, :self
108
+ end
109
+
110
+ def self.acts_as_insque_crud(*args)
111
+ options = args.extract_options!
112
+ excluded = (options[:exclude] || []).map(&:to_s)
113
+
114
+ [:update, :create, :destroy].each do |action|
115
+ set_callback action, :after do
116
+ params = self.serializable_hash(options).delete_if {|key| (['created_at', 'updated_at'] + excluded).include? key}
117
+ Insque.broadcast :"#{self.class.to_s.underscore}_#{action}", params
118
+ end
119
+ end
120
+ end
121
+
122
+ end
@@ -0,0 +1,13 @@
1
+ namespace :insque do
2
+ desc 'Starts insque listener'
3
+ task :listener => :environment do
4
+ trap('TERM') { puts "SIGTERM"; exit 0 }
5
+ Insque.listen
6
+ end
7
+
8
+ desc 'Starts insque janitor'
9
+ task :janitor => :environment do
10
+ trap('TERM') { puts "SIGTERM"; exit 0 }
11
+ Insque.janitor
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: insque
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - gropher
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.2.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.2.2
30
+ description: Instant queue. Background processing and message driven communication
31
+ tool. Faster and simplier alternative to Resque.
32
+ email:
33
+ - grophen@gmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - LICENSE
41
+ - README.md
42
+ - Rakefile
43
+ - insque.gemspec
44
+ - lib/generators/insque/initializer_generator.rb
45
+ - lib/generators/insque/templates/insque.erb
46
+ - lib/generators/insque/templates/redis.yml
47
+ - lib/insque.rb
48
+ - lib/insque/railtie.rb
49
+ - lib/insque/version.rb
50
+ - lib/tasks/insque.rake
51
+ homepage: https://github.com/Gropher/Insque
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.24
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Instant queue. Background processing and message driven communication tool.
75
+ Faster and simplier alternative to Resque.
76
+ test_files: []
77
+ has_rdoc: