cloudist 0.2.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile +15 -11
  2. data/Gemfile.lock +20 -7
  3. data/README.md +61 -39
  4. data/VERSION +1 -1
  5. data/cloudist.gemspec +50 -16
  6. data/examples/amqp/Gemfile +3 -0
  7. data/examples/amqp/Gemfile.lock +12 -0
  8. data/examples/amqp/amqp_consumer.rb +56 -0
  9. data/examples/amqp/amqp_publisher.rb +50 -0
  10. data/examples/queue_message.rb +7 -7
  11. data/examples/sandwich_client_with_custom_listener.rb +77 -0
  12. data/examples/sandwich_worker_with_class.rb +22 -7
  13. data/lib/cloudist.rb +113 -56
  14. data/lib/cloudist/application.rb +60 -0
  15. data/lib/cloudist/core_ext/class.rb +139 -0
  16. data/lib/cloudist/core_ext/kernel.rb +13 -0
  17. data/lib/cloudist/core_ext/module.rb +11 -0
  18. data/lib/cloudist/encoding.rb +13 -0
  19. data/lib/cloudist/errors.rb +2 -0
  20. data/lib/cloudist/job.rb +21 -18
  21. data/lib/cloudist/listener.rb +108 -54
  22. data/lib/cloudist/message.rb +97 -0
  23. data/lib/cloudist/messaging.rb +29 -0
  24. data/lib/cloudist/payload.rb +45 -105
  25. data/lib/cloudist/payload_old.rb +155 -0
  26. data/lib/cloudist/publisher.rb +7 -2
  27. data/lib/cloudist/queue.rb +152 -0
  28. data/lib/cloudist/queues/basic_queue.rb +83 -53
  29. data/lib/cloudist/queues/job_queue.rb +13 -24
  30. data/lib/cloudist/queues/reply_queue.rb +13 -21
  31. data/lib/cloudist/request.rb +33 -7
  32. data/lib/cloudist/worker.rb +9 -2
  33. data/lib/cloudist_old.rb +300 -0
  34. data/lib/em/em_timer_utils.rb +55 -0
  35. data/lib/em/iterator.rb +27 -0
  36. data/spec/cloudist/message_spec.rb +91 -0
  37. data/spec/cloudist/messaging_spec.rb +19 -0
  38. data/spec/cloudist/payload_spec.rb +10 -4
  39. data/spec/cloudist/payload_spec_2_spec.rb +78 -0
  40. data/spec/cloudist/queue_spec.rb +16 -0
  41. data/spec/cloudist_spec.rb +49 -45
  42. data/spec/spec_helper.rb +0 -1
  43. data/spec/support/amqp.rb +16 -0
  44. metadata +112 -102
  45. data/examples/extending_values.rb +0 -44
  46. data/examples/sandwich_client.rb +0 -57
  47. data/lib/cloudist/callback.rb +0 -16
  48. data/lib/cloudist/callback_methods.rb +0 -19
  49. data/lib/cloudist/callbacks/error_callback.rb +0 -14
@@ -4,10 +4,15 @@ module Cloudist
4
4
  class << self
5
5
  def enqueue(queue_name, data)
6
6
  payload = Cloudist::Payload.new(data)
7
-
7
+
8
8
  queue = Cloudist::JobQueue.new(queue_name)
9
+
9
10
  queue.setup
10
- queue.publish(payload)
11
+
12
+ # send_message = proc {
13
+ queue.publish(payload)
14
+ # }
15
+ # EM.next_tick(&send_message)
11
16
 
12
17
  return Job.new(payload)
13
18
  end
@@ -0,0 +1,152 @@
1
+ module Cloudist
2
+ class Queue
3
+
4
+ attr_reader :options, :name, :channel, :q, :ex
5
+
6
+ class_attribute :cached_queues
7
+
8
+ def initialize(name, options = {})
9
+ self.class.cached_queues ||= {}
10
+
11
+ options = {
12
+ :auto_delete => false,
13
+ :durable => true
14
+ }.update(options)
15
+
16
+ @name, @options = name, options
17
+
18
+ setup
19
+ p self.cached_queues.keys
20
+
21
+ log.debug(tag)
22
+ purge
23
+ end
24
+
25
+ def purge
26
+ q.purge
27
+ end
28
+
29
+ def inspect
30
+ "<#{self.class.name} name=#{name} exchange=#{ex ? ex.name : 'nil'}>"
31
+ end
32
+
33
+ def log
34
+ Cloudist.log
35
+ end
36
+
37
+ def tag
38
+ [].tap { |a|
39
+ a << "queue=#{q.name}" if q
40
+ a << "exchange=#{ex.name}" if ex
41
+ }.join(' ')
42
+ end
43
+
44
+ def publish(msg)
45
+ raise ArgumentError, "Publish expects a Cloudist::Message object" unless msg.is_a?(Cloudist::Message)
46
+
47
+ body, headers = msg.encoded
48
+ # EM.defer {
49
+ publish_to_ex(body, headers)
50
+ # }
51
+
52
+ p msg.body.to_hash
53
+ end
54
+
55
+ # def channel
56
+ # self.class.channel
57
+ # end
58
+ #
59
+ # def q
60
+ # self.class.q
61
+ # end
62
+ #
63
+ # def ex
64
+ # self.class.ex
65
+ # end
66
+
67
+ def publish_to_ex(body, headers = {})
68
+ ex.publish(body, headers)
69
+ end
70
+
71
+ def publish_to_q(body, headers = {})
72
+ q.publish(body, headers)
73
+ end
74
+
75
+ def teardown
76
+ q.unsubscribe
77
+ channel.close
78
+ log.debug "AMQP Unsubscribed: #{tag}"
79
+ end
80
+
81
+ def destroy
82
+ teardown
83
+ end
84
+
85
+ def subscribe(options = {}, &block)
86
+ options[:ack] = true
87
+ q.subscribe(options) do |queue_header, encoded_message|
88
+ request = Cloudist::Request.new(self, encoded_message, queue_header)
89
+
90
+ msg = Cloudist::Message.new(*request.for_message)
91
+
92
+ EM.defer do
93
+ begin
94
+ raise Cloudist::ExpiredMessage if request.expired?
95
+ yield msg
96
+
97
+ rescue Cloudist::ExpiredMessage
98
+ log.error "AMQP Message Timeout: #{tag} ttl=#{request.ttl} age=#{request.age}"
99
+
100
+ rescue Exception => e
101
+ Cloudist.handle_error(e)
102
+
103
+ ensure
104
+ request.ack unless Cloudist.closing?
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def setup
113
+ if self.class.cached_queues.keys.include?(name.to_sym)
114
+ @q = self.class.cached_queues[name.to_sym][:q]
115
+ @ex = self.class.cached_queues[name.to_sym][:ex]
116
+ @channel = self.class.cached_queues[name.to_sym][:channel]
117
+ setup_binding
118
+ else
119
+ puts "Setup"
120
+
121
+ setup_channel
122
+ setup_queue
123
+ setup_exchange
124
+
125
+ self.class.cached_queues[name.to_sym] = {:q => q, :ex => ex, :channel => channel}
126
+ end
127
+ setup_binding
128
+ end
129
+
130
+ def setup_channel
131
+ @channel = ::AMQP::Channel.new
132
+
133
+ # Set up QOS. If you do not do this then the subscribe in receive_message
134
+ # will get overwelmd and the whole thing will collapse in on itself.
135
+ channel.prefetch(1)
136
+ end
137
+
138
+ def setup_queue
139
+ @q = channel.queue(name, options)
140
+ end
141
+
142
+ def setup_exchange
143
+ @ex = channel.direct(name)
144
+ # setup_binding
145
+ end
146
+
147
+ def setup_binding
148
+ q.bind(ex)
149
+ end
150
+
151
+ end
152
+ end
@@ -4,92 +4,122 @@ module Cloudist
4
4
 
5
5
  module Queues
6
6
  class BasicQueue
7
- attr_reader :queue_name, :opts
8
- attr_reader :q, :ex, :mq
9
-
10
- def initialize(queue_name, opts = {})
11
- opts = {
7
+ attr_reader :queue_name, :options
8
+ attr_reader :queue, :exchange, :channel, :prefetch
9
+
10
+ alias :q :queue
11
+ alias :ex :exchange
12
+ alias :mq :channel
13
+
14
+ def initialize(queue_name, options = {})
15
+ @prefetch ||= options.delete(:prefetch) || 1
16
+
17
+ options = {
12
18
  :auto_delete => true,
13
19
  :durable => false,
14
- :prefetch => 1
15
- }.update(opts)
20
+ :nowait => true
21
+ }.update(options)
16
22
 
17
- @queue_name, @opts = queue_name, opts
23
+ @queue_name, @options = queue_name, options
24
+
25
+ setup
26
+ end
27
+
28
+ def inspect
29
+ "<#{self.class.name} queue_name=#{queue_name}>"
18
30
  end
19
31
 
20
32
  def setup
21
- return if @setup == true
22
-
23
- @mq = MQ.new
24
- @q = @mq.queue(queue_name, opts)
25
- # if we don't specify an exchange name it defaults to the queue_name
26
- @ex = @mq.direct(opts[:exchange_name] || queue_name)
27
-
28
- q.bind(ex) if ex
29
-
33
+ return if @setup.eql?(true)
34
+
35
+ @channel ||= AMQP::Channel.new(Cloudist.connection) do
36
+ channel.prefetch(self.prefetch, false) if self.prefetch.present?
37
+ end
38
+
39
+ @queue = @channel.queue(queue_name, options)
40
+
41
+ setup_exchange
42
+
30
43
  @setup = true
31
44
  end
45
+
46
+ def setup_exchange
47
+ @exchange = channel.direct("")
48
+ end
49
+
50
+ # def setup_exchange
51
+ # @exchange = channel.direct(queue_name)
52
+ # queue.bind(exchange)
53
+ # end
32
54
 
33
55
  def log
34
56
  Cloudist.log
35
57
  end
36
58
 
37
59
  def tag
38
- s = "queue=#{q.name}"
39
- s += " exchange=#{ex.name}" if ex
60
+ s = "queue=#{queue.name}"
61
+ s += " exchange=#{exchange.name}" if exchange
40
62
  s
41
63
  end
42
-
43
- def subscribe(amqp_opts={}, opts={})
44
- setup
45
- print_status
46
- q.subscribe(amqp_opts) do |queue_header, json_encoded_message|
47
- next if Cloudist.closing?
48
-
49
- request = Cloudist::Request.new(self, json_encoded_message, queue_header)
50
-
51
- begin
52
- raise Cloudist::ExpiredMessage if request.expired?
53
- yield request if block_given?
54
- # finished = Time.now.utc.to_i
55
- # log.debug("Finished Job in #{finished - request.start} seconds")
56
-
57
- rescue Cloudist::ExpiredMessage
58
- log.error "AMQP Message Timeout: #{tag} ttl=#{request.ttl} age=#{request.age}"
59
- request.ack if amqp_opts[:ack]
60
-
61
- rescue => e
62
- request.ack if amqp_opts[:ack]
63
- Cloudist.handle_error(e)
64
- end
64
+
65
+ def subscribe(&block)
66
+ queue.subscribe(:ack => true) do |queue_header, encoded_message|
67
+ # next if Cloudist.closing?
68
+
69
+ request = Cloudist::Request.new(self, encoded_message, queue_header)
70
+
71
+ handle_request = proc {
72
+ begin
73
+ raise Cloudist::ExpiredMessage if request.expired?
74
+ # yield request if block_given?
75
+ block.call(request)
76
+
77
+ rescue Cloudist::ExpiredMessage
78
+ log.error "AMQP Message Timeout: #{tag} ttl=#{request.ttl} age=#{request.age}"
79
+
80
+ rescue => e
81
+ Cloudist.handle_error(e)
82
+ ensure
83
+ request.ack
84
+ # unless Cloudist.closing?
85
+ # finished = Time.now.utc.to_i
86
+ # log.debug("Finished Job in #{finished - request.start} seconds")
87
+ end
88
+ }
89
+
90
+ handle_ack = proc {
91
+ request.ack
92
+ }
93
+
94
+ EM.defer(handle_request, handle_ack)
65
95
  end
66
96
  log.info "AMQP Subscribed: #{tag}"
67
97
  self
68
98
  end
69
99
 
70
100
  def print_status
71
- q.status{ |num_messages, num_consumers|
72
- log.info("STATUS: #{q.name}: JOBS: #{num_messages} WORKERS: #{num_consumers+1}")
73
- }
101
+ # queue.status{ |num_messages, num_consumers|
102
+ # log.info("STATUS: #{queue.name}: JOBS: #{num_messages} WORKERS: #{num_consumers+1}")
103
+ # }
74
104
  end
75
105
 
76
106
  def publish(payload)
77
107
  payload.set_reply_to(queue_name)
78
- body, headers = payload.formatted
79
- ex.publish(body, headers)
80
- payload.publish
108
+ body, headers = payload.to_a
109
+ headers.merge!(:routing_key => queue.name)
110
+ exchange.publish(body, headers)
81
111
  end
82
112
 
83
113
  def publish_to_q(payload)
84
- body, headers = payload.formatted
85
- q.publish(body, headers)
86
- payload.publish
114
+ body, headers = payload.to_a
115
+ # headers.merge!(:routing_key => queue.name)
116
+ queue.publish(body, headers)
87
117
  return headers
88
118
  end
89
119
 
90
120
  def teardown
91
- @q.unsubscribe
92
- @mq.close
121
+ @queue.unsubscribe
122
+ @channel.close
93
123
  log.debug "AMQP Unsubscribed: #{tag}"
94
124
  end
95
125
 
@@ -1,28 +1,17 @@
1
1
  module Cloudist
2
2
  class JobQueue < Cloudist::Queues::BasicQueue
3
- attr_reader :prefetch
4
-
5
- def initialize(queue_name, opts={})
6
- @prefetch = opts.delete(:prefetch) || 1
7
- opts[:auto_delete] = false
8
-
9
- super(queue_name, opts)
10
- end
11
-
12
- def setup
13
- super
14
- @mq.prefetch(self.prefetch)
15
- end
16
-
17
- def subscribe(amqp_opts={}, opts={})
18
- amqp_opts[:ack] ||= true
19
- super(amqp_opts, opts) do |request|
20
- begin
21
- yield request if block_given?
22
- ensure
23
- request.ack unless amqp_opts[:auto_ack] == false || Cloudist.closing?
24
- end
25
- end
26
- end
3
+
4
+ # def initialize(queue_name, options={})
5
+ # @prefetch = 1
6
+ # # opts[:auto_delete] = false
7
+ #
8
+ # super(queue_name, options)
9
+ # end
10
+
11
+ # def setup_exchange
12
+ # @exchange = channel.direct(queue_name)
13
+ # queue.bind(exchange)
14
+ # end
15
+
27
16
  end
28
17
  end
@@ -1,31 +1,23 @@
1
1
  module Cloudist
2
2
  class ReplyQueue < Cloudist::Queues::BasicQueue
3
- def initialize(queue_name, opts={})
4
- opts[:auto_delete] = true
5
- opts[:nowait] = false
6
- super
3
+ def initialize(queue_name, options={})
4
+ options[:auto_delete] = true
5
+ options[:nowait] = true
6
+
7
+ @prefetch = 2
8
+
9
+ super(queue_name, options)
7
10
  end
8
-
9
- def setup(key = nil)
10
- @mq = MQ.new
11
- @q = @mq.queue(queue_name, opts)
12
- @ex = @mq.direct
13
- if key
14
- @q.bind(@ex, :key => key)
15
- else
16
- @q.bind(@ex)
17
- end
18
- end
19
-
20
- # def subscribe(amqp_opts={}, opts={})
21
- # super(amqp_opts, opts) do |request|
11
+
12
+ # def subscribe(&block)
13
+ # super do |request|
22
14
  # yield request if block_given?
23
- # self.destroy
15
+ # teardown
24
16
  # end
25
17
  # end
26
- #
18
+
27
19
  # def teardown
28
- # @q.delete
20
+ # queue.delete
29
21
  # super
30
22
  # end
31
23
 
@@ -1,31 +1,55 @@
1
1
  module Cloudist
2
2
  class Request
3
- attr_reader :queue_header, :qobj, :payload, :start, :headers
3
+ include Cloudist::Encoding
4
+
5
+ attr_reader :queue_header, :qobj, :payload, :start, :headers, :body
4
6
 
5
7
  def initialize(queue, encoded_body, queue_header)
6
8
  @qobj, @queue_header = queue, queue_header
7
9
 
10
+ @body = decode(encoded_body)
11
+ @headers = parse_custom_headers(queue_header)
12
+
8
13
  @payload = Cloudist::Payload.new(encoded_body, queue_header.headers.dup)
9
- @headers = @payload.parse_custom_headers
14
+ @headers = @payload.headers
10
15
 
11
- @start = Time.now.utc.to_i
16
+ @start = Time.now.utc.to_f
17
+ end
18
+
19
+ def parse_custom_headers(amqp_headers)
20
+ h = amqp_headers.headers.dup
21
+
22
+ h[:published_on] = h[:published_on].to_i
23
+
24
+ h[:ttl] = h[:ttl].to_i rescue -1
25
+ h[:ttl] = -1 if h[:ttl] == 0
26
+
27
+ h
28
+ end
29
+
30
+ def for_message
31
+ [body.dup, queue_header.headers.dup]
12
32
  end
13
33
 
14
34
  def q
15
- qobj.q
35
+ qobj.queue
16
36
  end
17
37
 
18
38
  def ex
19
- qobj.ex
39
+ qobj.exchange
20
40
  end
21
41
 
22
42
  def mq
23
- qobj.mq
43
+ qobj.channel
44
+ end
45
+
46
+ def channel
47
+ mq
24
48
  end
25
49
 
26
50
  def age
27
51
  return -1 unless headers[:published_on]
28
- start - headers[:published_on]
52
+ start - headers[:published_on].to_f
29
53
  end
30
54
 
31
55
  def ttl
@@ -45,6 +69,8 @@ module Cloudist
45
69
  return if acked?
46
70
  queue_header.ack
47
71
  @acked = true
72
+ rescue AMQP::ChannelClosedError => e
73
+ Cloudist.handle_error(e)
48
74
  end
49
75
 
50
76
  end