jobster 0.0.0 → 1.0.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: 8b42cbde20bbcdd7c823f4d202590b81c6e5ed78
4
- data.tar.gz: f3da2037dbb263d8f22f8293b7caa5362054732a
3
+ metadata.gz: 3f97863176fe30a7cef64b7fad42838ea0df3de1
4
+ data.tar.gz: 05d04d055613758d20d3be44f50379640b912263
5
5
  SHA512:
6
- metadata.gz: 2c3622f0c889627374b070523cce325a9c2b6b34419b18b7ecebfe0f92205ec0d980c91d27d6301e15e7595ef5e050226a210fd314ce18bac4e0e8a80f2d7e11
7
- data.tar.gz: f542c6eed046b828410597c2fb38537a2f33dcdf348e8e0a6717f9d3cc892a856548c9b6a22e39c32ea0e0ae8b5564e0a6df3f607dd91587ac44bbf3a81b96be
6
+ metadata.gz: 9ad5c5c3c8780ba623c8284b6ce6eeee482bbb24d655e6d327f771442ff2e8bd7bc540923bdc647ac0e9dc3ceb5d722adc823ceaf3992b86b166bfe3d7373532
7
+ data.tar.gz: 826bcfaadb5641153f9a1296400cfac9c91fa8d043e1b5aafa4d613b7064a0ba163da62037234c6116f22ad958bd660e652edfcc6c111bb1663d6dd5f93d63cc
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'jobster'
5
+
6
+ $stdout.sync = true
7
+ $stderr.sync = true
8
+
9
+ options = {}
10
+
11
+ OptionParser.new do |opts|
12
+ opts.version = Jobster::VERSION
13
+ opts.banner = "Usage: jobster [options]"
14
+ opts.on("-c", "--config PATH", "The path to your jobster config file") do |config|
15
+ options[:config] = config
16
+ end
17
+ opts.on("-q", "--queues QUEUE1,QUEUE2", "Additional queues that you wish to subscribe to") do |queues|
18
+ queues.split(/,/).uniq.each do |queue|
19
+ Jobster::Worker.queues << queue
20
+ end
21
+ end
22
+
23
+ end.parse!
24
+
25
+ if options[:config]
26
+ if File.file?(options[:config])
27
+ file = File.expand_path(options[:config])
28
+ require file
29
+ else
30
+ puts "Jobster config file not found at #{options[:config]}"
31
+ exit 1
32
+ end
33
+ end
34
+
35
+ worker = Jobster::Worker.new
36
+ worker.work
@@ -0,0 +1,47 @@
1
+ require 'bunny'
2
+ require 'jobster/job'
3
+ require 'jobster/worker'
4
+ require 'jobster/version'
5
+
6
+ module Jobster
7
+
8
+ class << self
9
+
10
+ def bunny
11
+ @bunny ||= begin
12
+ connection = Bunny.new
13
+ connection.start
14
+ connection
15
+ end
16
+ end
17
+ attr_writer :bunny
18
+
19
+ def logger
20
+ @logger ||= Logger.new(STDOUT)
21
+ end
22
+ attr_writer :logger
23
+
24
+ def ttl
25
+ @ttl ||= 60 * 60 * 10
26
+ end
27
+ attr_writer :ttl
28
+
29
+ def queue_prefix
30
+ @queue_prefix ||= "jobster"
31
+ end
32
+ attr_writer :queue_prefix
33
+
34
+ def channel
35
+ @channel ||= bunny.create_channel
36
+ end
37
+
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
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,40 @@
1
+ module Jobster
2
+ class Job
3
+
4
+ class Abort < StandardError; end
5
+
6
+ attr_reader :id
7
+ attr_reader :params
8
+
9
+ def initialize(id, params = {})
10
+ @id = id
11
+ @params = params.with_indifferent_access
12
+ end
13
+
14
+ def perform
15
+ # Override in child jobs
16
+ end
17
+
18
+ def log(text)
19
+ Jobster.logger.info "[#{@id}] #{text}"
20
+ end
21
+
22
+ def self.queue(queue = {}, params = {})
23
+
24
+ if queue.is_a?(Hash)
25
+ params = queue
26
+ queue = :main
27
+ end
28
+
29
+ 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)
32
+ job_id
33
+ end
34
+
35
+ def self.perform(params = {})
36
+ new(nil, params).perform
37
+ end
38
+
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module Jobster
2
- VERSION = '0.0.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -0,0 +1,144 @@
1
+ module Jobster
2
+ class Worker
3
+
4
+ def initialize(queues = nil)
5
+ @initial_queues = queues || self.class.queues || [:main]
6
+ @active_queues = {}
7
+ @process_name = $0
8
+ end
9
+
10
+ def work
11
+ logger.info "Jobster worker started"
12
+ self.class.run_callbacks(:after_start)
13
+
14
+ @running_job = false
15
+ Signal.trap("INT") { @exit = true }
16
+ Signal.trap("TERM") { @exit = true }
17
+
18
+ Jobster.channel.prefetch(1)
19
+ @initial_queues.uniq.each { |queue | join_queue(queue) }
20
+
21
+ exit_checks = 0
22
+ 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)
26
+ exit 0
27
+ elsif @exit
28
+ if exit_checks >= 300
29
+ logger.info "Job did not finish in a timely manner. Exiting"
30
+ self.class.run_callbacks(:before_quit, :timeout)
31
+ exit 0
32
+ end
33
+ if exit_checks == 0
34
+ logger.info "Exit requested but job is running. Waiting for job to finish."
35
+ end
36
+ sleep 5
37
+ exit_checks += 1
38
+ else
39
+ sleep 1
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def receive_job(delivery_info, properties, body)
47
+ @running_job = true
48
+ begin
49
+ message = JSON.parse(body) rescue nil
50
+ if message && message['class_name']
51
+ start_time = Time.now
52
+ $0 = "#{@process_name} (running #{message['class_name']})"
53
+ 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
72
+ end
73
+ ensure
74
+ 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)
81
+ exit 0
82
+ end
83
+ end
84
+ end
85
+
86
+ def join_queue(queue)
87
+ if @active_queues[queue]
88
+ logger.info "Attempted to join queue #{queue} but already joined."
89
+ else
90
+ self.class.run_callbacks(:before_queue_join, queue)
91
+ consumer = Jobster.queue(queue).subscribe(:manual_ack => true) do |delivery_info, properties, body|
92
+ receive_job(delivery_info, properties, body)
93
+ end
94
+ @active_queues[queue] = consumer
95
+ self.class.run_callbacks(:after_queue_join, queue, consumer)
96
+ logger.info "Joined \e[32m#{queue}\e[0m queue"
97
+ end
98
+ end
99
+
100
+ def leave_queue(queue)
101
+ if consumer = @active_queues[queue]
102
+ consumer.cancel
103
+ @active_queues.delete(queue)
104
+ logger.info "Left \e[32m#{queue}\e[0m queue"
105
+ else
106
+ logger.info "Not joined #{queue} so cannot leave"
107
+ end
108
+ end
109
+
110
+ def logger
111
+ Jobster.logger
112
+ end
113
+
114
+ def self.queues
115
+ @queues ||= [:main]
116
+ end
117
+
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|
138
+ callback.call(*args)
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+ 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: 0.0.0
4
+ version: 1.0.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-22 00:00:00.000000000 Z
11
+ date: 2017-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -33,11 +33,16 @@ dependencies:
33
33
  description: A RabbitMQ-based job queueing system
34
34
  email:
35
35
  - me@adamcooke.io
36
- executables: []
36
+ executables:
37
+ - jobster
37
38
  extensions: []
38
39
  extra_rdoc_files: []
39
40
  files:
41
+ - bin/jobster
42
+ - lib/jobster.rb
43
+ - lib/jobster/job.rb
40
44
  - lib/jobster/version.rb
45
+ - lib/jobster/worker.rb
41
46
  homepage: https://github.com/adamcooke/jobster
42
47
  licenses:
43
48
  - MIT
@@ -58,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
63
  version: '0'
59
64
  requirements: []
60
65
  rubyforge_project:
61
- rubygems_version: 2.5.2
66
+ rubygems_version: 2.5.1
62
67
  signing_key:
63
68
  specification_version: 4
64
69
  summary: A RabbitMQ-based job queueing system