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 +4 -4
- data/lib/jobster.rb +28 -29
- data/lib/jobster/config.rb +61 -0
- data/lib/jobster/job.rb +23 -8
- data/lib/jobster/version.rb +1 -1
- data/lib/jobster/worker.rb +68 -62
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30f6aef2178e0d7768841444674349210d567b03
|
4
|
+
data.tar.gz: f0d28eb0e772a1ec33b28bb9fc108feaf6521b45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d58fc83c4df5ed65e71c1e6fe54d0013cee2d567cf26b48d4def603a4f749db523365ee03d2d596fd54e72207d765b0987526f47df7781d0d3736206bed1825
|
7
|
+
data.tar.gz: 335bae815fea0868611fa84740a1ef2b870aca0a5174c3893d2e6cd34bd1a6d0ef9166375c17112a1828f1f37201ee3153c0a702c024f610a8e53830174137ac
|
data/lib/jobster.rb
CHANGED
@@ -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
|
-
|
9
|
+
def self.config
|
10
|
+
@config ||= Config.new
|
11
|
+
end
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
attr_writer :logger
|
17
|
+
def self.channel
|
18
|
+
@channel ||= config.bunny.create_channel(nil, config.worker_threads)
|
19
|
+
end
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
35
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
data/lib/jobster/job.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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' =>
|
31
|
-
|
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
|
|
data/lib/jobster/version.rb
CHANGED
data/lib/jobster/worker.rb
CHANGED
@@ -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
|
-
|
23
|
+
logger.info "Jobster worker started (#{Jobster.config.worker_threads} thread(s))"
|
24
|
+
run_callbacks :after_start
|
13
25
|
|
14
|
-
|
15
|
-
Signal.trap("INT") { @exit = true }
|
16
|
-
Signal.trap("TERM") { @exit = true }
|
26
|
+
Jobster.delay_queue # Declare it
|
17
27
|
|
18
|
-
|
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 && @
|
24
|
-
logger.info "Exiting immediately because no
|
25
|
-
|
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
|
-
|
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(
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
76
|
-
|
77
|
-
@
|
78
|
-
|
79
|
-
|
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
|
-
|
109
|
+
run_callbacks :before_queue_join, queue
|
91
110
|
consumer = Jobster.queue(queue).subscribe(:manual_ack => true) do |delivery_info, properties, body|
|
92
|
-
|
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
|
-
|
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
|
119
|
-
|
120
|
-
|
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
|
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-
|
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.
|
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:
|