cloudist 0.2.1 → 0.4.1
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 +15 -11
- data/Gemfile.lock +20 -7
- data/README.md +61 -39
- data/VERSION +1 -1
- data/cloudist.gemspec +50 -16
- data/examples/amqp/Gemfile +3 -0
- data/examples/amqp/Gemfile.lock +12 -0
- data/examples/amqp/amqp_consumer.rb +56 -0
- data/examples/amqp/amqp_publisher.rb +50 -0
- data/examples/queue_message.rb +7 -7
- data/examples/sandwich_client_with_custom_listener.rb +77 -0
- data/examples/sandwich_worker_with_class.rb +22 -7
- data/lib/cloudist.rb +113 -56
- data/lib/cloudist/application.rb +60 -0
- data/lib/cloudist/core_ext/class.rb +139 -0
- data/lib/cloudist/core_ext/kernel.rb +13 -0
- data/lib/cloudist/core_ext/module.rb +11 -0
- data/lib/cloudist/encoding.rb +13 -0
- data/lib/cloudist/errors.rb +2 -0
- data/lib/cloudist/job.rb +21 -18
- data/lib/cloudist/listener.rb +108 -54
- data/lib/cloudist/message.rb +97 -0
- data/lib/cloudist/messaging.rb +29 -0
- data/lib/cloudist/payload.rb +45 -105
- data/lib/cloudist/payload_old.rb +155 -0
- data/lib/cloudist/publisher.rb +7 -2
- data/lib/cloudist/queue.rb +152 -0
- data/lib/cloudist/queues/basic_queue.rb +83 -53
- data/lib/cloudist/queues/job_queue.rb +13 -24
- data/lib/cloudist/queues/reply_queue.rb +13 -21
- data/lib/cloudist/request.rb +33 -7
- data/lib/cloudist/worker.rb +9 -2
- data/lib/cloudist_old.rb +300 -0
- data/lib/em/em_timer_utils.rb +55 -0
- data/lib/em/iterator.rb +27 -0
- data/spec/cloudist/message_spec.rb +91 -0
- data/spec/cloudist/messaging_spec.rb +19 -0
- data/spec/cloudist/payload_spec.rb +10 -4
- data/spec/cloudist/payload_spec_2_spec.rb +78 -0
- data/spec/cloudist/queue_spec.rb +16 -0
- data/spec/cloudist_spec.rb +49 -45
- data/spec/spec_helper.rb +0 -1
- data/spec/support/amqp.rb +16 -0
- metadata +112 -102
- data/examples/extending_values.rb +0 -44
- data/examples/sandwich_client.rb +0 -57
- data/lib/cloudist/callback.rb +0 -16
- data/lib/cloudist/callback_methods.rb +0 -19
- data/lib/cloudist/callbacks/error_callback.rb +0 -14
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require 'amqp'
|
6
|
+
|
7
|
+
def amqp_settings
|
8
|
+
uri = URI.parse(ENV["AMQP_URL"] || 'amqp://guest:guest@localhost:5672/')
|
9
|
+
{
|
10
|
+
:vhost => uri.path,
|
11
|
+
:host => uri.host,
|
12
|
+
:user => uri.user,
|
13
|
+
:port => uri.port || 5672,
|
14
|
+
:pass => uri.password,
|
15
|
+
:heartbeat => 120,
|
16
|
+
:logging => false
|
17
|
+
}
|
18
|
+
rescue Object => e
|
19
|
+
raise "invalid AMQP_URL: (#{uri.inspect}) #{e.class} -> #{e.message}"
|
20
|
+
end
|
21
|
+
|
22
|
+
p amqp_settings
|
23
|
+
|
24
|
+
def log(*args)
|
25
|
+
puts args.inspect
|
26
|
+
end
|
27
|
+
|
28
|
+
EM.run do
|
29
|
+
puts "Running..."
|
30
|
+
AMQP.start(amqp_settings) do |connection|
|
31
|
+
log "Connected to AMQP broker"
|
32
|
+
|
33
|
+
channel = AMQP::Channel.new(connection)
|
34
|
+
channel.prefetch(1)
|
35
|
+
queue = channel.queue("test.hello.world")
|
36
|
+
exchange = channel.direct
|
37
|
+
queue.bind(exchange)
|
38
|
+
|
39
|
+
EM.defer do
|
40
|
+
10000.times { |i|
|
41
|
+
log "Publishing message #{i+1}"
|
42
|
+
if i % 1000 == 0
|
43
|
+
puts "Sleeping..."
|
44
|
+
sleep(1)
|
45
|
+
end
|
46
|
+
exchange.publish "Hello, world! - #{i+1}"#, :routing_key => queue.name
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/examples/queue_message.rb
CHANGED
@@ -2,16 +2,16 @@ $:.unshift File.dirname(__FILE__) + '/../lib'
|
|
2
2
|
require "rubygems"
|
3
3
|
require "cloudist"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
Cloudist.signal_trap!
|
6
|
+
#
|
7
|
+
# This demonstrates how to send a message to a listener
|
8
|
+
#
|
8
9
|
Cloudist.start {
|
9
10
|
|
10
|
-
payload = Cloudist::Payload.new(
|
11
|
+
payload = Cloudist::Payload.new(:event => :started, :message_type => 'event')
|
11
12
|
|
12
13
|
q = Cloudist::ReplyQueue.new('temp.reply.make.sandwich')
|
13
|
-
q.
|
14
|
-
q.publish_to_q(payload)
|
14
|
+
q.publish(payload)
|
15
15
|
|
16
16
|
stop
|
17
|
-
}
|
17
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Cloudst Example: Sandwich Client with custom listener class
|
2
|
+
#
|
3
|
+
# This example demonstrates dispatching a job to the worker and receiving event callbacks.
|
4
|
+
#
|
5
|
+
# Be sure to update the Cloudist connection settings if they differ from defaults:
|
6
|
+
# user: guest
|
7
|
+
# pass: guest
|
8
|
+
# port: 5672
|
9
|
+
# host: localhost
|
10
|
+
# vhost: /
|
11
|
+
#
|
12
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
13
|
+
require "rubygems"
|
14
|
+
require "cloudist"
|
15
|
+
|
16
|
+
$total_jobs = 0
|
17
|
+
|
18
|
+
class SandwichListener < Cloudist::Listener
|
19
|
+
listen_to "make.sandwich"
|
20
|
+
|
21
|
+
before :find_job
|
22
|
+
|
23
|
+
def find_job
|
24
|
+
puts "--- #{payload.id}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def progress(i)
|
28
|
+
puts "Progress: %1d%" % i
|
29
|
+
end
|
30
|
+
|
31
|
+
def runtime(seconds)
|
32
|
+
puts "#{id} Finished job in #{seconds} seconds"
|
33
|
+
$total_jobs -= 1
|
34
|
+
puts "--- #{$total_jobs} jobs remaining"
|
35
|
+
end
|
36
|
+
|
37
|
+
# def started
|
38
|
+
# puts "Started"
|
39
|
+
# end
|
40
|
+
|
41
|
+
def event(type)
|
42
|
+
puts "Event: #{type}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def finished
|
46
|
+
puts "*** Finished ***"
|
47
|
+
|
48
|
+
if $total_jobs == 0
|
49
|
+
puts "Completed all jobs"
|
50
|
+
Cloudist.stop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def error(e)
|
55
|
+
puts "#{e.exception}: #{e.message} (#{e.backtrace.first})"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
Cloudist.signal_trap!
|
61
|
+
|
62
|
+
Cloudist.start(:logging => true) {
|
63
|
+
puts AMQP.settings.inspect
|
64
|
+
|
65
|
+
unless ARGV.empty?
|
66
|
+
puts "*** Please ensure you have a worker running ***"
|
67
|
+
|
68
|
+
job_count = ARGV.pop.to_i
|
69
|
+
$total_jobs = job_count
|
70
|
+
job_count.times { |i|
|
71
|
+
log.info("Dispatching sandwich making job...")
|
72
|
+
puts "Queued job: " + enqueue('make.sandwich', {:bread => 'white', :sandwich_number => i}).id
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
add_listener(SandwichListener)
|
77
|
+
}
|
@@ -16,22 +16,37 @@ require "cloudist"
|
|
16
16
|
|
17
17
|
class SandwichWorker < Cloudist::Worker
|
18
18
|
def process
|
19
|
-
log.info("Processing
|
20
|
-
log.info(data.inspect)
|
19
|
+
log.info("Processing #{queue.name} job: #{id}")
|
21
20
|
|
21
|
+
# This will trigger the start event
|
22
|
+
# Appending ! to the end of a method will trigger an
|
23
|
+
# event reply with its name
|
24
|
+
#
|
25
|
+
# e.g. job.working!
|
26
|
+
#
|
22
27
|
job.started!
|
23
|
-
|
24
|
-
|
28
|
+
|
29
|
+
(1..5).each do |i|
|
30
|
+
# This sends a progress reply, you could use this to
|
31
|
+
# update a progress bar in your UI
|
32
|
+
#
|
33
|
+
# usage: #progress([INTEGER 0 - 100])
|
34
|
+
job.progress(i * 20)
|
35
|
+
|
36
|
+
# Work hard!
|
25
37
|
sleep(1)
|
26
38
|
|
27
|
-
|
39
|
+
# Uncomment this to test error handling in Listener
|
40
|
+
# raise ArgumentError, "NOT GOOD!" if i == 4
|
28
41
|
end
|
42
|
+
|
43
|
+
# Trigger finished event
|
29
44
|
job.finished!
|
30
45
|
end
|
31
46
|
end
|
32
47
|
|
33
48
|
Cloudist.signal_trap!
|
34
49
|
|
35
|
-
Cloudist.start {
|
36
|
-
Cloudist.handle('make.sandwich'
|
50
|
+
Cloudist.start(:logging => false) {
|
51
|
+
Cloudist.handle('make.sandwich').with(SandwichWorker)
|
37
52
|
}
|
data/lib/cloudist.rb
CHANGED
@@ -1,33 +1,38 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'json' unless defined? ActiveSupport::JSON
|
3
|
-
require "active_support/hash_with_indifferent_access"
|
4
3
|
require "amqp"
|
5
|
-
require "
|
4
|
+
require "hashie"
|
6
5
|
require "logger"
|
7
6
|
require "digest/md5"
|
7
|
+
require "uuid"
|
8
8
|
|
9
9
|
$:.unshift File.dirname(__FILE__)
|
10
|
+
|
11
|
+
require "em/em_timer_utils"
|
10
12
|
require "cloudist/core_ext/string"
|
11
13
|
require "cloudist/core_ext/object"
|
14
|
+
require "cloudist/core_ext/class"
|
12
15
|
require "cloudist/errors"
|
13
16
|
require "cloudist/utils"
|
17
|
+
require "cloudist/encoding"
|
14
18
|
require "cloudist/queues/basic_queue"
|
15
19
|
require "cloudist/queues/job_queue"
|
16
20
|
require "cloudist/queues/reply_queue"
|
17
21
|
require "cloudist/publisher"
|
18
22
|
require "cloudist/payload"
|
19
23
|
require "cloudist/request"
|
20
|
-
require "cloudist/callback_methods"
|
21
24
|
require "cloudist/listener"
|
22
|
-
require "cloudist/callback"
|
23
|
-
require "cloudist/callbacks/error_callback"
|
24
25
|
require "cloudist/job"
|
25
26
|
require "cloudist/worker"
|
26
27
|
|
27
28
|
module Cloudist
|
29
|
+
DEFAULT_TTL = 300
|
30
|
+
|
28
31
|
class << self
|
29
|
-
|
30
|
-
|
32
|
+
thread_local_accessor :channels, :default => {}
|
33
|
+
thread_local_accessor :workers, :default => {}
|
34
|
+
thread_local_accessor :listeners, :default => []
|
35
|
+
thread_local_accessor :listener_instances, :default => {}
|
31
36
|
|
32
37
|
# Start the Cloudist loop
|
33
38
|
#
|
@@ -41,15 +46,25 @@ module Cloudist
|
|
41
46
|
# * :host => 'localhost'
|
42
47
|
# * :port => 5672
|
43
48
|
# * :vhost => /
|
49
|
+
# * :heartbeat => 0
|
50
|
+
# * :logging => false
|
44
51
|
#
|
45
52
|
# Refer to default config below for how to set these as defaults
|
46
53
|
#
|
47
54
|
def start(options = {}, &block)
|
48
55
|
config = settings.update(options)
|
49
56
|
AMQP.start(config) do
|
50
|
-
self.instance_eval(&block)
|
57
|
+
self.instance_eval(&block) if block_given?
|
51
58
|
end
|
52
59
|
end
|
60
|
+
|
61
|
+
def connection
|
62
|
+
AMQP.connection
|
63
|
+
end
|
64
|
+
|
65
|
+
def connection=(conn)
|
66
|
+
AMQP.connection = conn
|
67
|
+
end
|
53
68
|
|
54
69
|
# Define a worker. Must be called inside start loop
|
55
70
|
#
|
@@ -58,6 +73,7 @@ module Cloudist
|
|
58
73
|
# }
|
59
74
|
#
|
60
75
|
# REMOVED
|
76
|
+
#
|
61
77
|
def worker(&block)
|
62
78
|
raise NotImplementedError, "This DSL format has been removed. Please use job('make.sandwich') {} instead."
|
63
79
|
end
|
@@ -73,8 +89,13 @@ module Cloudist
|
|
73
89
|
#
|
74
90
|
# Refer to sandwich_worker.rb example
|
75
91
|
#
|
76
|
-
def job(queue_name
|
77
|
-
|
92
|
+
def job(queue_name)
|
93
|
+
if block_given?
|
94
|
+
block = Proc.new
|
95
|
+
register_worker(queue_name, &block)
|
96
|
+
else
|
97
|
+
raise ArgumentError, "You must supply a block as the last argument"
|
98
|
+
end
|
78
99
|
end
|
79
100
|
|
80
101
|
# Registers a worker class to handle a specific queue
|
@@ -107,28 +128,27 @@ module Cloudist
|
|
107
128
|
job_queue = JobQueue.new(queue_name)
|
108
129
|
job_queue.subscribe do |request|
|
109
130
|
j = Job.new(request.payload.dup)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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)
|
131
|
+
begin
|
132
|
+
if block_given?
|
133
|
+
worker_instance = GenericWorker.new(j, job_queue.q)
|
134
|
+
worker_instance.process(&block)
|
135
|
+
elsif klass
|
136
|
+
worker_instance = klass.new(j, job_queue.q)
|
137
|
+
worker_instance.process
|
138
|
+
else
|
139
|
+
raise RuntimeError, "Failed to register worker, I need either a handler class or block."
|
126
140
|
end
|
141
|
+
rescue Exception => e
|
142
|
+
j.handle_error(e)
|
143
|
+
ensure
|
144
|
+
finished = Time.now.utc.to_f
|
145
|
+
log.debug("Finished Job in #{finished - request.start} seconds")
|
146
|
+
j.reply({:runtime => (finished - request.start)}, {:message_type => 'runtime'})
|
147
|
+
j.cleanup
|
127
148
|
end
|
128
|
-
j.cleanup
|
129
149
|
end
|
130
150
|
|
131
|
-
((
|
151
|
+
((self.workers[queue_name.to_s] ||= []) << job_queue).uniq!
|
132
152
|
end
|
133
153
|
|
134
154
|
# Accepts either a queue name or a job instance returned from enqueue.
|
@@ -136,14 +156,25 @@ module Cloudist
|
|
136
156
|
# will return all responses regardless of job id so you can use the job
|
137
157
|
# id to lookup a database record to update etc.
|
138
158
|
# When given a job instance it will only return messages from that job.
|
159
|
+
#
|
160
|
+
# DEPRECATED
|
161
|
+
#
|
139
162
|
def listen(*queue_names, &block)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
163
|
+
raise NotImplementedError, "This DSL method has been removed. Please use add_listener"
|
164
|
+
end
|
165
|
+
|
166
|
+
# Adds a listener class
|
167
|
+
def add_listener(klass)
|
168
|
+
raise ArgumentError, "Your listener must extend Cloudist::Listener" unless klass.superclass == Cloudist::Listener
|
169
|
+
raise ArgumentError, "Your listener must declare at least one queue to listen to. Use listen_to 'queue.name'" if klass.job_queue_names.nil?
|
170
|
+
|
171
|
+
klass.job_queue_names.each do |queue_name|
|
172
|
+
klass.subscribe(queue_name)
|
145
173
|
end
|
146
|
-
|
174
|
+
|
175
|
+
self.listeners << klass
|
176
|
+
|
177
|
+
return self.listeners
|
147
178
|
end
|
148
179
|
|
149
180
|
# Enqueues a job.
|
@@ -151,37 +182,52 @@ module Cloudist
|
|
151
182
|
# Returns Job instance
|
152
183
|
# Use Job#id to reference job later on.
|
153
184
|
def enqueue(job_queue_name, data = nil)
|
154
|
-
raise EnqueueError, "Incorrect arguments, you must include data when
|
185
|
+
raise EnqueueError, "Incorrect arguments, you must include data when enqueuing job" if data.nil?
|
155
186
|
# TODO: Detect if inside loop, if not use bunny sync
|
156
187
|
Cloudist::Publisher.enqueue(job_queue_name, data)
|
157
188
|
end
|
189
|
+
|
190
|
+
# Send a reply synchronously
|
191
|
+
# This uses bunny instead of AMQP and as such can be run outside
|
192
|
+
# of EventMachine and the Cloudist start loop.
|
193
|
+
#
|
194
|
+
# Usage: Cloudist.reply('make.sandwich', {:sandwhich_id => 12345})
|
195
|
+
#
|
196
|
+
# def reply(queue_name, job_id, data, options = {})
|
197
|
+
# headers = {
|
198
|
+
# :message_id => job_id,
|
199
|
+
# :message_type => "reply",
|
200
|
+
# # :event => 'working',
|
201
|
+
# :message_type => 'reply'
|
202
|
+
# }.update(options)
|
203
|
+
#
|
204
|
+
# payload = Cloudist::Payload.new(data, headers)
|
205
|
+
#
|
206
|
+
# queue = Cloudist::SyncReplyQueue.new(queue_name)
|
207
|
+
#
|
208
|
+
# queue.setup
|
209
|
+
# queue.publish_to_q(payload)
|
210
|
+
# end
|
158
211
|
|
159
212
|
# Call this at anytime inside the loop to exit the app.
|
160
213
|
def stop_safely
|
161
|
-
|
162
|
-
::
|
163
|
-
::
|
214
|
+
if EM.reactor_running?
|
215
|
+
::EM.add_timer(0.2) {
|
216
|
+
::AMQP.stop {
|
217
|
+
::EM.stop
|
218
|
+
puts "\n"
|
219
|
+
}
|
164
220
|
}
|
165
|
-
|
221
|
+
end
|
166
222
|
end
|
167
223
|
|
168
224
|
alias :stop :stop_safely
|
169
|
-
|
170
|
-
def closing?
|
171
|
-
::AMQP.closing?
|
172
|
-
end
|
173
|
-
|
174
|
-
def log
|
175
|
-
@@log ||= Logger.new($stdout)
|
176
|
-
end
|
177
|
-
|
178
|
-
def log=(log)
|
179
|
-
@@log = log
|
180
|
-
end
|
181
225
|
|
182
226
|
def handle_error(e)
|
183
227
|
log.error "#{e.class}: #{e.message}"#, :exception => e
|
184
|
-
|
228
|
+
e.backtrace.each do |line|
|
229
|
+
log.error line
|
230
|
+
end
|
185
231
|
end
|
186
232
|
|
187
233
|
def version
|
@@ -195,7 +241,9 @@ module Cloudist
|
|
195
241
|
:host => uri.host,
|
196
242
|
:user => uri.user,
|
197
243
|
:port => uri.port || 5672,
|
198
|
-
:pass => uri.password
|
244
|
+
:pass => uri.password,
|
245
|
+
:heartbeat => 0,
|
246
|
+
:logging => false
|
199
247
|
}
|
200
248
|
rescue Object => e
|
201
249
|
raise "invalid AMQP_URL: (#{uri.inspect}) #{e.class} -> #{e.message}"
|
@@ -214,14 +262,23 @@ module Cloudist
|
|
214
262
|
::Signal.trap('TERM'){ Cloudist.stop }
|
215
263
|
end
|
216
264
|
|
217
|
-
def
|
218
|
-
@@
|
265
|
+
def log
|
266
|
+
@@log ||= Logger.new($stdout)
|
267
|
+
end
|
268
|
+
|
269
|
+
def log=(log)
|
270
|
+
@@log = log
|
219
271
|
end
|
220
272
|
|
273
|
+
alias :install_signal_trap :signal_trap!
|
274
|
+
|
221
275
|
def remove_workers
|
222
|
-
|
276
|
+
self.workers.keys.each do |worker|
|
277
|
+
self.workers.delete(worker)
|
278
|
+
end
|
223
279
|
end
|
224
280
|
|
225
281
|
end
|
226
282
|
|
283
|
+
include Cloudist::EMTimerUtils
|
227
284
|
end
|