cloudist 0.1.2 → 0.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.
- 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
|