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 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/customink/moqueue.git"
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
@@ -1,6 +1,6 @@
1
1
  GIT
2
- remote: git://github.com/customink/moqueue.git
3
- revision: 091a8f57e5c79b0b25e152b4d5230e4031797d62
2
+ remote: git://github.com/ivanvanderbyl/moqueue.git
3
+ revision: cf2108e3bee7730a6a57e0a95a4c17082c2b2603
4
4
  specs:
5
5
  moqueue (0.1.4)
6
6
  amqp
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
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.1.2"
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-18}
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/reply_queue.rb",
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]}
@@ -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
 
@@ -19,24 +19,18 @@ Cloudist.signal_trap!
19
19
  Cloudist.start {
20
20
  log.info("Started Worker")
21
21
 
22
- worker {
23
- job('make.sandwich') {
24
- # Fire the started event
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
- log.info("JOB (#{id}) Make sandwich with #{data[:bread]} bread")
27
- log.debug(data.inspect)
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
- # worker {
52
- # job('make.sandwich') {}
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 worker(options = {}, &block)
57
- _worker = Cloudist::Worker.new(options)
58
- _worker.instance_eval(&block)
59
- return _worker
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(job_or_queue_name, &block)
68
- _listener = Cloudist::Listener.new(job_or_queue_name)
69
- _listener.subscribe(&block)
70
- return _listener
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,14 @@
1
+ module Cloudist
2
+ class ErrorCallback < Callback
3
+ def call(payload)
4
+ @payload = payload
5
+
6
+ case source.arity
7
+ when 0
8
+ instance_exec(&source)
9
+ when 1
10
+ instance_exec(payload.exception, &source)
11
+ end
12
+ end
13
+ end
14
+ 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