creeper 1.0.9 → 2.0.0
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/.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
|