jobster 1.0.1 → 1.2.0
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.
- 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:
|