rabbit_jobs 0.0.4 → 0.0.5
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.
- data/README.md +2 -2
- data/examples/configuration.rb +0 -1
- data/lib/rabbit_jobs.rb +5 -0
- data/lib/rabbit_jobs/amqp_helpers.rb +17 -1
- data/lib/rabbit_jobs/job.rb +1 -1
- data/lib/rabbit_jobs/publisher.rb +23 -2
- data/lib/rabbit_jobs/scheduler.rb +160 -0
- data/lib/rabbit_jobs/tasks.rb +23 -7
- data/lib/rabbit_jobs/version.rb +1 -1
- data/lib/tasks/rabbit_jobs.rake +1 -1
- data/rabbit_jobs.gemspec +1 -0
- data/spec/fixtures/schedule.yml +13 -0
- data/spec/integration/scheduler_spec.rb +17 -0
- metadata +23 -7
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RabbitJobs
|
2
2
|
|
3
|
-
|
3
|
+
Schedule jobs on rabbitmq and run them in background.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -18,7 +18,7 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
TODO: Write usage instructions here
|
21
|
+
TODO: Write usage instructions here.
|
22
22
|
|
23
23
|
## Contributing
|
24
24
|
|
data/examples/configuration.rb
CHANGED
data/lib/rabbit_jobs.rb
CHANGED
@@ -10,6 +10,7 @@ require 'rabbit_jobs/logger'
|
|
10
10
|
require 'rabbit_jobs/job'
|
11
11
|
require 'rabbit_jobs/publisher'
|
12
12
|
require 'rabbit_jobs/worker'
|
13
|
+
require 'rabbit_jobs/scheduler'
|
13
14
|
|
14
15
|
module RabbitJobs
|
15
16
|
extend self
|
@@ -21,4 +22,8 @@ module RabbitJobs
|
|
21
22
|
def publish_to(routing_key, klass, opts = {}, *params)
|
22
23
|
RabbitJobs::Publisher.publish_to(routing_key, klass, opts, *params)
|
23
24
|
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module RJ
|
28
|
+
include RabbitJobs
|
24
29
|
end
|
@@ -4,7 +4,6 @@ module RabbitJobs
|
|
4
4
|
module AmqpHelpers
|
5
5
|
|
6
6
|
# Calls given block with initialized amqp
|
7
|
-
|
8
7
|
def amqp_with_exchange(&block)
|
9
8
|
raise ArgumentError unless block
|
10
9
|
|
@@ -23,6 +22,23 @@ module RabbitJobs
|
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
25
|
+
def em_amqp_with_exchange(&block)
|
26
|
+
raise ArgumentError unless block
|
27
|
+
|
28
|
+
connection = AMQP.connect(host: RabbitJobs.config.host)
|
29
|
+
channel = AMQP::Channel.new(connection)
|
30
|
+
|
31
|
+
channel.on_error do |ch, channel_close|
|
32
|
+
puts "Channel-level error: #{channel_close.reply_text}, shutting down..."
|
33
|
+
connection.close { EM.stop }
|
34
|
+
end
|
35
|
+
|
36
|
+
exchange = channel.direct(RabbitJobs.config[:exchange], RabbitJobs.config[:exchange_params])
|
37
|
+
|
38
|
+
# go work
|
39
|
+
block.call(connection, exchange)
|
40
|
+
end
|
41
|
+
|
26
42
|
def amqp_with_queue(routing_key, &block)
|
27
43
|
|
28
44
|
raise ArgumentError unless routing_key && block
|
data/lib/rabbit_jobs/job.rb
CHANGED
@@ -21,7 +21,7 @@ module RabbitJobs::Job
|
|
21
21
|
def run_perform
|
22
22
|
if @child_pid = fork
|
23
23
|
srand # Reseeding
|
24
|
-
log "Forked #{@child_pid} at #{Time.now} to process #{self.class}.perform(#{ params.map(&:inspect).join(', ') })"
|
24
|
+
RabbitJobs::Logger.log "Forked #{@child_pid} at #{Time.now} to process #{self.class}.perform(#{ params.map(&:inspect).join(', ') })"
|
25
25
|
Process.wait(@child_pid)
|
26
26
|
yield if block_given?
|
27
27
|
else
|
@@ -21,7 +21,11 @@ module RabbitJobs
|
|
21
21
|
job = klass.new(*params)
|
22
22
|
job.opts = opts
|
23
23
|
|
24
|
-
|
24
|
+
if defined?(EM) && EM.reactor_running?
|
25
|
+
em_publish_job_to(routing_key, job)
|
26
|
+
else
|
27
|
+
publish_job_to(routing_key, job)
|
28
|
+
end
|
25
29
|
end
|
26
30
|
|
27
31
|
def publish_job_to(routing_key, job)
|
@@ -33,7 +37,24 @@ module RabbitJobs
|
|
33
37
|
|
34
38
|
payload = job.payload
|
35
39
|
exchange.publish(job.payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({routing_key: routing_key})) {
|
36
|
-
connection.close {
|
40
|
+
connection.close {
|
41
|
+
EM.stop
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def em_publish_job_to(routing_key, job)
|
48
|
+
em_amqp_with_exchange do |connection, exchange|
|
49
|
+
|
50
|
+
queue = make_queue(exchange, routing_key)
|
51
|
+
|
52
|
+
job.opts['created_at'] = Time.now.to_s
|
53
|
+
|
54
|
+
payload = job.payload
|
55
|
+
exchange.publish(job.payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({routing_key: routing_key})) {
|
56
|
+
connection.close {
|
57
|
+
}
|
37
58
|
}
|
38
59
|
end
|
39
60
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'rufus/scheduler'
|
4
|
+
require 'thwait'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module RabbitJobs
|
8
|
+
class Scheduler
|
9
|
+
include AmqpHelpers
|
10
|
+
include Logger
|
11
|
+
|
12
|
+
attr_accessor :pidfile, :background, :schedule
|
13
|
+
|
14
|
+
def load_default_schedule
|
15
|
+
if defined?(Rails)
|
16
|
+
file = Rails.root.join('config/schedule.yml')
|
17
|
+
if file.file?
|
18
|
+
@schedule = YAML.load_file(file)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Pulls the schedule from Resque.schedule and loads it into the
|
24
|
+
# rufus scheduler instance
|
25
|
+
def load_schedule!
|
26
|
+
@schedule ||= load_default_schedule
|
27
|
+
|
28
|
+
raise "You should setup a schedule or place it in config/schedule.yml" unless schedule
|
29
|
+
|
30
|
+
schedule.each do |name, config|
|
31
|
+
# If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
|
32
|
+
# required for the jobs to be scheduled. If rails_env is missing, the
|
33
|
+
# job should be scheduled regardless of what ENV['RAILS_ENV'] is set
|
34
|
+
# to.
|
35
|
+
if config['rails_env'].nil? || rails_env_matches?(config)
|
36
|
+
log! "Scheduling #{name} "
|
37
|
+
interval_defined = false
|
38
|
+
interval_types = %w{cron every}
|
39
|
+
interval_types.each do |interval_type|
|
40
|
+
if !config[interval_type].nil? && config[interval_type].length > 0
|
41
|
+
rufus_scheduler.send(interval_type, config[interval_type]) do
|
42
|
+
log! "queueing #{config['class']} (#{name})"
|
43
|
+
publish_from_config(config)
|
44
|
+
end
|
45
|
+
interval_defined = true
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
unless interval_defined
|
50
|
+
log! "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns true if the given schedule config hash matches the current ENV['RAILS_ENV']
|
57
|
+
def rails_env_matches?(config)
|
58
|
+
config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/,'').split(',').include?(ENV['RAILS_ENV'])
|
59
|
+
end
|
60
|
+
|
61
|
+
# Publish a job based on a config hash
|
62
|
+
def publish_from_config(config)
|
63
|
+
args = config['args'] || config[:args] || []
|
64
|
+
klass_name = config['class'] || config[:class]
|
65
|
+
params = args.is_a?(Hash) ? [args] : Array(args)
|
66
|
+
queue = config['queue'] || config[:queue] || RabbitJobs.config.routing_keys.first
|
67
|
+
|
68
|
+
log "publishing #{config}"
|
69
|
+
RabbitJobs.publish_to(queue, klass_name.constantize, nil, *params)
|
70
|
+
rescue
|
71
|
+
log! "Failed to enqueue #{klass_name}:\n #{$!}\n params = #{params.inspect}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def rufus_scheduler
|
75
|
+
@rufus_scheduler ||= Rufus::Scheduler.start_new
|
76
|
+
end
|
77
|
+
|
78
|
+
# Stops old rufus scheduler and creates a new one. Returns the new
|
79
|
+
# rufus scheduler
|
80
|
+
def clear_schedule!
|
81
|
+
rufus_scheduler.stop
|
82
|
+
@rufus_scheduler = nil
|
83
|
+
rufus_scheduler
|
84
|
+
end
|
85
|
+
|
86
|
+
# Subscribes to channel and working on jobs
|
87
|
+
def work(time = 0)
|
88
|
+
startup
|
89
|
+
|
90
|
+
processed_count = 0
|
91
|
+
amqp_with_exchange do |connection, exchange|
|
92
|
+
load_schedule!
|
93
|
+
|
94
|
+
check_shutdown = Proc.new {
|
95
|
+
if @shutdown
|
96
|
+
connection.close {
|
97
|
+
File.delete(self.pidfile) if self.pidfile
|
98
|
+
EM.stop { exit! }
|
99
|
+
}
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
if time > 0
|
104
|
+
# for debugging
|
105
|
+
EM.add_timer(time) do
|
106
|
+
log "Stopping scheduler..."
|
107
|
+
self.shutdown
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
log "Scheduler started."
|
112
|
+
|
113
|
+
EM.add_periodic_timer(1) do
|
114
|
+
check_shutdown.call
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def shutdown
|
120
|
+
log "Stopping scheduler..."
|
121
|
+
@shutdown = true
|
122
|
+
end
|
123
|
+
|
124
|
+
def startup
|
125
|
+
# prune_dead_workers
|
126
|
+
|
127
|
+
Process.daemon(true) if self.background
|
128
|
+
|
129
|
+
if self.pidfile
|
130
|
+
File.open(self.pidfile, 'w') { |f| f << Process.pid }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fix buffering so we can `rake rj:work > resque.log` and
|
134
|
+
# get output from the child in there.
|
135
|
+
$stdout.sync = true
|
136
|
+
|
137
|
+
@shutdown = false
|
138
|
+
|
139
|
+
Signal.trap('TERM') { shutdown }
|
140
|
+
Signal.trap('INT') { shutdown! }
|
141
|
+
end
|
142
|
+
|
143
|
+
def shutdown!
|
144
|
+
shutdown
|
145
|
+
kill_child
|
146
|
+
end
|
147
|
+
|
148
|
+
def kill_child
|
149
|
+
if @job && @job.child_pid
|
150
|
+
# log! "Killing child at #{@child}"
|
151
|
+
if Kernel.system("ps -o pid,state -p #{@job.child_pid}")
|
152
|
+
Process.kill("KILL", @job.child_pid) rescue nil
|
153
|
+
else
|
154
|
+
# log! "Child #{@child} not found, restarting."
|
155
|
+
# shutdown
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/rabbit_jobs/tasks.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# require 'resque/tasks'
|
2
2
|
# will give you the resque tasks
|
3
3
|
|
4
|
+
require 'rabbit_jobs'
|
5
|
+
|
4
6
|
namespace :rj do
|
5
7
|
task :setup
|
6
8
|
|
7
9
|
desc "Start a Rabbit Jobs worker"
|
8
|
-
task :
|
10
|
+
task :worker => [ :preload, :setup ] do
|
9
11
|
require 'rabbit_jobs'
|
10
12
|
|
11
13
|
queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
|
@@ -18,10 +20,23 @@ namespace :rj do
|
|
18
20
|
# worker.very_verbose = ENV['VVERBOSE']
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
worker.work
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Start a Rabbit Jobs scheduler"
|
27
|
+
task :scheduler => [ :preload, :setup ] do
|
28
|
+
|
29
|
+
queues = (ENV['SCHEDULE']).to_s.split(',')
|
30
|
+
|
31
|
+
begin
|
32
|
+
scheduler = RabbitJobs::Scheduler.new(*queues)
|
33
|
+
scheduler.pidfile = ENV['PIDFILE']
|
34
|
+
scheduler.background = %w(yes true).include? ENV['BACKGROUND']
|
35
|
+
RabbitJobs::Logger.verbose = true if ENV['VERBOSE']
|
36
|
+
# worker.very_verbose = ENV['VVERBOSE']
|
37
|
+
end
|
38
|
+
|
39
|
+
scheduler.work
|
25
40
|
end
|
26
41
|
|
27
42
|
desc "Start multiple Resque workers. Should only be used in dev mode."
|
@@ -30,7 +45,7 @@ namespace :rj do
|
|
30
45
|
|
31
46
|
ENV['COUNT'].to_i.times do
|
32
47
|
threads << Thread.new do
|
33
|
-
system "rake resque:
|
48
|
+
system "rake resque:worker"
|
34
49
|
end
|
35
50
|
end
|
36
51
|
|
@@ -41,7 +56,8 @@ namespace :rj do
|
|
41
56
|
task :preload => :setup do
|
42
57
|
if defined?(Rails) && Rails.respond_to?(:application)
|
43
58
|
# Rails 3
|
44
|
-
Rails.application.eager_load!
|
59
|
+
# Rails.application.eager_load!
|
60
|
+
Rails.application.require_environment!
|
45
61
|
elsif defined?(Rails::Initializer)
|
46
62
|
# Rails 2.3
|
47
63
|
$rails_rake_task = false
|
data/lib/rabbit_jobs/version.rb
CHANGED
data/lib/tasks/rabbit_jobs.rake
CHANGED
data/rabbit_jobs.gemspec
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
do_a_job:
|
2
|
+
cron: "* * * * *" # run every minute
|
3
|
+
queue: queue_name
|
4
|
+
class: MyJobClass
|
5
|
+
args: "string to be passed to MyJobClass.perform"
|
6
|
+
description: "Description is not supported yet"
|
7
|
+
|
8
|
+
another_job:
|
9
|
+
every: 1h # run every 1 hour
|
10
|
+
queue: queue_name
|
11
|
+
class: MyJobClass
|
12
|
+
args: "string to be passed to MyJobClass.perform"
|
13
|
+
description: "Description is not supported yet"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RabbitJobs::Scheduler do
|
5
|
+
it 'should start with config.yml' do
|
6
|
+
scheduler = RabbitJobs::Scheduler.new
|
7
|
+
scheduler.schedule = YAML.load_file(File.expand_path('../../fixtures/schedule.yml', __FILE__))
|
8
|
+
|
9
|
+
scheduler.pidfile = '/tmp/rj_scheduler.pid'
|
10
|
+
scheduler.background = false
|
11
|
+
|
12
|
+
scheduler.work(10) # work for 1 second
|
13
|
+
|
14
|
+
puts "messages queued: " + RabbitJobs::Publisher.purge_queue('default').to_s
|
15
|
+
RabbitJobs::Publisher.purge_queue('default').should == 0
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rabbit_jobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01
|
12
|
+
date: 2012-02-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: amqp
|
16
|
-
requirement: &
|
16
|
+
requirement: &70230061017880 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0.9'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70230061017880
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70230061016460 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,18 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70230061016460
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rufus-scheduler
|
38
|
+
requirement: &70230061008760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70230061008760
|
36
47
|
description: Background jobs on RabbitMQ
|
37
48
|
email:
|
38
49
|
- lazureykis@gmail.com
|
@@ -54,6 +65,7 @@ files:
|
|
54
65
|
- lib/rabbit_jobs/job.rb
|
55
66
|
- lib/rabbit_jobs/logger.rb
|
56
67
|
- lib/rabbit_jobs/publisher.rb
|
68
|
+
- lib/rabbit_jobs/scheduler.rb
|
57
69
|
- lib/rabbit_jobs/tasks.rb
|
58
70
|
- lib/rabbit_jobs/version.rb
|
59
71
|
- lib/rabbit_jobs/worker.rb
|
@@ -61,7 +73,9 @@ files:
|
|
61
73
|
- rabbit_jobs.gemspec
|
62
74
|
- spec/fixtures/config.yml
|
63
75
|
- spec/fixtures/jobs.rb
|
76
|
+
- spec/fixtures/schedule.yml
|
64
77
|
- spec/integration/publisher_spec.rb
|
78
|
+
- spec/integration/scheduler_spec.rb
|
65
79
|
- spec/integration/worker_spec.rb
|
66
80
|
- spec/spec_helper.rb
|
67
81
|
- spec/unit/configuration_spec.rb
|
@@ -89,14 +103,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
103
|
version: '0'
|
90
104
|
requirements: []
|
91
105
|
rubyforge_project:
|
92
|
-
rubygems_version: 1.8.
|
106
|
+
rubygems_version: 1.8.10
|
93
107
|
signing_key:
|
94
108
|
specification_version: 3
|
95
109
|
summary: Background jobs on RabbitMQ
|
96
110
|
test_files:
|
97
111
|
- spec/fixtures/config.yml
|
98
112
|
- spec/fixtures/jobs.rb
|
113
|
+
- spec/fixtures/schedule.yml
|
99
114
|
- spec/integration/publisher_spec.rb
|
115
|
+
- spec/integration/scheduler_spec.rb
|
100
116
|
- spec/integration/worker_spec.rb
|
101
117
|
- spec/spec_helper.rb
|
102
118
|
- spec/unit/configuration_spec.rb
|