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,60 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Cloudist
|
4
|
+
class Application
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def start(options = {}, &block)
|
9
|
+
options = instance.settings.update(options)
|
10
|
+
AMQP.start(options) do
|
11
|
+
instance.setup_reconnect_hook!
|
12
|
+
|
13
|
+
instance.instance_eval(&block) if block_given?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def signal_trap!
|
18
|
+
::Signal.trap('INT') { Cloudist.stop }
|
19
|
+
::Signal.trap('TERM'){ Cloudist.stop }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def settings
|
24
|
+
@@settings ||= default_settings
|
25
|
+
end
|
26
|
+
|
27
|
+
def settings=(settings_hash)
|
28
|
+
@@settings = default_settings.update(settings_hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_settings
|
32
|
+
uri = URI.parse(ENV["AMQP_URL"] || 'amqp://guest:guest@localhost:5672/')
|
33
|
+
{
|
34
|
+
:vhost => uri.path,
|
35
|
+
:host => uri.host,
|
36
|
+
:user => uri.user,
|
37
|
+
:port => uri.port || 5672,
|
38
|
+
:pass => uri.password,
|
39
|
+
:heartbeat => 5,
|
40
|
+
:logging => false
|
41
|
+
}
|
42
|
+
rescue Object => e
|
43
|
+
raise "invalid AMQP_URL: (#{uri.inspect}) #{e.class} -> #{e.message}"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def setup_reconnect_hook!
|
49
|
+
AMQP.conn.connection_status do |status|
|
50
|
+
|
51
|
+
log.debug("AMQP connection status changed: #{status}")
|
52
|
+
|
53
|
+
if status == :disconnected
|
54
|
+
AMQP.conn.reconnect(true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'cloudist/core_ext/kernel'
|
2
|
+
require 'cloudist/core_ext/module'
|
3
|
+
|
4
|
+
# Extracted from ActiveSupport 3.0
|
5
|
+
class Class
|
6
|
+
|
7
|
+
# Taken from http://coderrr.wordpress.com/2008/04/10/lets-stop-polluting-the-threadcurrent-hash/
|
8
|
+
def thread_local_accessor name, options = {}
|
9
|
+
m = Module.new
|
10
|
+
m.module_eval do
|
11
|
+
class_variable_set :"@@#{name}", Hash.new {|h,k| h[k] = options[:default] }
|
12
|
+
end
|
13
|
+
m.module_eval %{
|
14
|
+
FINALIZER = lambda {|id| @@#{name}.delete id }
|
15
|
+
|
16
|
+
def #{name}
|
17
|
+
@@#{name}[Thread.current.object_id]
|
18
|
+
end
|
19
|
+
|
20
|
+
def #{name}=(val)
|
21
|
+
ObjectSpace.define_finalizer Thread.current, FINALIZER unless @@#{name}.has_key? Thread.current.object_id
|
22
|
+
@@#{name}[Thread.current.object_id] = val
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
class_eval do
|
27
|
+
include m
|
28
|
+
extend m
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Declare a class-level attribute whose value is inheritable by subclasses.
|
34
|
+
# Subclasses can change their own value and it will not impact parent class.
|
35
|
+
#
|
36
|
+
# class Base
|
37
|
+
# class_attribute :setting
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# class Subclass < Base
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# Base.setting = true
|
44
|
+
# Subclass.setting # => true
|
45
|
+
# Subclass.setting = false
|
46
|
+
# Subclass.setting # => false
|
47
|
+
# Base.setting # => true
|
48
|
+
#
|
49
|
+
# In the above case as long as Subclass does not assign a value to setting
|
50
|
+
# by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
|
51
|
+
# would read value assigned to parent class. Once Subclass assigns a value then
|
52
|
+
# the value assigned by Subclass would be returned.
|
53
|
+
#
|
54
|
+
# This matches normal Ruby method inheritance: think of writing an attribute
|
55
|
+
# on a subclass as overriding the reader method. However, you need to be aware
|
56
|
+
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
|
57
|
+
# In such cases, you don't want to do changes in places but use setters:
|
58
|
+
#
|
59
|
+
# Base.setting = []
|
60
|
+
# Base.setting # => []
|
61
|
+
# Subclass.setting # => []
|
62
|
+
#
|
63
|
+
# # Appending in child changes both parent and child because it is the same object:
|
64
|
+
# Subclass.setting << :foo
|
65
|
+
# Base.setting # => [:foo]
|
66
|
+
# Subclass.setting # => [:foo]
|
67
|
+
#
|
68
|
+
# # Use setters to not propagate changes:
|
69
|
+
# Base.setting = []
|
70
|
+
# Subclass.setting += [:foo]
|
71
|
+
# Base.setting # => []
|
72
|
+
# Subclass.setting # => [:foo]
|
73
|
+
#
|
74
|
+
# For convenience, a query method is defined as well:
|
75
|
+
#
|
76
|
+
# Subclass.setting? # => false
|
77
|
+
#
|
78
|
+
# Instances may overwrite the class value in the same way:
|
79
|
+
#
|
80
|
+
# Base.setting = true
|
81
|
+
# object = Base.new
|
82
|
+
# object.setting # => true
|
83
|
+
# object.setting = false
|
84
|
+
# object.setting # => false
|
85
|
+
# Base.setting # => true
|
86
|
+
#
|
87
|
+
# To opt out of the instance writer method, pass :instance_writer => false.
|
88
|
+
#
|
89
|
+
# object.setting = false # => NoMethodError
|
90
|
+
def class_attribute(*attrs)
|
91
|
+
instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer]
|
92
|
+
|
93
|
+
attrs.each do |name|
|
94
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
95
|
+
def self.#{name}() nil end
|
96
|
+
def self.#{name}?() !!#{name} end
|
97
|
+
|
98
|
+
def self.#{name}=(val)
|
99
|
+
singleton_class.class_eval do
|
100
|
+
remove_possible_method(:#{name})
|
101
|
+
define_method(:#{name}) { val }
|
102
|
+
end
|
103
|
+
|
104
|
+
if singleton_class?
|
105
|
+
class_eval do
|
106
|
+
remove_possible_method(:#{name})
|
107
|
+
def #{name}
|
108
|
+
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
val
|
113
|
+
end
|
114
|
+
|
115
|
+
remove_method :#{name} if method_defined?(:#{name})
|
116
|
+
def #{name}
|
117
|
+
defined?(@#{name}) ? @#{name} : self.class.#{name}
|
118
|
+
end
|
119
|
+
|
120
|
+
def #{name}?
|
121
|
+
!!#{name}
|
122
|
+
end
|
123
|
+
RUBY
|
124
|
+
|
125
|
+
attr_writer name if instance_writer
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def singleton_class?
|
131
|
+
# in case somebody is crazy enough to overwrite allocate
|
132
|
+
allocate = Class.instance_method(:allocate)
|
133
|
+
# object.class always points to a real (non-singleton) class
|
134
|
+
allocate.bind(self).call.class != self
|
135
|
+
rescue TypeError
|
136
|
+
# MRI/YARV/JRuby all disallow creating new instances of a singleton class
|
137
|
+
true
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Kernel
|
2
|
+
# Returns the object's singleton class.
|
3
|
+
def singleton_class
|
4
|
+
class << self
|
5
|
+
self
|
6
|
+
end
|
7
|
+
end unless respond_to?(:singleton_class) # exists in 1.9.2
|
8
|
+
|
9
|
+
# class_eval on an object acts like singleton_class.class_eval.
|
10
|
+
def class_eval(*args, &block)
|
11
|
+
singleton_class.class_eval(*args, &block)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Cloudist
|
2
|
+
module Encoding
|
3
|
+
def encode(message)
|
4
|
+
Marshal.dump(message)
|
5
|
+
end
|
6
|
+
|
7
|
+
def decode(message)
|
8
|
+
raise ArgumentError, "First argument can't be nil" if message.nil?
|
9
|
+
return message unless message.is_a?(String)
|
10
|
+
Marshal.load(message)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/cloudist/errors.rb
CHANGED
data/lib/cloudist/job.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
module Cloudist
|
2
2
|
class Job
|
3
|
-
attr_reader :payload
|
3
|
+
attr_reader :payload, :reply_queue
|
4
|
+
|
4
5
|
def initialize(payload)
|
5
6
|
@payload = payload
|
7
|
+
|
8
|
+
if payload.reply_to
|
9
|
+
@reply_queue = ReplyQueue.new(payload.reply_to)
|
10
|
+
reply_queue.setup
|
11
|
+
else
|
12
|
+
@reply_queue = nil
|
13
|
+
end
|
6
14
|
end
|
7
15
|
|
8
16
|
def id
|
@@ -13,34 +21,34 @@ module Cloudist
|
|
13
21
|
payload.body
|
14
22
|
end
|
15
23
|
|
24
|
+
def body
|
25
|
+
data
|
26
|
+
end
|
27
|
+
|
16
28
|
def log
|
17
29
|
Cloudist.log
|
18
30
|
end
|
19
31
|
|
20
32
|
def cleanup
|
21
|
-
|
33
|
+
# :noop
|
22
34
|
end
|
23
35
|
|
24
36
|
def reply(body, headers = {}, options = {})
|
37
|
+
raise ArgumentError, "Reply queue not ready" unless reply_queue
|
38
|
+
|
25
39
|
options = {
|
26
40
|
:echo => false
|
27
41
|
}.update(options)
|
28
42
|
|
29
43
|
headers = {
|
30
|
-
:message_id => payload.
|
44
|
+
:message_id => payload.id,
|
31
45
|
:message_type => "reply"
|
32
46
|
}.update(headers)
|
33
47
|
|
34
|
-
# Echo the payload back
|
35
|
-
# body.merge!(payload.body) if options[:echo] == true
|
36
|
-
|
37
48
|
reply_payload = Payload.new(body, headers)
|
49
|
+
published_headers = reply_queue.publish(reply_payload)
|
38
50
|
|
39
|
-
|
40
|
-
reply_queue.setup
|
41
|
-
published_headers = reply_queue.publish_to_q(reply_payload)
|
42
|
-
|
43
|
-
log.debug("Replying: #{body.inspect} HEADERS: #{headers.inspect}")
|
51
|
+
reply_payload
|
44
52
|
end
|
45
53
|
|
46
54
|
# Sends a progress update
|
@@ -51,23 +59,18 @@ module Cloudist
|
|
51
59
|
end
|
52
60
|
|
53
61
|
def event(event_name, event_data = {}, options = {})
|
54
|
-
event_data
|
62
|
+
event_data ||= {}
|
55
63
|
reply(event_data, {:event => event_name, :message_type => 'event'}, options)
|
56
64
|
end
|
57
65
|
|
58
66
|
def safely(&blk)
|
59
|
-
# begin
|
60
67
|
yield
|
61
68
|
rescue Exception => e
|
62
69
|
handle_error(e)
|
63
|
-
# end
|
64
|
-
# result
|
65
70
|
end
|
66
71
|
|
67
|
-
# This will transfer the Exception object to the client
|
68
72
|
def handle_error(e)
|
69
|
-
|
70
|
-
reply({:exception => e}, {:message_type => 'error'})
|
73
|
+
reply({:exception => e.class.name.to_s, :message => e.message.to_s, :backtrace => e.backtrace}, {:message_type => 'error'})
|
71
74
|
end
|
72
75
|
|
73
76
|
def method_missing(meth, *args, &blk)
|
data/lib/cloudist/listener.rb
CHANGED
@@ -1,79 +1,133 @@
|
|
1
|
+
require "active_support"
|
1
2
|
module Cloudist
|
2
3
|
class Listener
|
3
|
-
include
|
4
|
+
include ActiveSupport::Callbacks
|
4
5
|
|
5
|
-
attr_reader :job_queue_name, :
|
6
|
+
attr_reader :job_queue_name, :payload
|
7
|
+
class_attribute :job_queue_names
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
class << self
|
10
|
+
def listen_to(*job_queue_names)
|
11
|
+
self.job_queue_names = job_queue_names.map { |q| Utils.reply_prefix(q) }
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
def subscribe(queue_name)
|
15
|
+
raise RuntimeError, "You can't subscribe until EM is running" unless EM.reactor_running?
|
16
|
+
|
17
|
+
reply_queue = Cloudist::ReplyQueue.new(queue_name)
|
18
|
+
reply_queue.subscribe do |request|
|
19
|
+
instance = Cloudist.listener_instances[queue_name] ||= new
|
20
|
+
instance.handle_request(request)
|
21
|
+
end
|
22
|
+
|
23
|
+
queue_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def before(*args, &block)
|
27
|
+
set_callback(:call, :before, *args, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def after(*args, &block)
|
31
|
+
set_callback(:call, :after, *args, &block)
|
20
32
|
end
|
21
33
|
end
|
22
34
|
|
23
|
-
|
24
|
-
|
25
|
-
|
35
|
+
define_callbacks :call, :rescuable => true
|
36
|
+
|
37
|
+
def handle_request(request)
|
38
|
+
@payload = request.payload
|
39
|
+
key = [payload.message_type.to_s, payload.headers[:event]].compact.join(':')
|
26
40
|
|
27
|
-
|
41
|
+
meth, *args = handle_key(key)
|
28
42
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# If we want to get a callback on every event, do it here
|
35
|
-
if callbacks.has_key?('everything')
|
36
|
-
callbacks['everything'].each do |c|
|
37
|
-
c.call(payload)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
if callbacks.has_key?('error')
|
42
|
-
callbacks['error'].each do |c|
|
43
|
-
# c.call(payload)
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
if callbacks.has_key?(key)
|
49
|
-
callbacks_to_call = callbacks[key]
|
50
|
-
callbacks_to_call.each do |c|
|
51
|
-
c.call(payload)
|
52
|
-
end
|
43
|
+
if meth.present? && self.respond_to?(meth)
|
44
|
+
if method(meth).arity <= args.size
|
45
|
+
call(meth, args.first(method(meth).arity))
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Unable to fire callback (#{meth}) because we don't have enough args"
|
53
48
|
end
|
54
49
|
end
|
55
50
|
end
|
56
51
|
|
57
|
-
def
|
58
|
-
|
52
|
+
def id
|
53
|
+
payload.id
|
59
54
|
end
|
60
55
|
|
61
|
-
def
|
62
|
-
|
56
|
+
def data
|
57
|
+
payload.body
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_key(key)
|
61
|
+
key = key.split(':', 2)
|
62
|
+
return [nil, nil] if key.empty?
|
63
|
+
|
64
|
+
method_and_args = [key.shift.to_sym]
|
65
|
+
case method_and_args[0]
|
66
|
+
when :event
|
67
|
+
if key.size > 0 && self.respond_to?(key.first)
|
68
|
+
method_and_args = [key.shift]
|
69
|
+
end
|
70
|
+
method_and_args << key
|
71
|
+
|
72
|
+
when :progress
|
73
|
+
method_and_args << payload.progress
|
74
|
+
method_and_args << payload.description
|
75
|
+
|
76
|
+
when :runtime
|
77
|
+
method_and_args << payload.runtime
|
63
78
|
|
64
|
-
|
65
|
-
key = [meth.to_s, args.shift].compact.join(':')
|
79
|
+
when :reply
|
66
80
|
|
67
|
-
|
68
|
-
|
69
|
-
|
81
|
+
when :update
|
82
|
+
|
83
|
+
when :error
|
84
|
+
# method_and_args << Cloudist::SafeError.new(payload)
|
85
|
+
method_and_args << Hashie::Mash.new(payload.body)
|
86
|
+
|
87
|
+
when :log
|
88
|
+
method_and_args << payload.message
|
89
|
+
method_and_args << payload.level
|
90
|
+
|
91
|
+
else
|
92
|
+
method_and_args << data if method(method_and_args[0]).arity == 1
|
93
|
+
end
|
94
|
+
|
95
|
+
return method_and_args
|
96
|
+
end
|
97
|
+
|
98
|
+
def call(meth, args)
|
99
|
+
run_callbacks :call do
|
100
|
+
if args.empty?
|
101
|
+
send(meth)
|
70
102
|
else
|
71
|
-
(
|
103
|
+
send(meth, *args)
|
72
104
|
end
|
73
|
-
else
|
74
|
-
super
|
75
105
|
end
|
76
106
|
end
|
77
107
|
|
108
|
+
def progress(pct)
|
109
|
+
# :noop
|
110
|
+
end
|
111
|
+
|
112
|
+
def runtime(seconds)
|
113
|
+
# :noop
|
114
|
+
end
|
115
|
+
|
116
|
+
def event(type)
|
117
|
+
# :noop
|
118
|
+
end
|
119
|
+
|
120
|
+
def log(message, level)
|
121
|
+
# :noop
|
122
|
+
end
|
123
|
+
|
124
|
+
def error(e)
|
125
|
+
# :noop
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
class GenericListener < Listener
|
131
|
+
|
78
132
|
end
|
79
133
|
end
|