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.
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/bin/bleedq +19 -0
- data/droid.gemspec +97 -0
- data/examples/async_reply.rb +25 -0
- data/examples/heroku_async_reply.rb +22 -0
- data/examples/sync.rb +32 -0
- data/examples/worker.rb +58 -0
- data/lib/droid.rb +140 -0
- data/lib/droid/em.rb +55 -0
- data/lib/droid/heroku.rb +102 -0
- data/lib/droid/heroku/local_stats.rb +145 -0
- data/lib/droid/heroku/logger_client.rb +218 -0
- data/lib/droid/heroku/memcache_cluster.rb +152 -0
- data/lib/droid/heroku/stats.rb +14 -0
- data/lib/droid/json_server.rb +109 -0
- data/lib/droid/monkey.rb +8 -0
- data/lib/droid/publish.rb +31 -0
- data/lib/droid/queue.rb +196 -0
- data/lib/droid/request.rb +110 -0
- data/lib/droid/sync.rb +87 -0
- data/lib/droid/utilization.rb +113 -0
- data/lib/droid/utils.rb +113 -0
- data/lib/heroku_droid.rb +1 -0
- data/lib/local_stats.rb +1 -0
- data/lib/memcache_cluster.rb +1 -0
- data/lib/stats.rb +1 -0
- data/spec/publish_spec.rb +27 -0
- data/spec/response_spec.rb +47 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/utils_spec.rb +43 -0
- data/spec/wait_for_port_spec.rb +16 -0
- metadata +190 -0
data/Rakefile
ADDED
@@ -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
|
data/bin/bleedq
ADDED
@@ -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."
|
data/droid.gemspec
ADDED
@@ -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
|
data/examples/sync.rb
ADDED
@@ -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
|
+
|
data/examples/worker.rb
ADDED
@@ -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
|
data/lib/droid.rb
ADDED
@@ -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
|
data/lib/droid/em.rb
ADDED
@@ -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
|