creeper 1.0.9 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rvmrc +48 -0
- data/Gemfile +17 -1
- data/Guardfile +32 -0
- data/Rakefile +9 -1
- data/bin/creeper +10 -58
- data/bin/creeperctl +74 -0
- data/config.ru +18 -0
- data/creeper.gemspec +19 -9
- data/lib/creeper.rb +108 -413
- data/lib/creeper/beanstalk_connection.rb +35 -0
- data/lib/creeper/cli.rb +225 -0
- data/lib/creeper/client.rb +93 -0
- data/lib/creeper/core_ext.rb +54 -0
- data/lib/creeper/exception_handler.rb +30 -0
- data/lib/creeper/extensions/action_mailer.rb +33 -0
- data/lib/creeper/extensions/active_record.rb +30 -0
- data/lib/creeper/extensions/generic_proxy.rb +26 -0
- data/lib/creeper/fetch.rb +94 -0
- data/lib/creeper/legacy.rb +46 -0
- data/lib/creeper/logging.rb +46 -0
- data/lib/creeper/manager.rb +164 -0
- data/lib/creeper/middleware/chain.rb +100 -0
- data/lib/creeper/middleware/server/active_record.rb +13 -0
- data/lib/creeper/middleware/server/logging.rb +31 -0
- data/lib/creeper/middleware/server/retry_jobs.rb +79 -0
- data/lib/creeper/middleware/server/timeout.rb +21 -0
- data/lib/creeper/paginator.rb +31 -0
- data/lib/creeper/processor.rb +116 -0
- data/lib/creeper/rails.rb +21 -0
- data/lib/creeper/redis_connection.rb +28 -0
- data/lib/creeper/testing.rb +44 -0
- data/lib/creeper/util.rb +45 -0
- data/lib/creeper/version.rb +1 -1
- data/lib/creeper/web.rb +248 -0
- data/lib/creeper/worker.rb +62 -313
- data/spec/dummy/.gitignore +15 -0
- data/spec/dummy/Gemfile +51 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/images/rails.png +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/work_controller.rb +71 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/mailers/user_mailer.rb +9 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/post.rb +8 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/user_mailer/greetings.html.erb +3 -0
- data/spec/dummy/app/views/work/index.html.erb +1 -0
- data/spec/dummy/app/workers/fast_worker.rb +10 -0
- data/spec/dummy/app/workers/hard_worker.rb +11 -0
- data/spec/dummy/app/workers/lazy_worker.rb +12 -0
- data/spec/dummy/app/workers/suicidal_worker.rb +33 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +68 -0
- data/spec/dummy/config/boot.rb +6 -0
- data/spec/dummy/config/creeper.yml +9 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/creeper.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +13 -0
- data/spec/dummy/db/migrate/20120123214055_create_posts.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/tasks/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/index.html +241 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/dummy/vendor/plugins/.gitkeep +0 -0
- data/spec/lib/creeper/cli_spec.rb +208 -0
- data/spec/lib/creeper/client_spec.rb +110 -0
- data/spec/lib/creeper/exception_handler_spec.rb +110 -0
- data/spec/lib/creeper/processor_spec.rb +92 -0
- data/spec/lib/creeper/testing_spec.rb +105 -0
- data/spec/lib/creeper_spec.rb +54 -120
- data/spec/spec_helper.rb +81 -7
- data/spec/support/config.yml +9 -0
- data/spec/support/fake_env.rb +0 -0
- data/spec/support/workers/base_worker.rb +11 -0
- data/spec/support/workers/my_worker.rb +4 -0
- data/spec/support/workers/queued_worker.rb +5 -0
- data/spec/support/workers/real_worker.rb +10 -0
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/javascripts/application.js +49 -0
- data/web/assets/javascripts/vendor/bootstrap.js +12 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
- data/web/assets/javascripts/vendor/jquery.js +9266 -0
- data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
- data/web/assets/stylesheets/application.css +6 -0
- data/web/assets/stylesheets/layout.css +26 -0
- data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
- data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
- data/web/views/_paging.slim +15 -0
- data/web/views/_summary.slim +9 -0
- data/web/views/_workers.slim +14 -0
- data/web/views/index.slim +10 -0
- data/web/views/layout.slim +37 -0
- data/web/views/poll.slim +3 -0
- data/web/views/queue.slim +15 -0
- data/web/views/queues.slim +19 -0
- data/web/views/retries.slim +31 -0
- data/web/views/retry.slim +52 -0
- data/web/views/scheduled.slim +27 -0
- metadata +341 -23
- data/lib/creeper/celluloid_ext.rb +0 -42
- data/lib/creeper/creep.rb +0 -25
- data/lib/creeper/err_logger.rb +0 -37
- data/lib/creeper/launcher.rb +0 -44
- data/lib/creeper/out_logger.rb +0 -39
- data/spec/lib/creeper/session_spec.rb +0 -15
- data/spec/lib/creeper/worker_spec.rb +0 -21
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'beanstalk-client'
|
2
|
+
|
3
|
+
module Creeper
|
4
|
+
class BeanstalkConnection
|
5
|
+
def self.create(options={})
|
6
|
+
client_options.merge!(options)
|
7
|
+
|
8
|
+
url = client_options[:url] || ENV['BEANSTALK_URL'] || 'beanstalk://127.0.0.1:11300/'
|
9
|
+
default = client_options[:default]
|
10
|
+
tubes = client_options[:tubes] || Creeper.job_descriptions.keys
|
11
|
+
|
12
|
+
build_client(url, default, tubes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.build_client(urls, default, tubes)
|
16
|
+
uris = [*urls].flatten.map do |url_string|
|
17
|
+
url_string.split(/[\s,]+/).map do |url|
|
18
|
+
uri = URI.parse(url)
|
19
|
+
"#{uri.host}:#{uri.port || 11300}"
|
20
|
+
end
|
21
|
+
end.flatten
|
22
|
+
|
23
|
+
Beanstalk::Pool.new(uris, default).tap do |client|
|
24
|
+
tubes.each do |tube|
|
25
|
+
client.watch(tube)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
private_class_method :build_client
|
30
|
+
|
31
|
+
def self.client_options
|
32
|
+
@client_options ||= {}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/creeper/cli.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
trap 'INT' do
|
2
|
+
# Handle Ctrl-C in JRuby like MRI
|
3
|
+
# http://jira.codehaus.org/browse/JRUBY-4637
|
4
|
+
Creeper::CLI.instance.interrupt
|
5
|
+
end
|
6
|
+
|
7
|
+
trap 'TERM' do
|
8
|
+
# Heroku sends TERM and then waits 10 seconds for process to exit.
|
9
|
+
Creeper::CLI.instance.interrupt
|
10
|
+
end
|
11
|
+
|
12
|
+
trap 'QUIT' do
|
13
|
+
Creeper.logger.info "Received QUIT, no longer accepting new work"
|
14
|
+
mgr = Creeper::CLI.instance.manager
|
15
|
+
if mgr
|
16
|
+
mgr.stop!(shutdown: true, timeout: Creeper.options[:timeout])
|
17
|
+
mgr.wait(:shutdown)
|
18
|
+
# Explicitly exit so busy Processor threads can't block
|
19
|
+
# process shutdown.
|
20
|
+
exit(0)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
trap 'USR1' do
|
25
|
+
Creeper.logger.info "Received USR1, no longer accepting new work"
|
26
|
+
mgr = Creeper::CLI.instance.manager
|
27
|
+
mgr.stop! if mgr
|
28
|
+
end
|
29
|
+
|
30
|
+
trap 'TTIN' do
|
31
|
+
Thread.list.each do |thread|
|
32
|
+
Creeper.logger.info "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
33
|
+
Creeper.logger.info thread.backtrace.join("\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
$stdout.sync = true
|
38
|
+
|
39
|
+
require 'yaml'
|
40
|
+
require 'singleton'
|
41
|
+
require 'optparse'
|
42
|
+
require 'celluloid'
|
43
|
+
|
44
|
+
require 'creeper'
|
45
|
+
require 'creeper/util'
|
46
|
+
require 'creeper/manager'
|
47
|
+
# require 'creeper/scheduled'
|
48
|
+
|
49
|
+
module Creeper
|
50
|
+
class CLI
|
51
|
+
include Util
|
52
|
+
include Singleton
|
53
|
+
|
54
|
+
# Used for CLI testing
|
55
|
+
attr_accessor :code
|
56
|
+
attr_accessor :manager
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@code = nil
|
60
|
+
@interrupt_mutex = Mutex.new
|
61
|
+
@interrupted = false
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(args=ARGV)
|
65
|
+
@code = nil
|
66
|
+
Creeper.logger
|
67
|
+
|
68
|
+
cli = parse_options(args)
|
69
|
+
config = parse_config(cli)
|
70
|
+
options.merge!(config.merge(cli))
|
71
|
+
|
72
|
+
Creeper.logger.level = Logger::DEBUG if options[:verbose]
|
73
|
+
Celluloid.logger = nil unless options[:verbose]
|
74
|
+
|
75
|
+
validate!
|
76
|
+
write_pid
|
77
|
+
boot_system
|
78
|
+
end
|
79
|
+
|
80
|
+
def run
|
81
|
+
@manager = Creeper::Manager.new(options)
|
82
|
+
begin
|
83
|
+
logger.info 'Starting processing, hit Ctrl-C to stop'
|
84
|
+
@manager.start!
|
85
|
+
sleep
|
86
|
+
rescue Interrupt
|
87
|
+
logger.info 'Shutting down'
|
88
|
+
@manager.stop!(:shutdown => true, :timeout => options[:timeout])
|
89
|
+
@manager.wait(:shutdown)
|
90
|
+
# Explicitly exit so busy Processor threads can't block
|
91
|
+
# process shutdown.
|
92
|
+
exit(0)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def interrupt
|
97
|
+
@interrupt_mutex.synchronize do
|
98
|
+
unless @interrupted
|
99
|
+
@interrupted = true
|
100
|
+
Thread.main.raise Interrupt
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def die(code)
|
108
|
+
exit(code)
|
109
|
+
end
|
110
|
+
|
111
|
+
def options
|
112
|
+
Creeper.options
|
113
|
+
end
|
114
|
+
|
115
|
+
def detected_environment
|
116
|
+
options[:environment] ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
117
|
+
end
|
118
|
+
|
119
|
+
def boot_system
|
120
|
+
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = detected_environment
|
121
|
+
|
122
|
+
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
123
|
+
|
124
|
+
if File.directory?(options[:require])
|
125
|
+
require 'rails'
|
126
|
+
require 'creeper/rails'
|
127
|
+
require File.expand_path("#{options[:require]}/config/environment.rb")
|
128
|
+
::Rails.application.eager_load!
|
129
|
+
else
|
130
|
+
require options[:require]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def validate!
|
135
|
+
options[:queues] << 'default' if options[:queues].empty?
|
136
|
+
|
137
|
+
if !File.exist?(options[:require]) ||
|
138
|
+
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
139
|
+
logger.info "=================================================================="
|
140
|
+
logger.info " Please point creeper to a Rails 3 application or a Ruby file "
|
141
|
+
logger.info " to load your worker classes with -r [DIR|FILE]."
|
142
|
+
logger.info "=================================================================="
|
143
|
+
logger.info @parser
|
144
|
+
die(1)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_options(argv)
|
149
|
+
opts = {}
|
150
|
+
|
151
|
+
@parser = OptionParser.new do |o|
|
152
|
+
o.on "-q", "--queue QUEUE[,WEIGHT]...", "Queues to process with optional weights" do |arg|
|
153
|
+
queues_and_weights = arg.scan(/([\w-]+),?(\d*)/)
|
154
|
+
queues_and_weights.each {|queue_and_weight| parse_queues(opts, *queue_and_weight)}
|
155
|
+
opts[:strict] = queues_and_weights.collect(&:last).none? {|weight| weight != ''}
|
156
|
+
end
|
157
|
+
|
158
|
+
o.on "-v", "--verbose", "Print more verbose output" do
|
159
|
+
Creeper.logger.level = ::Logger::DEBUG
|
160
|
+
end
|
161
|
+
|
162
|
+
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
163
|
+
opts[:environment] = arg
|
164
|
+
end
|
165
|
+
|
166
|
+
o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg|
|
167
|
+
opts[:timeout] = arg.to_i
|
168
|
+
end
|
169
|
+
|
170
|
+
o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
|
171
|
+
opts[:require] = arg
|
172
|
+
end
|
173
|
+
|
174
|
+
o.on '-c', '--concurrency INT', "processor threads to use" do |arg|
|
175
|
+
opts[:concurrency] = arg.to_i
|
176
|
+
end
|
177
|
+
|
178
|
+
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
179
|
+
opts[:pidfile] = arg
|
180
|
+
end
|
181
|
+
|
182
|
+
o.on '-C', '--config PATH', "path to YAML config file" do |arg|
|
183
|
+
opts[:config_file] = arg
|
184
|
+
end
|
185
|
+
|
186
|
+
o.on '-V', '--version', "Print version and exit" do |arg|
|
187
|
+
puts "Creeper #{Creeper::VERSION}"
|
188
|
+
die(0)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
@parser.banner = "creeper [options]"
|
193
|
+
@parser.on_tail "-h", "--help", "Show help" do
|
194
|
+
logger.info @parser
|
195
|
+
die 1
|
196
|
+
end
|
197
|
+
@parser.parse!(argv)
|
198
|
+
opts
|
199
|
+
end
|
200
|
+
|
201
|
+
def write_pid
|
202
|
+
if path = options[:pidfile]
|
203
|
+
File.open(path, 'w') do |f|
|
204
|
+
f.puts Process.pid
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_config(cli)
|
210
|
+
opts = {}
|
211
|
+
if cli[:config_file] && File.exist?(cli[:config_file])
|
212
|
+
opts = YAML.load_file cli[:config_file]
|
213
|
+
queues = opts.delete(:queues) || []
|
214
|
+
queues.each { |name, weight| parse_queues(opts, name, weight) }
|
215
|
+
end
|
216
|
+
opts
|
217
|
+
end
|
218
|
+
|
219
|
+
def parse_queues(opts, q, weight)
|
220
|
+
[weight.to_i, 1].max.times do
|
221
|
+
(opts[:queues] ||= []) << q
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'creeper/legacy'
|
2
|
+
require 'creeper/middleware/chain'
|
3
|
+
|
4
|
+
module Creeper
|
5
|
+
class Client
|
6
|
+
|
7
|
+
def self.default_middleware
|
8
|
+
Middleware::Chain.new do |m|
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.registered_workers
|
13
|
+
Creeper.redis { |x| x.smembers('workers') }
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.registered_queues
|
17
|
+
Creeper.redis { |x| x.smembers('queues') }
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# The main method used to push a job to Redis. Accepts a number of options:
|
22
|
+
#
|
23
|
+
# queue - the named queue to use, default 'default'
|
24
|
+
# class - the worker class to call, required
|
25
|
+
# args - an array of simple arguments to the perform method, must be JSON-serializable
|
26
|
+
# retry - whether to retry this job if it fails, true or false, default true
|
27
|
+
# backtrace - whether to save any error backtrace, default false
|
28
|
+
#
|
29
|
+
# All options must be strings, not symbols. NB: because we are serializing to JSON, all
|
30
|
+
# symbols in 'args' will be converted to strings.
|
31
|
+
#
|
32
|
+
# Returns nil if not pushed to Redis or a unique Job ID if pushed.
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
# Creeper::Client.push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar'])
|
36
|
+
#
|
37
|
+
def self.push(item)
|
38
|
+
raise(ArgumentError, "Message must be a Hash of the form: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash)
|
39
|
+
raise(ArgumentError, "Message must include a class and set of arguments: #{item.inspect}") if !item['class'] || !item['args']
|
40
|
+
raise(ArgumentError, "Message must include a Creeper::Worker class, not class name: #{item['class'].ancestors.inspect}") if !item['class'].is_a?(Class) || !item['class'].respond_to?('get_creeper_options')
|
41
|
+
|
42
|
+
worker_class = item['class']
|
43
|
+
item['class'] = item['class'].to_s
|
44
|
+
|
45
|
+
item = worker_class.get_creeper_options.merge(item)
|
46
|
+
# item['retry'] = !!item['retry']
|
47
|
+
at = item['at']
|
48
|
+
queue = item['queue']
|
49
|
+
priority = item['priority']
|
50
|
+
delay = item['delay']
|
51
|
+
delay ||= at.to_i - Time.now.to_i if at and at.respond_to?(:to_i)
|
52
|
+
time_to_run = item['time_to_run']
|
53
|
+
# item['jid'] = SecureRandom.base64
|
54
|
+
|
55
|
+
pushed = false
|
56
|
+
job = Creeper.client_middleware.invoke(worker_class, item, queue) do
|
57
|
+
payload = Creeper.dump_json([ queue, item ])
|
58
|
+
args = [ payload, priority, delay, time_to_run ]
|
59
|
+
args.pop while args.last.nil?
|
60
|
+
Creeper.redis do |conn|
|
61
|
+
conn.sadd('queues', queue)
|
62
|
+
end
|
63
|
+
Creeper.beanstalk do |beanstalk|
|
64
|
+
beanstalk.on_tube(queue) do |conn|
|
65
|
+
conn.put(*args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
# Creeper.redis do |conn|
|
69
|
+
# if item['at']
|
70
|
+
# pushed = conn.zadd('schedule', item['at'].to_s, payload)
|
71
|
+
# else
|
72
|
+
# _, pushed = conn.multi do
|
73
|
+
# conn.sadd('queues', queue)
|
74
|
+
# conn.rpush("queue:#{queue}", payload)
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
end
|
79
|
+
pushed = !!job
|
80
|
+
pushed ? job : nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Redis compatibility helper. Example usage:
|
84
|
+
#
|
85
|
+
# Creeper::Client.enqueue(MyWorker, 'foo', 1, :bat => 'bar')
|
86
|
+
#
|
87
|
+
# Messages are enqueued to the 'default' queue.
|
88
|
+
#
|
89
|
+
def self.enqueue(klass, *args)
|
90
|
+
klass.perform_async(*args)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
rescue LoadError
|
4
|
+
|
5
|
+
# A dumbed down version of ActiveSupport's
|
6
|
+
# Class#class_attribute helper.
|
7
|
+
class Class
|
8
|
+
def class_attribute(*attrs)
|
9
|
+
instance_reader = true
|
10
|
+
instance_writer = true
|
11
|
+
|
12
|
+
attrs.each do |name|
|
13
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
14
|
+
def self.#{name}() nil end
|
15
|
+
def self.#{name}?() !!#{name} end
|
16
|
+
|
17
|
+
def self.#{name}=(val)
|
18
|
+
singleton_class.class_eval do
|
19
|
+
define_method(:#{name}) { val }
|
20
|
+
end
|
21
|
+
|
22
|
+
if singleton_class?
|
23
|
+
class_eval do
|
24
|
+
def #{name}
|
25
|
+
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
val
|
30
|
+
end
|
31
|
+
|
32
|
+
if instance_reader
|
33
|
+
def #{name}
|
34
|
+
defined?(@#{name}) ? @#{name} : self.class.#{name}
|
35
|
+
end
|
36
|
+
|
37
|
+
def #{name}?
|
38
|
+
!!#{name}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
RUBY
|
42
|
+
|
43
|
+
attr_writer name if instance_writer
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def singleton_class?
|
49
|
+
ancestors.first != self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Creeper
|
2
|
+
module ExceptionHandler
|
3
|
+
|
4
|
+
def handle_exception(ex, msg)
|
5
|
+
Creeper.logger.warn msg
|
6
|
+
Creeper.logger.warn ex
|
7
|
+
Creeper.logger.warn ex.backtrace.join("\n")
|
8
|
+
send_to_airbrake(msg, ex) if defined?(::Airbrake)
|
9
|
+
send_to_exceptional(msg, ex) if defined?(::Exceptional)
|
10
|
+
send_to_exception_notifier(msg, ex) if defined?(::ExceptionNotifier)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def send_to_airbrake(msg, ex)
|
16
|
+
::Airbrake.notify(ex, :parameters => msg)
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_to_exceptional(msg, ex)
|
20
|
+
if ::Exceptional::Config.should_send_to_api?
|
21
|
+
::Exceptional.context(msg)
|
22
|
+
::Exceptional::Remote.error(::Exceptional::ExceptionData.new(ex))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_to_exception_notifier(msg, ex)
|
27
|
+
::ExceptionNotifier::Notifier.background_exception_notification(ex, :data => { :message => msg }).deliver
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'creeper/extensions/generic_proxy'
|
2
|
+
|
3
|
+
module Creeper
|
4
|
+
module Extensions
|
5
|
+
##
|
6
|
+
# Adds 'delay' and 'delay_for' to ActionMailer to offload arbitrary email
|
7
|
+
# delivery to Creeper. Example:
|
8
|
+
#
|
9
|
+
# UserMailer.delay.send_welcome_email(new_user)
|
10
|
+
# UserMailer.delay_for(5.days).send_welcome_email(new_user)
|
11
|
+
class DelayedMailer
|
12
|
+
include Creeper::Worker
|
13
|
+
# I think it's reasonable to assume that emails should take less
|
14
|
+
# than 30 seconds to send.
|
15
|
+
creeper_options :timeout => 30
|
16
|
+
|
17
|
+
def perform(yml)
|
18
|
+
(target, method_name, args) = YAML.load(yml)
|
19
|
+
target.send(method_name, *args).deliver
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module ActionMailer
|
24
|
+
def delay
|
25
|
+
Proxy.new(DelayedMailer, self)
|
26
|
+
end
|
27
|
+
def delay_for(interval)
|
28
|
+
Proxy.new(DelayedMailer, self, Time.now.to_f + interval.to_f)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|