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.
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