jobster 0.0.0 → 1.0.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 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