cloudist 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/VERSION +1 -1
- data/cloudist.gemspec +13 -5
- data/examples/extending_values.rb +44 -0
- data/examples/sandwich_client.rb +18 -4
- data/examples/sandwich_worker.rb +12 -18
- data/examples/sandwich_worker_with_class.rb +37 -0
- data/lib/cloudist.rb +101 -17
- data/lib/cloudist/callbacks/error_callback.rb +14 -0
- data/lib/cloudist/core_ext/object.rb +81 -0
- data/lib/cloudist/errors.rb +1 -1
- data/lib/cloudist/job.rb +19 -4
- data/lib/cloudist/listener.rb +14 -2
- data/lib/cloudist/payload.rb +21 -11
- data/lib/cloudist/queues/basic_queue.rb +101 -0
- data/lib/cloudist/{job_queue.rb → queues/job_queue.rb} +2 -2
- data/lib/cloudist/{reply_queue.rb → queues/reply_queue.rb} +1 -1
- data/lib/cloudist/request.rb +2 -2
- data/lib/cloudist/utils.rb +16 -0
- data/lib/cloudist/worker.rb +20 -11
- data/spec/cloudist/basic_queue_spec.rb +2 -2
- data/spec/cloudist/payload_spec.rb +23 -18
- data/spec/cloudist/request_spec.rb +2 -2
- data/spec/cloudist/utils_spec.rb +19 -0
- data/spec/cloudist_spec.rb +42 -11
- metadata +61 -53
- data/lib/cloudist/basic_queue.rb +0 -92
data/Gemfile
CHANGED
@@ -8,7 +8,7 @@ gem "activesupport", "~> 3.0.3"
|
|
8
8
|
# Include everything needed to run rake, tests, features, etc.
|
9
9
|
group :development do
|
10
10
|
gem "rspec", "~> 2.3.0"
|
11
|
-
gem "moqueue", :git => "git://github.com/
|
11
|
+
gem "moqueue", :git => "git://github.com/ivanvanderbyl/moqueue.git"
|
12
12
|
gem "mocha"
|
13
13
|
gem "bundler", "~> 1.0.0"
|
14
14
|
gem "jeweler", "~> 1.5.2"
|
data/Gemfile.lock
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/cloudist.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{cloudist}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ivan Vanderbyl"]
|
12
|
-
s.date = %q{2011-01-
|
12
|
+
s.date = %q{2011-01-20}
|
13
13
|
s.description = %q{Cloudist is a simple, highly scalable job queue for Ruby applications, it can run within Rails, DaemonKit or your own custom application. Refer to github page for examples}
|
14
14
|
s.email = %q{ivanvanderbyl@me.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,21 +26,25 @@ Gem::Specification.new do |s|
|
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"cloudist.gemspec",
|
29
|
+
"examples/extending_values.rb",
|
29
30
|
"examples/queue_message.rb",
|
30
31
|
"examples/sandwich_client.rb",
|
31
32
|
"examples/sandwich_worker.rb",
|
33
|
+
"examples/sandwich_worker_with_class.rb",
|
32
34
|
"lib/cloudist.rb",
|
33
|
-
"lib/cloudist/basic_queue.rb",
|
34
35
|
"lib/cloudist/callback.rb",
|
35
36
|
"lib/cloudist/callback_methods.rb",
|
37
|
+
"lib/cloudist/callbacks/error_callback.rb",
|
38
|
+
"lib/cloudist/core_ext/object.rb",
|
36
39
|
"lib/cloudist/core_ext/string.rb",
|
37
40
|
"lib/cloudist/errors.rb",
|
38
41
|
"lib/cloudist/job.rb",
|
39
|
-
"lib/cloudist/job_queue.rb",
|
40
42
|
"lib/cloudist/listener.rb",
|
41
43
|
"lib/cloudist/payload.rb",
|
42
44
|
"lib/cloudist/publisher.rb",
|
43
|
-
"lib/cloudist/
|
45
|
+
"lib/cloudist/queues/basic_queue.rb",
|
46
|
+
"lib/cloudist/queues/job_queue.rb",
|
47
|
+
"lib/cloudist/queues/reply_queue.rb",
|
44
48
|
"lib/cloudist/request.rb",
|
45
49
|
"lib/cloudist/utils.rb",
|
46
50
|
"lib/cloudist/worker.rb",
|
@@ -48,6 +52,7 @@ Gem::Specification.new do |s|
|
|
48
52
|
"spec/cloudist/job_spec.rb",
|
49
53
|
"spec/cloudist/payload_spec.rb",
|
50
54
|
"spec/cloudist/request_spec.rb",
|
55
|
+
"spec/cloudist/utils_spec.rb",
|
51
56
|
"spec/cloudist_spec.rb",
|
52
57
|
"spec/core_ext/string_spec.rb",
|
53
58
|
"spec/spec_helper.rb"
|
@@ -58,13 +63,16 @@ Gem::Specification.new do |s|
|
|
58
63
|
s.rubygems_version = %q{1.3.7}
|
59
64
|
s.summary = %q{Super fast job queue using AMQP}
|
60
65
|
s.test_files = [
|
66
|
+
"examples/extending_values.rb",
|
61
67
|
"examples/queue_message.rb",
|
62
68
|
"examples/sandwich_client.rb",
|
63
69
|
"examples/sandwich_worker.rb",
|
70
|
+
"examples/sandwich_worker_with_class.rb",
|
64
71
|
"spec/cloudist/basic_queue_spec.rb",
|
65
72
|
"spec/cloudist/job_spec.rb",
|
66
73
|
"spec/cloudist/payload_spec.rb",
|
67
74
|
"spec/cloudist/request_spec.rb",
|
75
|
+
"spec/cloudist/utils_spec.rb",
|
68
76
|
"spec/cloudist_spec.rb",
|
69
77
|
"spec/core_ext/string_spec.rb",
|
70
78
|
"spec/spec_helper.rb"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class SandwichMaker
|
2
|
+
|
3
|
+
end
|
4
|
+
|
5
|
+
class SandwichEater
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
module Cloudist
|
10
|
+
class << self
|
11
|
+
@@workers = {}
|
12
|
+
|
13
|
+
def handle(*queue_names)
|
14
|
+
class << queue_names
|
15
|
+
def with(handler)
|
16
|
+
self.each do |queue_name|
|
17
|
+
((@@workers[queue_name.to_s] ||= []) << handler).uniq!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
queue_names
|
22
|
+
end
|
23
|
+
|
24
|
+
def use(handler)
|
25
|
+
proxy = handler.new
|
26
|
+
class << proxy
|
27
|
+
def to(queue_name)
|
28
|
+
((@@workers[queue_name.to_s] ||= []) << self.class).uniq!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
proxy
|
32
|
+
end
|
33
|
+
|
34
|
+
def workers
|
35
|
+
@@workers
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Cloudist.handle('make.sandwich', 'eat').with(SandwichMaker)
|
41
|
+
Cloudist.use(SandwichEater).to('eat.sandwich')
|
42
|
+
|
43
|
+
p Cloudist.workers
|
44
|
+
# >> {"eat"=>[SandwichMaker], "make.sandwich"=>[SandwichMaker], "eat.sandwich"=>[SandwichEater]}
|
data/examples/sandwich_client.rb
CHANGED
@@ -18,13 +18,26 @@ Cloudist.signal_trap!
|
|
18
18
|
Cloudist.start {
|
19
19
|
|
20
20
|
log.info("Dispatching sandwich making job...")
|
21
|
-
enqueue('make.sandwich', {:bread => 'white'})
|
22
|
-
# enqueue('make.sandwich', {:bread => 'brown'})
|
23
21
|
|
22
|
+
unless ARGV.empty?
|
23
|
+
job_count = ARGV.pop.to_i
|
24
|
+
job_count.times { |i| enqueue('make.sandwich', {:bread => 'white', :sandwich_number => i})}
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# enqueue('eat.sandwich', {:sandwich => job.id})
|
29
|
+
# enqueue('make.sandwich', {:bread => 'brown'})
|
30
|
+
|
24
31
|
# Listen to all sandwich jobs
|
25
|
-
listen('make.sandwich') {
|
32
|
+
listen('make.sandwich', 'eat.sandwich') {
|
26
33
|
everything {
|
27
|
-
Cloudist.log.info("Job ID: #{job_id}")
|
34
|
+
Cloudist.log.info("#{headers[:message_type]} - Job ID: #{job_id}")
|
35
|
+
}
|
36
|
+
|
37
|
+
error { |e|
|
38
|
+
Cloudist.log.error(e.inspect)
|
39
|
+
Cloudist.log.error(e.backtrace.inspect)
|
40
|
+
Cloudist.stop
|
28
41
|
}
|
29
42
|
|
30
43
|
progress {
|
@@ -37,6 +50,7 @@ Cloudist.start {
|
|
37
50
|
|
38
51
|
event('finished'){
|
39
52
|
Cloudist.log.info("Finished making sandwich at #{Time.now.to_s}")
|
53
|
+
Cloudist.stop
|
40
54
|
}
|
41
55
|
}
|
42
56
|
|
data/examples/sandwich_worker.rb
CHANGED
@@ -19,24 +19,18 @@ Cloudist.signal_trap!
|
|
19
19
|
Cloudist.start {
|
20
20
|
log.info("Started Worker")
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
job('make.sandwich') {
|
23
|
+
log.info("JOB (#{id}) Make sandwich with #{data[:bread]} bread")
|
24
|
+
|
25
|
+
job.started!
|
26
|
+
|
27
|
+
(1..20).each do |i|
|
28
|
+
job.progress(i * 5)
|
29
|
+
sleep(1)
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
EM.defer {
|
30
|
-
progress(0)
|
31
|
-
started!
|
32
|
-
progress(10)
|
33
|
-
sleep(1)
|
34
|
-
progress(20)
|
35
|
-
sleep(5)
|
36
|
-
progress(90)
|
37
|
-
finished!
|
38
|
-
progress(100)
|
39
|
-
}
|
40
|
-
}
|
31
|
+
raise ArgumentError, "NOT GOOD!" if i == 4
|
32
|
+
end
|
33
|
+
job.finished!
|
41
34
|
}
|
35
|
+
|
42
36
|
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Cloudst Example: Sandwich Worker
|
2
|
+
#
|
3
|
+
# This example demonstrates receiving a job and sending back events to the client to let it know we've started and finsihed
|
4
|
+
# making a sandwich. From here you could dispatch an eat.sandwich event.
|
5
|
+
#
|
6
|
+
# Be sure to update the Cloudist connection settings if they differ from defaults:
|
7
|
+
# user: guest
|
8
|
+
# pass: guest
|
9
|
+
# port: 5672
|
10
|
+
# host: localhost
|
11
|
+
# vhost: /
|
12
|
+
#
|
13
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
14
|
+
require "rubygems"
|
15
|
+
require "cloudist"
|
16
|
+
|
17
|
+
class SandwichWorker < Cloudist::Worker
|
18
|
+
def process
|
19
|
+
log.info("Processing queue: #{queue.name}")
|
20
|
+
log.info(data.inspect)
|
21
|
+
|
22
|
+
job.started!
|
23
|
+
(1..20).each do |i|
|
24
|
+
job.progress(i * 5)
|
25
|
+
sleep(1)
|
26
|
+
|
27
|
+
raise ArgumentError, "NOT GOOD!" if i == 4
|
28
|
+
end
|
29
|
+
job.finished!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Cloudist.signal_trap!
|
34
|
+
|
35
|
+
Cloudist.start {
|
36
|
+
Cloudist.handle('make.sandwich', 'eat.sandwich').with(SandwichWorker)
|
37
|
+
}
|
data/lib/cloudist.rb
CHANGED
@@ -8,22 +8,27 @@ require "digest/md5"
|
|
8
8
|
|
9
9
|
$:.unshift File.dirname(__FILE__)
|
10
10
|
require "cloudist/core_ext/string"
|
11
|
+
require "cloudist/core_ext/object"
|
11
12
|
require "cloudist/errors"
|
12
13
|
require "cloudist/utils"
|
13
|
-
require "cloudist/basic_queue"
|
14
|
-
require "cloudist/job_queue"
|
15
|
-
require "cloudist/reply_queue"
|
14
|
+
require "cloudist/queues/basic_queue"
|
15
|
+
require "cloudist/queues/job_queue"
|
16
|
+
require "cloudist/queues/reply_queue"
|
16
17
|
require "cloudist/publisher"
|
17
18
|
require "cloudist/payload"
|
18
19
|
require "cloudist/request"
|
19
|
-
require "cloudist/worker"
|
20
20
|
require "cloudist/callback_methods"
|
21
21
|
require "cloudist/listener"
|
22
22
|
require "cloudist/callback"
|
23
|
+
require "cloudist/callbacks/error_callback"
|
23
24
|
require "cloudist/job"
|
25
|
+
require "cloudist/worker"
|
24
26
|
|
25
27
|
module Cloudist
|
26
28
|
class << self
|
29
|
+
|
30
|
+
@@workers = {}
|
31
|
+
|
27
32
|
# Start the Cloudist loop
|
28
33
|
#
|
29
34
|
# Cloudist.start {
|
@@ -48,15 +53,82 @@ module Cloudist
|
|
48
53
|
|
49
54
|
# Define a worker. Must be called inside start loop
|
50
55
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
56
|
+
# worker {
|
57
|
+
# job('make.sandwich') {}
|
58
|
+
# }
|
59
|
+
#
|
60
|
+
# REMOVED
|
61
|
+
def worker(&block)
|
62
|
+
raise NotImplementedError, "This DSL format has been removed. Please use job('make.sandwich') {} instead."
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines a job handler (GenericWorker)
|
66
|
+
#
|
67
|
+
# job('make.sandwich') {
|
68
|
+
# job.started!
|
69
|
+
# # Work hard
|
70
|
+
# sleep(5)
|
71
|
+
# job.finished!
|
72
|
+
# }
|
73
|
+
#
|
74
|
+
# Refer to sandwich_worker.rb example
|
75
|
+
#
|
76
|
+
def job(queue_name, &block)
|
77
|
+
register_worker(queue_name, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Registers a worker class to handle a specific queue
|
81
|
+
#
|
82
|
+
# Cloudist.handle('make.sandwich', 'eat.sandwich').with(MyWorker)
|
83
|
+
#
|
84
|
+
# A standard worker would look like this:
|
85
|
+
#
|
86
|
+
# class MyWorker < Cloudist::Worker
|
87
|
+
# def process
|
88
|
+
# log.debug(data.inspect)
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# A new instance of this worker will be created everytime a job arrives
|
54
93
|
#
|
55
94
|
# Refer to examples.
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
95
|
+
def handle(*queue_names)
|
96
|
+
class << queue_names
|
97
|
+
def with(handler)
|
98
|
+
self.each do |queue_name|
|
99
|
+
Cloudist.register_worker(queue_name.to_s, handler)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
queue_names
|
104
|
+
end
|
105
|
+
|
106
|
+
def register_worker(queue_name, klass = nil, &block)
|
107
|
+
job_queue = JobQueue.new(queue_name)
|
108
|
+
job_queue.subscribe do |request|
|
109
|
+
j = Job.new(request.payload.dup)
|
110
|
+
EM.defer do
|
111
|
+
begin
|
112
|
+
if block_given?
|
113
|
+
worker_instance = GenericWorker.new(j, job_queue.q)
|
114
|
+
worker_instance.process(&block)
|
115
|
+
elsif klass
|
116
|
+
worker_instance = klass.new(j, job_queue.q)
|
117
|
+
worker_instance.process
|
118
|
+
else
|
119
|
+
raise RuntimeError, "Failed to register worker, I need either a handler class or block."
|
120
|
+
end
|
121
|
+
finished = Time.now.utc.to_i
|
122
|
+
log.debug("Finished Job in #{finished - request.start} seconds")
|
123
|
+
|
124
|
+
rescue Exception => e
|
125
|
+
j.handle_error(e)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
j.cleanup
|
129
|
+
end
|
130
|
+
|
131
|
+
((@@workers[queue_name.to_s] ||= []) << job_queue).uniq!
|
60
132
|
end
|
61
133
|
|
62
134
|
# Accepts either a queue name or a job instance returned from enqueue.
|
@@ -64,10 +136,14 @@ module Cloudist
|
|
64
136
|
# will return all responses regardless of job id so you can use the job
|
65
137
|
# id to lookup a database record to update etc.
|
66
138
|
# When given a job instance it will only return messages from that job.
|
67
|
-
def listen(
|
68
|
-
|
69
|
-
|
70
|
-
|
139
|
+
def listen(*queue_names, &block)
|
140
|
+
@@listeners ||= []
|
141
|
+
queue_names.each do |job_or_queue_name|
|
142
|
+
_listener = Cloudist::Listener.new(job_or_queue_name)
|
143
|
+
_listener.subscribe(&block)
|
144
|
+
@@listeners << _listener
|
145
|
+
end
|
146
|
+
return @@listeners
|
71
147
|
end
|
72
148
|
|
73
149
|
# Enqueues a job.
|
@@ -82,11 +158,11 @@ module Cloudist
|
|
82
158
|
|
83
159
|
# Call this at anytime inside the loop to exit the app.
|
84
160
|
def stop_safely
|
85
|
-
::EM.add_timer(0.2) {
|
161
|
+
# ::EM.add_timer(0.2) {
|
86
162
|
::AMQP.stop {
|
87
163
|
::EM.stop
|
88
164
|
}
|
89
|
-
}
|
165
|
+
# }
|
90
166
|
end
|
91
167
|
|
92
168
|
alias :stop :stop_safely
|
@@ -138,6 +214,14 @@ module Cloudist
|
|
138
214
|
::Signal.trap('TERM'){ Cloudist.stop }
|
139
215
|
end
|
140
216
|
|
217
|
+
def workers
|
218
|
+
@@workers
|
219
|
+
end
|
220
|
+
|
221
|
+
def remove_workers
|
222
|
+
@@workers = {}
|
223
|
+
end
|
224
|
+
|
141
225
|
end
|
142
226
|
|
143
227
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Taken from Rails ActiveSupport
|
2
|
+
class Object
|
3
|
+
def remove_subclasses_of(*superclasses) #:nodoc:
|
4
|
+
Class.remove_class(*subclasses_of(*superclasses))
|
5
|
+
end
|
6
|
+
|
7
|
+
begin
|
8
|
+
ObjectSpace.each_object(Class.new) {}
|
9
|
+
|
10
|
+
# Exclude this class unless it's a subclass of our supers and is defined.
|
11
|
+
# We check defined? in case we find a removed class that has yet to be
|
12
|
+
# garbage collected. This also fails for anonymous classes -- please
|
13
|
+
# submit a patch if you have a workaround.
|
14
|
+
def subclasses_of(*superclasses) #:nodoc:
|
15
|
+
subclasses = []
|
16
|
+
|
17
|
+
superclasses.each do |sup|
|
18
|
+
ObjectSpace.each_object(class << sup; self; end) do |k|
|
19
|
+
if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
|
20
|
+
subclasses << k
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
subclasses
|
26
|
+
end
|
27
|
+
rescue RuntimeError
|
28
|
+
# JRuby and any implementations which cannot handle the objectspace traversal
|
29
|
+
# above fall back to this implementation
|
30
|
+
def subclasses_of(*superclasses) #:nodoc:
|
31
|
+
subclasses = []
|
32
|
+
|
33
|
+
superclasses.each do |sup|
|
34
|
+
ObjectSpace.each_object(Class) do |k|
|
35
|
+
if superclasses.any? { |superclass| k < superclass } &&
|
36
|
+
(k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
|
37
|
+
subclasses << k
|
38
|
+
end
|
39
|
+
end
|
40
|
+
subclasses.uniq!
|
41
|
+
end
|
42
|
+
subclasses
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def extended_by #:nodoc:
|
47
|
+
ancestors = class << self; ancestors end
|
48
|
+
ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
|
49
|
+
end
|
50
|
+
|
51
|
+
def extend_with_included_modules_from(object) #:nodoc:
|
52
|
+
object.extended_by.each { |mod| extend mod }
|
53
|
+
end
|
54
|
+
|
55
|
+
unless defined? instance_exec # 1.9
|
56
|
+
module InstanceExecMethods #:nodoc:
|
57
|
+
end
|
58
|
+
include InstanceExecMethods
|
59
|
+
|
60
|
+
# Evaluate the block with the given arguments within the context of
|
61
|
+
# this object, so self is set to the method receiver.
|
62
|
+
#
|
63
|
+
# From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
|
64
|
+
def instance_exec(*args, &block)
|
65
|
+
begin
|
66
|
+
old_critical, Thread.critical = Thread.critical, true
|
67
|
+
n = 0
|
68
|
+
n += 1 while respond_to?(method_name = "__instance_exec#{n}")
|
69
|
+
InstanceExecMethods.module_eval { define_method(method_name, &block) }
|
70
|
+
ensure
|
71
|
+
Thread.critical = old_critical
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
send(method_name, *args)
|
76
|
+
ensure
|
77
|
+
InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|