rj 0.0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .DS_Store
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'rabbit_jobs', :path => './'
6
+
7
+ group :development do
8
+ gem 'rspec', '~> 2.8'
9
+ gem 'rr'
10
+ gem 'autotest'
11
+ # gem 'fs-event'
12
+ gem 'simplecov', require: false
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Pavel Lazureykis
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.
@@ -0,0 +1,29 @@
1
+ # RabbitJobs
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rabbit_jobs'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rabbit_jobs
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'bundler/setup'
3
+ require 'rabbit_jobs'
4
+ require 'json'
5
+
6
+ RabbitJobs.configure do |c|
7
+ c.host "127.0.0.1"
8
+
9
+ c.exchange 'test_exchange', durable: true, auto_delete: false
10
+
11
+ c.queue 'rabbit_jobs_test1', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
12
+ c.queue 'rabbit_jobs_test2', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
13
+ c.queue 'rabbit_jobs_test3', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
14
+ end
15
+
16
+ puts JSON.pretty_generate(RabbitJobs.config.to_hash)
17
+
18
+ puts JSON.pretty_generate(RabbitJobs.config.queues)
19
+
20
+ class MyJob < RabbitJobs::Job
21
+
22
+ expires_in 60 # dont perform this job after 60 seconds
23
+
24
+ def self.perform(time)
25
+ puts "This job was published at #{}"
26
+ end
27
+ end
28
+
29
+ RabbitJobs.publish_to('rabbit_jobs_test1', MyJob, Time.now)
@@ -0,0 +1,24 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'rabbit_jobs/version'
4
+
5
+ require 'rabbit_jobs/helpers'
6
+ require 'rabbit_jobs/amqp_helpers'
7
+ require 'rabbit_jobs/configuration'
8
+ require 'rabbit_jobs/logger'
9
+
10
+ require 'rabbit_jobs/job'
11
+ require 'rabbit_jobs/publisher'
12
+ require 'rabbit_jobs/worker'
13
+
14
+ module RabbitJobs
15
+ extend self
16
+
17
+ def publish(klass, opts = {}, *params)
18
+ RabbitJobs::Publisher.publish(klass, opts, *params)
19
+ end
20
+
21
+ def publish_to(routing_key, klass, opts = {}, *params)
22
+ RabbitJobs::Publisher.publish_to(routing_key, klass, opts, *params)
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module RabbitJobs
4
+ module AmqpHelpers
5
+
6
+ # Calls given block with initialized amqp
7
+
8
+ def amqp_with_exchange(&block)
9
+ raise ArgumentError unless block
10
+
11
+ AMQP.start(host: RabbitJobs.config.host) do |connection|
12
+ channel = AMQP::Channel.new(connection)
13
+
14
+ channel.on_error do |ch, channel_close|
15
+ puts "Channel-level error: #{channel_close.reply_text}, shutting down..."
16
+ connection.close { EM.stop }
17
+ end
18
+
19
+ exchange = channel.direct(RabbitJobs.config[:exchange], RabbitJobs.config[:exchange_params])
20
+
21
+ # go work
22
+ block.call(connection, exchange)
23
+ end
24
+ end
25
+
26
+ def amqp_with_queue(routing_key, &block)
27
+
28
+ raise ArgumentError unless routing_key && block
29
+
30
+ amqp_with_exchange do |connection, exchange|
31
+ queue = exchange.channel.queue(RabbitJobs.config.queue_name(routing_key), RabbitJobs.config[:queues][routing_key])
32
+ queue.bind(exchange, :routing_key => routing_key)
33
+
34
+ # go work
35
+ block.call(connection, queue)
36
+ end
37
+ end
38
+
39
+ def make_queue(exchange, routing_key)
40
+ queue = exchange.channel.queue(RabbitJobs.config.queue_name(routing_key), RabbitJobs.config[:queues][routing_key])
41
+ queue.bind(exchange, :routing_key => routing_key)
42
+ queue
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,124 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'yaml'
3
+
4
+ module RabbitJobs
5
+
6
+ extend self
7
+
8
+ def configure(&block)
9
+ @@configuration ||= Configuration.new
10
+ block.call(@@configuration)
11
+ end
12
+
13
+ def config
14
+ @@configuration ||= load_config
15
+ end
16
+
17
+ def load_config
18
+ self.configure do |c|
19
+ c.host 'localhost'
20
+ c.exchange 'rabbit_jobs', auto_delete: false, durable: true
21
+ c.queue 'default', auto_delete: false, ack: true, durable: true
22
+ end
23
+ @@configuration
24
+ end
25
+
26
+ class Configuration
27
+ include Helpers
28
+
29
+ DEFAULT_QUEUE_PARAMS = {
30
+ auto_delete: false,
31
+ durable: true,
32
+ ack: true
33
+ }
34
+
35
+ DEFAULT_EXCHANGE_PARAMS = {
36
+ auto_delete: false,
37
+ durable: true
38
+ }
39
+
40
+ DEFAULT_MESSAGE_PARAMS = {
41
+ persistent: true,
42
+ nowait: false,
43
+ immediate: false
44
+ }
45
+
46
+ def to_hash
47
+ @data.dup
48
+ end
49
+
50
+ def initialize
51
+ @data = {
52
+ host: 'localhost',
53
+ exchange: 'rabbit_jobs',
54
+ exchange_params: DEFAULT_EXCHANGE_PARAMS,
55
+ queues: {}
56
+ }
57
+ end
58
+
59
+ def [](name)
60
+ @data[name]
61
+ end
62
+
63
+ def host(value = nil)
64
+ if value
65
+ raise ArgumentError unless value.is_a?(String) && value != ""
66
+ @data[:host] = value.to_s
67
+ else
68
+ @data[:host]
69
+ end
70
+ end
71
+
72
+ def exchange(value = nil, params = {})
73
+ if value
74
+ raise ArgumentError unless value.is_a?(String) && value != ""
75
+ @data[:exchange] = value.downcase
76
+ @data[:exchange_params] = DEFAULT_EXCHANGE_PARAMS.merge(params)
77
+ else
78
+ @data[:exchange]
79
+ end
80
+ end
81
+
82
+ def queue(name, params = {})
83
+ raise ArgumentError.new("name is #{name.inspect}") unless name && name.is_a?(String) && name != ""
84
+ raise ArgumentError.new("params is #{params.inspect}") unless params && params.is_a?(Hash)
85
+
86
+ name = name.downcase
87
+
88
+ if @data[:queues][name]
89
+ @data[:queues][name].merge!(params)
90
+ else
91
+ @data[:queues][name] = DEFAULT_QUEUE_PARAMS.merge(params)
92
+ end
93
+ end
94
+
95
+ def routing_keys
96
+ @data[:queues].keys
97
+ end
98
+
99
+ def queue_name(routing_key)
100
+ [@data[:exchange], routing_key].join('#')
101
+ end
102
+
103
+ def load_file(filename)
104
+ load_yaml(File.read(filename))
105
+ end
106
+
107
+ def load_yaml(text)
108
+ convert_yaml_config(YAML.load(text))
109
+ end
110
+
111
+ def convert_yaml_config(yaml)
112
+ if yaml['rabbit_jobs']
113
+ convert_yaml_config(yaml['rabbit_jobs'])
114
+ else
115
+ @data = {host: nil, exchange: nil, queues: {}}
116
+ host yaml['host']
117
+ exchange yaml['exchange'], symbolize_keys!(yaml['exchange_params'])
118
+ yaml['queues'].each do |name, params|
119
+ queue name, symbolize_keys!(params) || {}
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module RabbitJobs
4
+ module Helpers
5
+ def symbolize_keys!(hash)
6
+ hash.inject({}) do |options, (key, value)|
7
+ options[(key.to_sym rescue key) || key] = value
8
+ options
9
+ end
10
+ end
11
+
12
+
13
+ # Tries to find a constant with the name specified in the argument string:
14
+ #
15
+ # constantize("Module") # => Module
16
+ # constantize("Test::Unit") # => Test::Unit
17
+ #
18
+ # The name is assumed to be the one of a top-level constant, no matter
19
+ # whether it starts with "::" or not. No lexical context is taken into
20
+ # account:
21
+ #
22
+ # C = 'outside'
23
+ # module M
24
+ # C = 'inside'
25
+ # C # => 'inside'
26
+ # constantize("C") # => 'outside', same as ::C
27
+ # end
28
+ #
29
+ # NameError is raised when the constant is unknown.
30
+ def constantize(camel_cased_word)
31
+ camel_cased_word = camel_cased_word.to_s
32
+
33
+ if camel_cased_word.include?('-')
34
+ camel_cased_word = classify(camel_cased_word)
35
+ end
36
+
37
+ names = camel_cased_word.split('::')
38
+ names.shift if names.empty? || names.first.empty?
39
+
40
+ constant = Object
41
+ names.each do |name|
42
+ args = Module.method(:const_get).arity != 1 ? [false] : []
43
+
44
+ if constant.const_defined?(name, *args)
45
+ constant = constant.const_get(name)
46
+ else
47
+ constant = constant.const_missing(name)
48
+ end
49
+ end
50
+ constant
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,88 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'json'
3
+ require 'digest/md5'
4
+
5
+ module RabbitJobs::Job
6
+ extend RabbitJobs::Helpers
7
+ extend RabbitJobs::Logger
8
+ extend self
9
+
10
+ def self.included(base)
11
+ include RabbitJobs::Logger
12
+ base.extend (ClassMethods)
13
+
14
+ def initialize(*perform_params)
15
+ self.params = *perform_params
16
+ self.opts = {}
17
+ end
18
+
19
+ attr_accessor :params, :opts, :child_pid
20
+
21
+ def run_perform
22
+ if @child_pid = fork
23
+ srand # Reseeding
24
+ log "Forked #{@child_pid} at #{Time.now} to process #{self.class}.perform(#{ params.map(&:inspect).join(', ') })"
25
+ Process.wait(@child_pid)
26
+ yield if block_given?
27
+ else
28
+ begin
29
+ # log 'before perform'
30
+ self.class.perform(*params)
31
+ # log 'after perform'
32
+ rescue
33
+ puts $!.inspect
34
+ end
35
+ exit!
36
+ end
37
+ end
38
+
39
+ def payload
40
+ {'class' => self.class.to_s, 'opts' => (self.opts || {}), 'params' => params}.to_json
41
+ # ([self.class.to_s] + params).to_json
42
+ end
43
+
44
+ def expires_in
45
+ self.class.rj_expires_in
46
+ end
47
+
48
+ def expires?
49
+ !!self.expires_in
50
+ end
51
+
52
+ def expired?
53
+ if self.opts['expires_at']
54
+ Time.now > Time.new(opts['expires_at'])
55
+ elsif expires? && opts['created_at']
56
+ Time.now > (Time.new(opts['created_at']) + expires_in)
57
+ else
58
+ false
59
+ end
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+ attr_accessor :rj_expires_in
65
+
66
+ # DSL method for jobs
67
+ def expires_in(seconds)
68
+ @rj_expires_in = seconds
69
+ end
70
+ end
71
+
72
+ def self.parse(payload)
73
+ begin
74
+ encoded = JSON.parse(payload)
75
+ job_klass = constantize(encoded['class'])
76
+ job = job_klass.new(*encoded['params'])
77
+ job.opts = encoded['opts']
78
+ job
79
+ rescue
80
+ log "JOB INIT ERROR at #{Time.now.to_s}:"
81
+ log $!.inspect
82
+ log $!.backtrace
83
+ log "message: #{payload.inspect}"
84
+ # Mailer.send(klass_name, params, $!)
85
+ # raise $!
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,18 @@
1
+ module RabbitJobs
2
+ module Logger
3
+ extend self
4
+
5
+ def log(string)
6
+ puts string
7
+ end
8
+
9
+ def log!(string)
10
+ @@verbose ||= false
11
+ log(string) if RabbitJobs::Logger.verbose
12
+ end
13
+
14
+ class << self
15
+ attr_accessor :verbose
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'json'
4
+ require 'amqp'
5
+ require 'eventmachine'
6
+
7
+ module RabbitJobs
8
+ module Publisher
9
+ extend self
10
+ extend AmqpHelpers
11
+
12
+ def publish(klass, opts = {}, *params)
13
+ key = RabbitJobs.config.routing_keys.first
14
+ publish_to(key, klass, opts, *params)
15
+ end
16
+
17
+ def publish_to(routing_key, klass, opts = {}, *params)
18
+ raise ArgumentError unless klass && routing_key
19
+ opts ||= {}
20
+
21
+ job = klass.new(*params)
22
+ job.opts = opts
23
+
24
+ publish_job_to(routing_key, job)
25
+ end
26
+
27
+ def publish_job_to(routing_key, job)
28
+ amqp_with_exchange do |connection, exchange|
29
+
30
+ queue = make_queue(exchange, routing_key)
31
+
32
+ job.opts['created_at'] = Time.now.to_s
33
+
34
+ payload = job.payload
35
+ exchange.publish(job.payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({routing_key: routing_key})) {
36
+ connection.close { EM.stop }
37
+ }
38
+ end
39
+ end
40
+
41
+ def purge_queue(routing_key)
42
+ raise ArgumentError unless routing_key
43
+
44
+ amqp_with_queue(routing_key) do |connection, queue|
45
+ queue.status do |number_of_messages, number_of_consumers|
46
+ queue.purge {
47
+ connection.close {
48
+ EM.stop
49
+ return number_of_messages
50
+ }
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,51 @@
1
+ # require 'resque/tasks'
2
+ # will give you the resque tasks
3
+
4
+ namespace :rj do
5
+ task :setup
6
+
7
+ desc "Start a Rabbit Jobs worker"
8
+ task :work => [ :preload, :setup ] do
9
+ require 'rabbit_jobs'
10
+
11
+ queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
12
+
13
+ begin
14
+ worker = RabbitJobs::Worker.new(*queues)
15
+ worker.pidfile = ENV['PIDFILE']
16
+ worker.background = %w(yes true).include? ENV['BACKGROUND']
17
+ RabbitJobs::Logger.verbose = true if ENV['VERBOSE']
18
+ # worker.very_verbose = ENV['VVERBOSE']
19
+ end
20
+
21
+ # worker.log "Starting worker #{worker.pid}"
22
+ # worker.verbose = true
23
+ worker.work 10
24
+ # worker.work(ENV['INTERVAL'] || 5) # interval, will block
25
+ end
26
+
27
+ desc "Start multiple Resque workers. Should only be used in dev mode."
28
+ task :workers do
29
+ threads = []
30
+
31
+ ENV['COUNT'].to_i.times do
32
+ threads << Thread.new do
33
+ system "rake resque:work"
34
+ end
35
+ end
36
+
37
+ threads.each { |thread| thread.join }
38
+ end
39
+
40
+ # Preload app files if this is Rails
41
+ task :preload => :setup do
42
+ if defined?(Rails) && Rails.respond_to?(:application)
43
+ # Rails 3
44
+ Rails.application.eager_load!
45
+ elsif defined?(Rails::Initializer)
46
+ # Rails 2.3
47
+ $rails_rake_task = false
48
+ Rails::Initializer.run :load_application_classes
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module RabbitJobs
2
+ VERSION = "0.0.4.1"
3
+ end
@@ -0,0 +1,119 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module RabbitJobs
4
+ class Worker
5
+ include AmqpHelpers
6
+ include Logger
7
+
8
+ attr_accessor :pidfile, :background
9
+
10
+ # Workers should be initialized with an array of string queue
11
+ # names. The order is important: a Worker will check the first
12
+ # queue given for a job. If none is found, it will check the
13
+ # second queue name given. If a job is found, it will be
14
+ # processed. Upon completion, the Worker will again check the
15
+ # first queue given, and so forth. In this way the queue list
16
+ # passed to a Worker on startup defines the priorities of queues.
17
+ #
18
+ # If passed a single "*", this Worker will operate on all queues
19
+ # in alphabetical order. Queues can be dynamically added or
20
+ # removed without needing to restart workers using this method.
21
+ def initialize(*queues)
22
+ @queues = queues.map { |queue| queue.to_s.strip }.flatten.uniq
23
+ if @queues == ['*'] || @queues.empty?
24
+ @queues = RabbitJobs.config.routing_keys
25
+ end
26
+ end
27
+
28
+ def queues
29
+ @queues || ['default']
30
+ end
31
+
32
+ # Subscribes to channel and working on jobs
33
+ def work(time = 0)
34
+ startup
35
+
36
+ processed_count = 0
37
+ amqp_with_exchange do |connection, exchange|
38
+ exchange.channel.prefetch(1)
39
+
40
+ check_shutdown = Proc.new {
41
+ if @shutdown
42
+ log "Processed jobs: #{processed_count}"
43
+ log "Stopping worker..."
44
+
45
+ connection.close {
46
+ File.delete(self.pidfile) if self.pidfile
47
+ EM.stop { exit! }
48
+ }
49
+ end
50
+ }
51
+
52
+ queues.each do |routing_key|
53
+ queue = make_queue(exchange, routing_key)
54
+
55
+ log "Worker ##{Process.pid} <= #{exchange.name}##{routing_key}"
56
+
57
+ queue.subscribe(ack: true) do |metadata, payload|
58
+ @job = RabbitJobs::Job.parse(payload)
59
+ @job.run_perform unless @job.expired?
60
+ metadata.ack
61
+ processed_count += 1
62
+ check_shutdown.call
63
+ end
64
+ end
65
+
66
+ if time > 0
67
+ # for debugging
68
+ EM.add_timer(time) do
69
+ self.shutdown
70
+ end
71
+ end
72
+
73
+ EM.add_periodic_timer(1) do
74
+ check_shutdown.call
75
+ end
76
+ end
77
+ end
78
+
79
+ def shutdown
80
+ @shutdown = true
81
+ end
82
+
83
+ def startup
84
+ # prune_dead_workers
85
+
86
+ Process.daemon(true) if self.background
87
+
88
+ if self.pidfile
89
+ File.open(self.pidfile, 'w') { |f| f << Process.pid }
90
+ end
91
+
92
+ # Fix buffering so we can `rake rj:work > resque.log` and
93
+ # get output from the child in there.
94
+ $stdout.sync = true
95
+
96
+ @shutdown = false
97
+
98
+ Signal.trap('TERM') { shutdown }
99
+ Signal.trap('INT') { shutdown! }
100
+ end
101
+
102
+ def shutdown!
103
+ shutdown
104
+ kill_child
105
+ end
106
+
107
+ def kill_child
108
+ if @job && @job.child_pid
109
+ # log! "Killing child at #{@child}"
110
+ if Kernel.system("ps -o pid,state -p #{@job.child_pid}")
111
+ Process.kill("KILL", @job.child_pid) rescue nil
112
+ else
113
+ # log! "Child #{@child} not found, restarting."
114
+ # shutdown
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,3 @@
1
+
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
3
+ require 'rabbit_jobs/tasks'
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'rabbit_jobs/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ["Pavel Lazureykis"]
7
+ gem.email = ["lazureykis@gmail.com"]
8
+ gem.description = %q{Background jobs on RabbitMQ}
9
+ gem.summary = %q{Background jobs on RabbitMQ}
10
+ gem.homepage = ""
11
+ gem.date = Time.now.strftime('%Y-%m-%d')
12
+
13
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.name = "rj"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = RabbitJobs::VERSION
19
+
20
+ gem.add_dependency "amqp", "~> 0.9"
21
+ gem.add_dependency "rake"
22
+ end
@@ -0,0 +1,17 @@
1
+ rabbit_jobs:
2
+ host: example.com
3
+ exchange: 'my_exchange'
4
+ exchange_params:
5
+ durable: true
6
+ auto_delete: false
7
+ queues:
8
+ durable_queue:
9
+ durable: true
10
+ auto_delete: false
11
+ ack: true
12
+ arguments:
13
+ x-ha-policy: all
14
+ fast_queue:
15
+ durable: false
16
+ auto_delete: true
17
+ ack: false
@@ -0,0 +1,29 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ class TestJob
4
+ include RabbitJobs::Job
5
+ end
6
+
7
+ class PrintTimeJob
8
+ include RabbitJobs::Job
9
+
10
+ def self.perform(time)
11
+ puts "Running job queued at #{time}"
12
+ end
13
+ end
14
+
15
+ class JobWithExpire
16
+ include RabbitJobs::Job
17
+ expires_in 60*60 # expires in 1 hour
18
+ def self.perform
19
+
20
+ end
21
+ end
22
+
23
+ class ExpiredJob
24
+ include RabbitJobs::Job
25
+
26
+ def self.perform
27
+
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'json'
4
+
5
+ describe RabbitJobs::Publisher do
6
+
7
+ before(:each) do
8
+ queue_name = 'test'
9
+ RabbitJobs.configure do |c|
10
+ c.exchange 'test'
11
+ c.queue 'rspec_queue'
12
+ end
13
+ end
14
+
15
+ it 'should publish message to queue' do
16
+ RabbitJobs.publish(TestJob, nil, 'some', 'other', 'params')
17
+ RabbitJobs::Publisher.purge_queue('rspec_queue').should == 1
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ require 'eventmachine'
5
+ describe RabbitJobs::Worker do
6
+ it 'should listen for messages' do
7
+ RabbitJobs.configure do |c|
8
+ c.exchange 'test_durable', auto_delete: false, durable: true
9
+ c.queue 'rspec_durable_queue', auto_delete: false, durable: true, ack: true
10
+ end
11
+
12
+ 5.times { RabbitJobs.publish(PrintTimeJob, nil, Time.now) }
13
+ 5.times { RabbitJobs.publish(ExpiredJob, { :expires_at => Time.now - 10 }) }
14
+ worker = RabbitJobs::Worker.new
15
+
16
+ worker.work(1) # work for 1 second
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ add_filter "spec" # ignore spec files
5
+ end
6
+
7
+ require 'rr'
8
+
9
+ require 'rabbit_jobs'
10
+
11
+ require 'fixtures/jobs'
12
+
13
+ RSpec.configure do |config|
14
+ config.mock_with :rr
15
+ # or if that doesn't work due to a version incompatibility
16
+ # config.mock_with RR::Adapters::Rspec
17
+
18
+ config.before(:each) do
19
+ # clear config options
20
+ RabbitJobs.class_variable_set '@@configuration', nil
21
+ end
22
+ end
@@ -0,0 +1,89 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe RabbitJobs::Configuration do
5
+ it 'builds configuration from configure block' do
6
+ RabbitJobs.configure do |c|
7
+ c.host "somehost.lan"
8
+
9
+ c.exchange 'my_exchange', durable: true, auto_delete: false
10
+
11
+ c.queue 'durable_queue', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
12
+ c.queue 'fast_queue', durable: false, auto_delete: true, ack: false
13
+ end
14
+
15
+ RabbitJobs.config.to_hash.should == {
16
+ host: "somehost.lan",
17
+ exchange: "my_exchange",
18
+ exchange_params: {
19
+ durable: true,
20
+ auto_delete: false
21
+ },
22
+ queues: {
23
+ "durable_queue" => {
24
+ durable: true,
25
+ auto_delete: false,
26
+ ack: true,
27
+ arguments: {"x-ha-policy"=>"all"}
28
+ },
29
+ "fast_queue" => {
30
+ durable: false,
31
+ auto_delete: true,
32
+ ack: false
33
+ },
34
+ }
35
+ }
36
+ end
37
+
38
+ it 'builds configuration from yaml' do
39
+ RabbitJobs.config.load_file(File.expand_path('../../fixtures/config.yml', __FILE__))
40
+
41
+ RabbitJobs.config.to_hash.should == {
42
+ host: "example.com",
43
+ exchange: "my_exchange",
44
+ exchange_params: {
45
+ durable: true,
46
+ auto_delete: false
47
+ },
48
+ queues: {
49
+ "durable_queue" => {
50
+ durable: true,
51
+ auto_delete: false,
52
+ ack: true,
53
+ arguments: {"x-ha-policy"=>"all"}
54
+ },
55
+ "fast_queue" => {
56
+ durable: false,
57
+ auto_delete: true,
58
+ ack: false
59
+ }
60
+ }
61
+ }
62
+ end
63
+
64
+ it 'use default config' do
65
+ RabbitJobs.config.to_hash.should == {
66
+ host: "localhost",
67
+ exchange: "rabbit_jobs",
68
+ exchange_params: {
69
+ auto_delete: false,
70
+ durable: true
71
+ },
72
+ queues: {
73
+ "default" => {
74
+ auto_delete: false,
75
+ ack: true,
76
+ durable: true
77
+ }
78
+ }
79
+ }
80
+ end
81
+
82
+ it 'returns settings on some methods' do
83
+ RabbitJobs.config.host.should == 'localhost'
84
+ RabbitJobs.config[:host].should == 'localhost'
85
+ RabbitJobs.config.routing_keys.should == ['default']
86
+ RabbitJobs.config.exchange.should == 'rabbit_jobs'
87
+ RabbitJobs.config.queue_name('default').should == 'rabbit_jobs#default'
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe RabbitJobs::Job do
5
+ it 'should parse class and params' do
6
+ job = RabbitJobs::Job.parse({class: 'TestJob', params: [1,2,3]}.to_json)
7
+ job.params.should == [1, 2, 3]
8
+ end
9
+
10
+ it 'should understand expires_in' do
11
+ job = JobWithExpire.new(1, 2, 3)
12
+ job.expires_in.should == 60*60
13
+ job.expires?.should == true
14
+ end
15
+
16
+ context 'job expiration' do
17
+ it 'should expire job by expires_in option' do
18
+ job = TestJob.new
19
+ job.opts['expires_at'] = (Time.now - 10).to_s
20
+ job.expired?.should == true
21
+ end
22
+
23
+ it 'should expire job by expires_in option in job class and current_time' do
24
+ job = JobWithExpire.new(1, 2, 3)
25
+ job.opts['created_at'] = (Time.now - job.expires_in - 10).to_s
26
+ job.expired?.should == true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe RabbitJobs::Logger do
5
+ it '#log should write messages to stdout' do
6
+ mock($stdout).puts "hello"
7
+ RabbitJobs::Logger.log("hello")
8
+ end
9
+
10
+ it '#log! should not write messages to stdout in normal mode' do
11
+ RabbitJobs::Logger.verbose = false
12
+
13
+ dont_allow(RabbitJobs::Logger).log("hello")
14
+ RabbitJobs::Logger.log!("hello")
15
+ end
16
+
17
+ it '#log! should write messages to stdout in verbose mode' do
18
+ RabbitJobs::Logger.verbose = true
19
+
20
+ mock(RabbitJobs::Logger).log("hello")
21
+ RabbitJobs::Logger.log!("hello")
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ describe RabbitJobs do
4
+ it 'should pass publish methods to publisher' do
5
+ mock(RabbitJobs::Publisher).publish(TestJob, nil, 1, 2, "string")
6
+ RabbitJobs.publish(TestJob, nil, 1, 2, "string")
7
+
8
+ mock(RabbitJobs::Publisher).publish_to('default_queue', TestJob, nil, 1, 2, "string")
9
+ RabbitJobs.publish_to('default_queue', TestJob, nil, 1, 2, "string")
10
+ end
11
+ end
@@ -0,0 +1,60 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe RabbitJobs::Worker do
5
+ describe 'methods' do
6
+ before :each do
7
+ @worker = RabbitJobs::Worker.new
8
+ end
9
+
10
+ it '#initialize with default options' do
11
+ @worker.queues.should == ['default']
12
+ end
13
+
14
+ it '#startup should set @shutdown to false' do
15
+ @worker.instance_variable_get('@shutdown').should_not == true
16
+
17
+ mock(Signal).trap('TERM')
18
+ mock(Signal).trap('INT')
19
+
20
+ @worker.startup
21
+
22
+ @worker.instance_variable_get('@shutdown').should_not == true
23
+ end
24
+
25
+ it '#startup should write process id to file' do
26
+ mock(Signal).trap('TERM')
27
+ mock(Signal).trap('INT')
28
+
29
+ filename = 'test_worker.pid'
30
+ mock(File).open(filename, 'w') {}
31
+ @worker.pidfile = filename
32
+ @worker.startup
33
+ @worker.pidfile.should == filename
34
+ end
35
+
36
+ it '#shutdown should set @shutdown to true' do
37
+ @worker.instance_variable_get('@shutdown').should_not == true
38
+ @worker.shutdown
39
+ @worker.instance_variable_get('@shutdown').should == true
40
+ end
41
+
42
+ it '#shutdown! should kill child process' do
43
+ mock(@worker.kill_child)
44
+ mock(@worker.shutdown)
45
+
46
+ @worker.shutdown!
47
+ end
48
+
49
+ it '#kill_child' do
50
+ job = TestJob.new()
51
+ job.instance_variable_set '@child_pid', 123123
52
+ @worker.instance_variable_set('@job', job)
53
+
54
+ mock(Kernel).system("ps -o pid,state -p #{123123}") { true }
55
+ mock(Process).kill("KILL", 123123)
56
+
57
+ @worker.kill_child
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rj
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pavel Lazureykis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: amqp
16
+ requirement: &70272887573820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70272887573820
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70272887573200 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70272887573200
36
+ description: Background jobs on RabbitMQ
37
+ email:
38
+ - lazureykis@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - .rspec
45
+ - Gemfile
46
+ - LICENSE
47
+ - README.md
48
+ - Rakefile
49
+ - examples/configuration.rb
50
+ - lib/rabbit_jobs.rb
51
+ - lib/rabbit_jobs/amqp_helpers.rb
52
+ - lib/rabbit_jobs/configuration.rb
53
+ - lib/rabbit_jobs/helpers.rb
54
+ - lib/rabbit_jobs/job.rb
55
+ - lib/rabbit_jobs/logger.rb
56
+ - lib/rabbit_jobs/publisher.rb
57
+ - lib/rabbit_jobs/tasks.rb
58
+ - lib/rabbit_jobs/version.rb
59
+ - lib/rabbit_jobs/worker.rb
60
+ - lib/tasks/rabbit_jobs.rake
61
+ - rabbit_jobs.gemspec
62
+ - spec/fixtures/config.yml
63
+ - spec/fixtures/jobs.rb
64
+ - spec/integration/publisher_spec.rb
65
+ - spec/integration/worker_spec.rb
66
+ - spec/spec_helper.rb
67
+ - spec/unit/configuration_spec.rb
68
+ - spec/unit/job_spec.rb
69
+ - spec/unit/logger_spec.rb
70
+ - spec/unit/rabbit_jobs_spec.rb
71
+ - spec/unit/worker_spec.rb
72
+ homepage: ''
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 1.8.15
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Background jobs on RabbitMQ
96
+ test_files:
97
+ - spec/fixtures/config.yml
98
+ - spec/fixtures/jobs.rb
99
+ - spec/integration/publisher_spec.rb
100
+ - spec/integration/worker_spec.rb
101
+ - spec/spec_helper.rb
102
+ - spec/unit/configuration_spec.rb
103
+ - spec/unit/job_spec.rb
104
+ - spec/unit/logger_spec.rb
105
+ - spec/unit/rabbit_jobs_spec.rb
106
+ - spec/unit/worker_spec.rb