jobster 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b62587aac678545a75383e10bb98737ec8ddc0b2
4
- data.tar.gz: 83f880bacae8180651f0cbeae92fee0f42937560
3
+ metadata.gz: 30f6aef2178e0d7768841444674349210d567b03
4
+ data.tar.gz: f0d28eb0e772a1ec33b28bb9fc108feaf6521b45
5
5
  SHA512:
6
- metadata.gz: cd0fece9c0900cb682360535585e6731441194afa46c9605c98f4befedf2aef3b09efe6090261cee9841d356bfe82e32b76c297e2b10e2be0bbba468b23ef775
7
- data.tar.gz: e1dc4e0823f5b1e1cc35d861ebdbcd00b8d912357c6ea01436f8667bbf79bef8b63192af80cebd1a038c7a406a93838836d670a4ec31159b7d0d508ad5a8d83f
6
+ metadata.gz: 2d58fc83c4df5ed65e71c1e6fe54d0013cee2d567cf26b48d4def603a4f749db523365ee03d2d596fd54e72207d765b0987526f47df7781d0d3736206bed1825
7
+ data.tar.gz: 335bae815fea0868611fa84740a1ef2b870aca0a5174c3893d2e6cd34bd1a6d0ef9166375c17112a1828f1f37201ee3153c0a702c024f610a8e53830174137ac
@@ -2,46 +2,45 @@ require 'bunny'
2
2
  require 'jobster/job'
3
3
  require 'jobster/worker'
4
4
  require 'jobster/version'
5
+ require 'jobster/config'
5
6
 
6
7
  module Jobster
7
8
 
8
- class << self
9
+ def self.config
10
+ @config ||= Config.new
11
+ end
9
12
 
10
- def bunny
11
- @bunny ||= begin
12
- connection = Bunny.new
13
- connection.start
14
- connection
15
- end
16
- end
17
- attr_writer :bunny
13
+ def self.configure(&block)
14
+ block.call(self.config)
15
+ end
18
16
 
19
- def logger
20
- @logger ||= Logger.new(STDOUT)
21
- end
22
- attr_writer :logger
17
+ def self.channel
18
+ @channel ||= config.bunny.create_channel(nil, config.worker_threads)
19
+ end
23
20
 
24
- def ttl
25
- @ttl ||= 60 * 60 * 10
26
- end
27
- attr_writer :ttl
21
+ def self.exchange
22
+ @exchange ||= channel.exchange(config.exchange_name, :type => :direct, :durable => true, :auto_delete => false)
23
+ end
28
24
 
29
- def queue_prefix
30
- @queue_prefix ||= "jobster"
31
- end
32
- attr_writer :queue_prefix
25
+ def self.delay_exchange
26
+ @delay_exchange ||= channel.exchange(config.delay_exchange_name, :type => :fanout, :durable => true, :auto_delete => false)
27
+ end
33
28
 
34
- def channel
35
- @channel ||= bunny.create_channel
29
+ def self.delay_queue
30
+ @delay_queue ||= begin
31
+ queue = channel.queue(config.delay_queue_name, :durable => true, :auto_delete => false, :arguments => {'x-dead-letter-exchange' => config.exchange_name})
32
+ queue.bind(delay_exchange)
33
+ queue
36
34
  end
35
+ end
37
36
 
38
- def queue(name)
39
- @queues ||= {}
40
- @queues[name] ||= begin
41
- channel.queue("#{queue_prefix}-#{name}", :durable => true, :arguments => {'x-message-ttl' => self.ttl})
42
- end
37
+ def self.queue(name)
38
+ @queues ||= {}
39
+ @queues[name] ||= begin
40
+ queue = channel.queue("#{config.queue_name_prefix}-#{name}", :durable => true, :auto_delete => false)
41
+ queue.bind(exchange, :routing_key => name)
42
+ queue
43
43
  end
44
-
45
44
  end
46
45
 
47
46
  end
@@ -0,0 +1,61 @@
1
+ module Jobster
2
+ class Config
3
+
4
+ def bunny
5
+ @bunny ||= begin
6
+ connection = Bunny.new
7
+ connection.start
8
+ connection
9
+ end
10
+ end
11
+ attr_writer :bunny
12
+
13
+ def logger
14
+ @logger ||= Logger.new(STDOUT)
15
+ end
16
+ attr_writer :logger
17
+
18
+ def exchange_name
19
+ @exchange_name || "jobster"
20
+ end
21
+ attr_writer :exchange_name
22
+
23
+ def delay_exchange_name
24
+ @delay_exchange_name || "#{self.exchange_name}-delay-exch"
25
+ end
26
+ attr_writer :delay_exchange_name
27
+
28
+ def delay_queue_name
29
+ @delay_queue_name || "#{self.exchange_name}-delay-queue"
30
+ end
31
+ attr_writer :delay_queue_name
32
+
33
+ def queue_name_prefix
34
+ @queue_name_prefix || "#{self.exchange_name}-queue"
35
+ end
36
+ attr_writer :queue_name_prefix
37
+
38
+ def worker_threads
39
+ @worker_threads || 1
40
+ end
41
+ attr_writer :worker_threads
42
+
43
+ def worker_callbacks
44
+ @worker_callbacks ||= {}
45
+ end
46
+
47
+ def worker_callback(name, &block)
48
+ worker_callbacks[name.to_sym] ||= []
49
+ worker_callbacks[name.to_sym] << block
50
+ end
51
+
52
+ def worker_error_handlers
53
+ @worker_error_handlers ||= []
54
+ end
55
+
56
+ def worker_error_handler(&block)
57
+ worker_error_handlers << block
58
+ end
59
+
60
+ end
61
+ end
@@ -16,19 +16,34 @@ module Jobster
16
16
  end
17
17
 
18
18
  def log(text)
19
- Jobster.logger.info "[#{@id}] #{text}"
19
+ Jobster.config.logger.info "[#{@id}] #{text}"
20
20
  end
21
21
 
22
- def self.queue(queue = {}, params = {})
22
+ def self.queue(queue = {}, params = {}, &block)
23
+ queue, params = parse_args_for_queue(queue, params)
24
+ self.queue_job(Jobster.exchange, queue, self.name, params, &block)
25
+ end
26
+
27
+ def self.queue_with_delay(delay, queue = {}, params = {}, &block)
28
+ queue, params = parse_args_for_queue(queue, params)
29
+ self.queue_job(Jobster.delay_exchange, queue, self.name, params, :ttl => delay, &block)
30
+ end
23
31
 
24
- if queue.is_a?(Hash)
25
- params = queue
26
- queue = :main
27
- end
32
+ def self.parse_args_for_queue(queue, params)
33
+ queue.is_a?(Hash) ? [:main, queue] : [queue, params]
34
+ end
28
35
 
36
+ def self.queue_job(exchange, queue_name, class_name, params, options = {}, &block)
29
37
  job_id = SecureRandom.uuid[0,8]
30
- job_payload = {'params' => params, 'class_name' => self.name, 'id' => job_id, 'queue' => queue}
31
- Jobster.queue(queue).publish(job_payload.to_json, :persistent => false)
38
+ job_payload = {'params' => params, 'class_name' => class_name, 'id' => job_id, 'queue' => queue_name}
39
+ publish_opts = {}
40
+ publish_opts[:persistent] = true
41
+ publish_opts[:routing_key] = queue_name
42
+ publish_opts[:expiration] = options[:ttl] * 1000 if options[:ttl]
43
+ block.call(job_id, publish_opts) if block_given?
44
+ a = exchange.publish(job_payload.to_json, publish_opts)
45
+ when_string = (options[:ttl] ? "in #{options[:ttl]}s" : "immediately")
46
+ Jobster.config.logger.info "[#{job_id}] \e[34m#{class_name}\e[0m queued to run #{when_string} on #{queue_name} queue"
32
47
  job_id
33
48
  end
34
49
 
@@ -1,3 +1,3 @@
1
1
  module Jobster
2
- VERSION = '1.0.1'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -4,30 +4,43 @@ module Jobster
4
4
  def initialize(queues = nil)
5
5
  @initial_queues = queues || self.class.queues || [:main]
6
6
  @active_queues = {}
7
+ @running_jobs = []
7
8
  @process_name = $0
9
+ set_process_name
10
+ end
11
+
12
+ def set_process_name
13
+ prefix = @process_name.to_s
14
+ prefix += " [exiting]" if @exit
15
+ if @running_jobs.empty?
16
+ $0 = "#{prefix} (idle)"
17
+ else
18
+ $0 = "#{prefix} (running #{@running_jobs.join(', ')})"
19
+ end
8
20
  end
9
21
 
10
22
  def work
11
- logger.info "Jobster worker started"
12
- self.class.run_callbacks(:after_start)
23
+ logger.info "Jobster worker started (#{Jobster.config.worker_threads} thread(s))"
24
+ run_callbacks :after_start
13
25
 
14
- @running_job = false
15
- Signal.trap("INT") { @exit = true }
16
- Signal.trap("TERM") { @exit = true }
26
+ Jobster.delay_queue # Declare it
17
27
 
18
- Jobster.channel.prefetch(1)
28
+ Signal.trap("INT") { @exit = true; set_process_name }
29
+ Signal.trap("TERM") { @exit = true; set_process_name }
30
+
31
+ Jobster.channel.prefetch(Jobster.config.worker_threads)
19
32
  @initial_queues.uniq.each { |queue | join_queue(queue) }
20
33
 
21
34
  exit_checks = 0
22
35
  loop do
23
- if @exit && @running_job == false
24
- logger.info "Exiting immediately because no job running"
25
- self.class.run_callbacks(:before_quit, :immediate)
36
+ if @exit && @running_jobs.empty?
37
+ logger.info "Exiting immediately because no jobs running"
38
+ run_callbacks :before_quit, :immediate
26
39
  exit 0
27
40
  elsif @exit
28
41
  if exit_checks >= 300
29
42
  logger.info "Job did not finish in a timely manner. Exiting"
30
- self.class.run_callbacks(:before_quit, :timeout)
43
+ run_callbacks :before_quit, :timeout
31
44
  exit 0
32
45
  end
33
46
  if exit_checks == 0
@@ -41,43 +54,49 @@ module Jobster
41
54
  end
42
55
  end
43
56
 
57
+ def perform_job(class_name, params = {}, id = nil)
58
+ id ||= SecureRandom.uuid[0,8]
59
+ start_time = Time.now
60
+ exception = nil
61
+ logger.info "[#{id}] Started processing \e[34m#{class_name}\e[0m job"
62
+ begin
63
+ klass = Object.const_get(class_name).new(id, params)
64
+ run_callbacks :before_job, klass
65
+ klass.perform
66
+ rescue Job::Abort => e
67
+ exception = e
68
+ logger.info "[#{id}] Job aborted (#{e.message})"
69
+ rescue => e
70
+ exception = e
71
+ logger.warn "[#{id}] \e[31m#{e.class}: #{e.message}\e[0m"
72
+ e.backtrace.each do |line|
73
+ logger.warn "[#{id}] " + line
74
+ end
75
+ Jobster.config.worker_error_handlers.each { |handler| handler.call(e, klass) }
76
+ ensure
77
+ run_callbacks :after_job, klass, exception
78
+ logger.info "[#{id}] Finished processing \e[34m#{class_name}\e[0m job in #{Time.now - start_time}s"
79
+ end
80
+ end
81
+
44
82
  private
45
83
 
46
- def receive_job(delivery_info, properties, body)
47
- @running_job = true
84
+ def receive_job(properties, body)
48
85
  begin
49
86
  message = JSON.parse(body) rescue nil
50
87
  if message && message['class_name']
51
- start_time = Time.now
52
- $0 = "#{@process_name} (running #{message['class_name']})"
53
88
  Thread.current[:job_id] = message['id']
54
- logger.info "[#{message['id']}] Started processing \e[34m#{message['class_name']}\e[0m job"
55
- begin
56
- klass = Object.const_get(message['class_name']).new(message['id'], message['params'])
57
- self.class.run_callbacks(:before_job, klass)
58
- klass.perform
59
- self.class.run_callbacks(:after_job, klass)
60
- rescue Job::Abort => e
61
- logger.info "[#{message['id']}] Job aborted (#{e.message})"
62
- rescue => e
63
- logger.warn "[#{message['id']}] \e[31m#{e.class}: #{e.message}\e[0m"
64
- e.backtrace.each do |line|
65
- logger.warn "[#{message['id']}] " + line
66
- end
67
- self.class.run_callbacks(:after_job, klass, e)
68
- self.class.error_handlers.each { |handler| handler.call(e, klass) }
69
- ensure
70
- logger.info "[#{message['id']}] Finished processing \e[34m#{message['class_name']}\e[0m job in #{Time.now - start_time}s"
71
- end
89
+ @running_jobs << message['id']
90
+ set_process_name
91
+ perform_job(message['class_name'], message['params'] || {}, message['id'])
72
92
  end
73
93
  ensure
74
94
  Thread.current[:job_id] = nil
75
- $0 = @process_name
76
- Jobster.channel.ack(delivery_info.delivery_tag)
77
- @running_job = false
78
- if @exit
79
- logger.info "Exiting because a job has ended."
80
- self.class.run_callbacks(:before_quit, :job_completed)
95
+ @running_jobs.delete(message['id']) if message['id']
96
+ set_process_name
97
+ if @exit && @running_jobs.empty?
98
+ logger.info "Exiting because all jobs have finished."
99
+ run_callbacks :before_quit, :job_completed
81
100
  exit 0
82
101
  end
83
102
  end
@@ -87,12 +106,16 @@ module Jobster
87
106
  if @active_queues[queue]
88
107
  logger.info "Attempted to join queue #{queue} but already joined."
89
108
  else
90
- self.class.run_callbacks(:before_queue_join, queue)
109
+ run_callbacks :before_queue_join, queue
91
110
  consumer = Jobster.queue(queue).subscribe(:manual_ack => true) do |delivery_info, properties, body|
92
- receive_job(delivery_info, properties, body)
111
+ begin
112
+ receive_job(properties, body)
113
+ ensure
114
+ Jobster.channel.ack(delivery_info.delivery_tag)
115
+ end
93
116
  end
94
117
  @active_queues[queue] = consumer
95
- self.class.run_callbacks(:after_queue_join, queue, consumer)
118
+ run_callbacks :after_queue_join, queue, consumer
96
119
  logger.info "Joined \e[32m#{queue}\e[0m queue"
97
120
  end
98
121
  end
@@ -108,33 +131,16 @@ module Jobster
108
131
  end
109
132
 
110
133
  def logger
111
- Jobster.logger
134
+ Jobster.config.logger
112
135
  end
113
136
 
114
137
  def self.queues
115
138
  @queues ||= [:main]
116
139
  end
117
140
 
118
- def self.error_handlers
119
- @error_handlers ||= []
120
- end
121
-
122
- def self.register_error_handler(&block)
123
- error_handlers << block
124
- end
125
-
126
- def self.callbacks
127
- @callbacks ||= {}
128
- end
129
-
130
- def self.add_callback(event, &block)
131
- callbacks[event] ||= []
132
- callbacks[event] << block
133
- end
134
-
135
- def self.run_callbacks(event, *args)
136
- if callbacks[event]
137
- callbacks[event].each do |callback|
141
+ def run_callbacks(event, *args)
142
+ if callbacks = Jobster.config.worker_callbacks[event]
143
+ callbacks.each do |callback|
138
144
  callback.call(*args)
139
145
  end
140
146
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jobster
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Cooke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-27 00:00:00.000000000 Z
11
+ date: 2017-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -40,6 +40,7 @@ extra_rdoc_files: []
40
40
  files:
41
41
  - bin/jobster
42
42
  - lib/jobster.rb
43
+ - lib/jobster/config.rb
43
44
  - lib/jobster/job.rb
44
45
  - lib/jobster/version.rb
45
46
  - lib/jobster/worker.rb
@@ -63,9 +64,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
64
  version: '0'
64
65
  requirements: []
65
66
  rubyforge_project:
66
- rubygems_version: 2.5.1
67
+ rubygems_version: 2.5.2
67
68
  signing_key:
68
69
  specification_version: 4
69
70
  summary: A RabbitMQ-based job queueing system
70
71
  test_files: []
71
- has_rdoc: