rabbit_jobs 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RabbitJobs
2
2
 
3
- TODO: Write a gem description
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
 
@@ -14,7 +14,6 @@ RabbitJobs.configure do |c|
14
14
  end
15
15
 
16
16
  puts JSON.pretty_generate(RabbitJobs.config.to_hash)
17
-
18
17
  puts JSON.pretty_generate(RabbitJobs.config.queues)
19
18
 
20
19
  class MyJob < RabbitJobs::Job
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
@@ -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
- publish_job_to(routing_key, job)
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 { EM.stop }
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
@@ -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 :work => [ :preload, :setup ] do
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
- # worker.log "Starting worker #{worker.pid}"
22
- # worker.verbose = true
23
- worker.work 10
24
- # worker.work(ENV['INTERVAL'] || 5) # interval, will block
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:work"
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
@@ -1,3 +1,3 @@
1
1
  module RabbitJobs
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -1,3 +1,3 @@
1
-
2
1
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
2
+
3
3
  require 'rabbit_jobs/tasks'
data/rabbit_jobs.gemspec CHANGED
@@ -19,4 +19,5 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency "amqp", "~> 0.9"
21
21
  gem.add_dependency "rake"
22
+ gem.add_dependency "rufus-scheduler", "~> 2.0"
22
23
  end
@@ -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
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-31 00:00:00.000000000 Z
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: &70340051105620 !ruby/object:Gem::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: *70340051105620
24
+ version_requirements: *70230061017880
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70340051105000 !ruby/object:Gem::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: *70340051105000
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.15
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