droid19 1.0.2

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.
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ ruby_19 = RUBY_VERSION =~ /\A1\.9/
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "droid"
10
+ gem.name << "19" if ruby_19
11
+ gem.summary = %Q{AMQP Wrapper Library}
12
+ gem.description = %Q{Easy to use AMQP Library with constructs for typical usage patterns}
13
+ gem.email = "ricardo@heroku.com"
14
+ gem.homepage = "http://heroku.com"
15
+ gem.authors = ["Ricardo Chimal, Jr."]
16
+
17
+ gem.add_development_dependency "baconmocha", ">= 0"
18
+
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ gem.add_dependency 'json_pure', '>= 1.2.0'
21
+ gem.add_dependency 'rest-client', '>= 1.2.0'
22
+ gem.add_dependency 'amqp', '0.6.7'
23
+ gem.add_dependency 'bunny', '~> 0.6.0'
24
+ gem.add_dependency 'SystemTimer', '~> 1.2.0' unless ruby_19
25
+ gem.add_dependency 'eventmachine_httpserver', '0.2.0'
26
+ end
27
+ Jeweler::GemcutterTasks.new
28
+ rescue LoadError
29
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
30
+ end
31
+
32
+ require 'rake/testtask'
33
+ Rake::TestTask.new(:spec) do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.verbose = true
37
+ end
38
+
39
+ task :spec => :check_dependencies
40
+
41
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.2
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+
5
+ require 'pp'
6
+ require 'droid/sync'
7
+
8
+ queue_name = ARGV.shift or fail "usage: #{File.basename($0)} <queue>"
9
+
10
+ b = Droid.bunny
11
+ q = b.queue(queue_name)
12
+
13
+ puts "Bleeding queue #{queue_name}"
14
+
15
+ while ((msg = q.pop)[:payload] != :queue_empty) do
16
+ pp msg
17
+ end
18
+
19
+ puts "done."
@@ -0,0 +1,97 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{droid}
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ricardo Chimal, Jr."]
12
+ s.date = %q{2010-11-12}
13
+ s.default_executable = %q{bleedq}
14
+ s.description = %q{Easy to use AMQP Library with constructs for typical usage patterns}
15
+ s.email = %q{ricardo@heroku.com}
16
+ s.executables = ["bleedq"]
17
+ s.files = [
18
+ ".gitignore",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "bin/bleedq",
22
+ "droid.gemspec",
23
+ "examples/async_reply.rb",
24
+ "examples/heroku_async_reply.rb",
25
+ "examples/sync.rb",
26
+ "examples/worker.rb",
27
+ "lib/droid.rb",
28
+ "lib/droid/em.rb",
29
+ "lib/droid/heroku.rb",
30
+ "lib/droid/heroku/local_stats.rb",
31
+ "lib/droid/heroku/logger_client.rb",
32
+ "lib/droid/heroku/memcache_cluster.rb",
33
+ "lib/droid/heroku/stats.rb",
34
+ "lib/droid/json_server.rb",
35
+ "lib/droid/monkey.rb",
36
+ "lib/droid/publish.rb",
37
+ "lib/droid/queue.rb",
38
+ "lib/droid/request.rb",
39
+ "lib/droid/sync.rb",
40
+ "lib/droid/utilization.rb",
41
+ "lib/droid/utils.rb",
42
+ "lib/heroku_droid.rb",
43
+ "lib/local_stats.rb",
44
+ "lib/memcache_cluster.rb",
45
+ "lib/stats.rb",
46
+ "spec/publish_spec.rb",
47
+ "spec/response_spec.rb",
48
+ "spec/spec_helper.rb",
49
+ "spec/utils_spec.rb",
50
+ "spec/wait_for_port_spec.rb"
51
+ ]
52
+ s.homepage = %q{http://heroku.com}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubygems_version = %q{1.3.6}
56
+ s.summary = %q{AMQP Wrapper Library}
57
+ s.test_files = [
58
+ "spec/publish_spec.rb",
59
+ "spec/response_spec.rb",
60
+ "spec/spec_helper.rb",
61
+ "spec/utils_spec.rb",
62
+ "spec/wait_for_port_spec.rb",
63
+ "examples/async_reply.rb",
64
+ "examples/heroku_async_reply.rb",
65
+ "examples/sync.rb",
66
+ "examples/worker.rb"
67
+ ]
68
+
69
+ if s.respond_to? :specification_version then
70
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
71
+ s.specification_version = 3
72
+
73
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
74
+ s.add_development_dependency(%q<baconmocha>, [">= 0"])
75
+ s.add_runtime_dependency(%q<json_pure>, [">= 1.2.0"])
76
+ s.add_runtime_dependency(%q<rest-client>, [">= 1.2.0"])
77
+ s.add_runtime_dependency(%q<amqp>, ["= 0.6.7"])
78
+ s.add_runtime_dependency(%q<bunny>, ["~> 0.6.0"])
79
+ s.add_runtime_dependency(%q<eventmachine_httpserver>, ["= 0.2.0"])
80
+ else
81
+ s.add_dependency(%q<baconmocha>, [">= 0"])
82
+ s.add_dependency(%q<json_pure>, [">= 1.2.0"])
83
+ s.add_dependency(%q<rest-client>, [">= 1.2.0"])
84
+ s.add_dependency(%q<amqp>, ["= 0.6.7"])
85
+ s.add_dependency(%q<bunny>, ["~> 0.6.0"])
86
+ s.add_dependency(%q<eventmachine_httpserver>, ["= 0.2.0"])
87
+ end
88
+ else
89
+ s.add_dependency(%q<baconmocha>, [">= 0"])
90
+ s.add_dependency(%q<json_pure>, [">= 1.2.0"])
91
+ s.add_dependency(%q<rest-client>, [">= 1.2.0"])
92
+ s.add_dependency(%q<amqp>, ["= 0.6.7"])
93
+ s.add_dependency(%q<bunny>, ["~> 0.6.0"])
94
+ s.add_dependency(%q<eventmachine_httpserver>, ["= 0.2.0"])
95
+ end
96
+ end
97
+
@@ -0,0 +1,25 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'droid'
3
+
4
+ def log
5
+ Droid.log
6
+ end
7
+
8
+ Droid.new('Example Reply') do |droid|
9
+ droid.worker('example.target').subscribe do |req|
10
+ log.debug "headers: #{req.header.headers.inspect}"
11
+ log.debug "event_hash should be woot -> #{req.droid_headers[:event_hash]}"
12
+ req.reply(:target_received_at => Time.now.to_i)
13
+ req.ack
14
+ end
15
+
16
+ droid.listener('example.check.target').subscribe do |req|
17
+ req.publish('example.target', { :checking => Time.now.to_i }) do |req2|
18
+ log.debug "event_hash should be woot -> #{req2.droid_headers[:event_hash]}"
19
+ log.info "We're done checking!"
20
+ Droid.stop_safe
21
+ end
22
+ end
23
+
24
+ droid.timer(2) { Droid.publish('example.check.target', {:sent_at => Time.now.to_i}, {:event_hash => "woot"}) }
25
+ end
@@ -0,0 +1,22 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib/'
2
+ require 'heroku_droid'
3
+
4
+ def log
5
+ Droid.log
6
+ end
7
+
8
+ HerokuDroid.new('Example Reply') do |droid|
9
+ droid.worker('example.target').subscribe do |req|
10
+ req.reply(:target_received_at => Time.now.to_i)
11
+ req.ack
12
+ end
13
+
14
+ droid.listener('example.check.target').subscribe do |req|
15
+ req.publish('example.target', { :checking => Time.now.to_i }) do |req2|
16
+ log.info "We're done checking!"
17
+ Droid.stop_safe
18
+ end
19
+ end
20
+
21
+ EM.add_timer(2) { Droid.publish('example.check.target', :sent_at => Time.now.to_i ) }
22
+ end
@@ -0,0 +1,32 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'droid'
3
+
4
+ def log
5
+ Droid.log
6
+ end
7
+
8
+ if ARGV[0] == 'listen'
9
+ Droid.new('Example Worker') do |droid|
10
+ droid.worker('example.bunny.worker').subscribe do |req|
11
+ req.ack
12
+ log.info "Work done, replying..."
13
+ req.reply(:t => Time.now.to_i)
14
+ end
15
+
16
+ droid.listener('example.bunny.listener').subscribe do |req|
17
+ log.info "I heard #{req['t']}"
18
+ end
19
+ end
20
+ exit(0)
21
+ end
22
+
23
+
24
+ require 'droid/sync'
25
+
26
+ log.info "publishing to example.bunny.listener.."
27
+ Droid.publish('example.bunny.listener', :t => Time.now.to_i)
28
+
29
+ log.info "publishing to example.bunner.worker, expecting a result..."
30
+ res = Droid.call('example.bunny.worker', :t => Time.now.to_i)
31
+ log.info "received #{res.inspect}"
32
+
@@ -0,0 +1,58 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'droid'
3
+
4
+ def log
5
+ Droid.log
6
+ end
7
+
8
+ @gum_chews = 0
9
+ @toffee_chews = 0
10
+
11
+ Droid.new('Example Worker') do |droid|
12
+
13
+ # auto acks the message
14
+ @gum = droid.worker('example.chew.gum').subscribe do |req|
15
+ log.info "flavor: #{req['flavor']}, packs: #{req['packs']}"
16
+ log.info req.msg.inspect
17
+ log.info req.droid_headers
18
+
19
+ @gum_chews += 1
20
+ end
21
+
22
+ # explicit ack
23
+ @toffee = droid.worker('example.chew.toffee').subscribe(:auto_ack => false) do |req|
24
+ log.info "flavor: #{req['flavor']}"
25
+ log.info req.msg.inspect
26
+ log.info req.droid_headers
27
+
28
+ req.ack
29
+
30
+ @toffee_chews += 1
31
+ end
32
+
33
+ droid.periodic_timer(2) do
34
+ log.debug "checking gum & toffee chews"
35
+
36
+ if @gum_chews == 3 && @gum
37
+ @gum.destroy
38
+ @gum = nil
39
+ end
40
+
41
+ if @toffee_chews == 2 && @toffee
42
+ @toffee.destroy
43
+ @toffee = nil
44
+ end
45
+
46
+ if @toffee.nil? && @gum.nil?
47
+ Droid.stop_safe
48
+ end
49
+ end
50
+
51
+ droid.timer(2) do
52
+ droid.publish('example.chew.gum', :flavor => 'spearmint', :packs => 2)
53
+ droid.publish('example.chew.gum', :flavor => 'bubblegum', :packs => 3)
54
+ droid.publish('example.chew.gum', :flavor => 'peppermint', :packs => 1)
55
+ droid.publish('example.chew.toffee', :flavor => 'caramel')
56
+ droid.publish('example.chew.toffee', :flavor => 'licorish')
57
+ end
58
+ end
@@ -0,0 +1,140 @@
1
+ require 'uri'
2
+ require 'amqp'
3
+ require 'mq'
4
+
5
+ if !defined?(JSON) && !defined?(JSON_LOADED)
6
+ require 'json/pure'
7
+ end
8
+
9
+ require 'droid/monkey'
10
+
11
+ require 'droid/utils'
12
+ require 'droid/publish'
13
+ require 'droid/request'
14
+ require 'droid/utilization'
15
+ require 'droid/queue'
16
+ require 'droid/em'
17
+
18
+ class Droid
19
+ def self.version
20
+ @@version ||= File.read(File.dirname(__FILE__) + '/../VERSION').strip
21
+ end
22
+
23
+ def self.name
24
+ @@name
25
+ end
26
+
27
+ def self.name=(name)
28
+ @@name = name
29
+ end
30
+
31
+ def self.log=(log)
32
+ @@log = log
33
+ end
34
+
35
+ def self.log
36
+ @@log ||= begin
37
+ require 'logger'
38
+ Logger.class_eval <<EORUBY
39
+ alias_method :notice, :info
40
+
41
+ alias_method :error_og, :error
42
+ def error(err, opts={})
43
+ e = opts[:exception]
44
+ if e.respond_to?(:backtrace)
45
+ err += "\n" + e.backtrace.join("\n ")
46
+ end
47
+ error_og(err)
48
+ end
49
+ EORUBY
50
+ Logger.new($stderr)
51
+ end
52
+ end
53
+
54
+ def self.default_config
55
+ uri = URI.parse(ENV["AMQP_URL"] || 'amqp://guest:guest@localhost:5672/')
56
+ {
57
+ :vhost => uri.path,
58
+ :host => uri.host,
59
+ :user => uri.user,
60
+ :port => uri.port || 5672,
61
+ :pass => uri.password
62
+ }
63
+ rescue Object => e
64
+ raise "invalid AMQP_URL: (#{uri.inspect}) #{e.class} -> #{e.message}"
65
+ end
66
+
67
+ def self.start(opts={})
68
+ config = opts[:config] || self.default_config
69
+
70
+ wait_for_tcp_port(config[:host], config[:port])
71
+
72
+ begin
73
+ ::Signal.trap('INT') { ::AMQP.stop{ ::EM.stop } }
74
+ ::Signal.trap('TERM'){ ::AMQP.stop{ ::EM.stop } }
75
+
76
+ ::AMQP.start(config) do
77
+ yield if block_given?
78
+ end
79
+ rescue ::AMQP::Error => e
80
+ log.debug "Caught #{e.class}, sleeping to avoid inittab thrashing"
81
+ sleep 5
82
+ log.debug "Done."
83
+ raise
84
+ end
85
+ end
86
+
87
+ def self.stop_safe
88
+ ::EM.add_timer(0.2) { ::AMQP.stop { ::EM.stop } }
89
+ end
90
+
91
+ def self.closing?
92
+ ::AMQP.closing?
93
+ end
94
+
95
+ def self.handle_error(err)
96
+ log.error "#{err.class}: #{err.message}", :exception => err
97
+ end
98
+
99
+ def self.wait_for_tcp_port(host, port, opts={})
100
+ require 'socket'
101
+
102
+ opts[:retries] ||= 6
103
+ opts[:timeout] ||= 5
104
+
105
+ opts[:retries].times do
106
+ begin
107
+ timeout(opts[:timeout]) do
108
+ TCPSocket.new(host.to_s, port).close
109
+ end
110
+ return
111
+ rescue Object => e
112
+ log.info "#{host}:#{port} not available, waiting... #{e.class}: #{e.message}"
113
+ sleep 1
114
+ end
115
+ end
116
+
117
+ raise "#{host}:#{port} did not come up after #{opts[:retries]} retries"
118
+ end
119
+
120
+ def initialize(name, opts={})
121
+ log.info "=== #{name} droid initializing"
122
+
123
+ self.class.name = name
124
+ self.class.start do
125
+ yield self if block_given?
126
+ end
127
+ end
128
+
129
+ def publish(*args)
130
+ Droid.publish(*args)
131
+ end
132
+
133
+ def log
134
+ self.class.log
135
+ end
136
+
137
+ include Droid::QueueMethods
138
+ include Droid::BackwardsCompatibleMethods
139
+ include Droid::EMTimerUtils
140
+ end
@@ -0,0 +1,55 @@
1
+ class Droid
2
+ module EMTimerUtils
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ # Trap exceptions leaving the block and log them. Do not re-raise
9
+ def trap_exceptions
10
+ yield
11
+ rescue => e
12
+ em_exception(e)
13
+ end
14
+
15
+ def em_exception(e)
16
+ msg = format_em_exception(e)
17
+ log.error "[EM.timer] #{msg}", :exception => e
18
+ end
19
+
20
+ def format_em_exception(e)
21
+ # avoid backtrace in /usr or vendor if possible
22
+ system, app = e.backtrace.partition { |b| b =~ /(^\/usr\/|vendor)/ }
23
+ reordered_backtrace = app + system
24
+
25
+ # avoid "/" as the method name (we want the controller action)
26
+ row = 0
27
+ row = 1 if reordered_backtrace[row].match(/in `\/'$/)
28
+
29
+ # get file and method name
30
+ begin
31
+ file, method = reordered_backtrace[row].match(/(.*):in `(.*)'$/)[1..2]
32
+ file.gsub!(/.*\//, '')
33
+ "#{e.class} in #{file} #{method}: #{e.message}"
34
+ rescue
35
+ "#{e.class} in #{e.backtrace.first}: #{e.message}"
36
+ end
37
+ end
38
+
39
+ # One-shot timer
40
+ def timer(duration, &blk)
41
+ EM.add_timer(duration) { trap_exceptions(&blk) }
42
+ end
43
+
44
+ # Add a periodic timer. If the now argument is true, run the block
45
+ # immediately in addition to scheduling the periodic timer.
46
+ def periodic_timer(duration, now=false, &blk)
47
+ timer(1, &blk) if now
48
+ EM.add_periodic_timer(duration) { trap_exceptions(&blk) }
49
+ end
50
+ end
51
+
52
+ def timer(*args, &blk); self.class.timer(*args, &blk); end
53
+ def periodic_timer(*args, &blk); self.class.periodic_timer(*args, &blk); end
54
+ end
55
+ end