droid19 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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