creeper 1.0.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. data/.gitignore +1 -0
  2. data/.rvmrc +48 -0
  3. data/Gemfile +17 -1
  4. data/Guardfile +32 -0
  5. data/Rakefile +9 -1
  6. data/bin/creeper +10 -58
  7. data/bin/creeperctl +74 -0
  8. data/config.ru +18 -0
  9. data/creeper.gemspec +19 -9
  10. data/lib/creeper.rb +108 -413
  11. data/lib/creeper/beanstalk_connection.rb +35 -0
  12. data/lib/creeper/cli.rb +225 -0
  13. data/lib/creeper/client.rb +93 -0
  14. data/lib/creeper/core_ext.rb +54 -0
  15. data/lib/creeper/exception_handler.rb +30 -0
  16. data/lib/creeper/extensions/action_mailer.rb +33 -0
  17. data/lib/creeper/extensions/active_record.rb +30 -0
  18. data/lib/creeper/extensions/generic_proxy.rb +26 -0
  19. data/lib/creeper/fetch.rb +94 -0
  20. data/lib/creeper/legacy.rb +46 -0
  21. data/lib/creeper/logging.rb +46 -0
  22. data/lib/creeper/manager.rb +164 -0
  23. data/lib/creeper/middleware/chain.rb +100 -0
  24. data/lib/creeper/middleware/server/active_record.rb +13 -0
  25. data/lib/creeper/middleware/server/logging.rb +31 -0
  26. data/lib/creeper/middleware/server/retry_jobs.rb +79 -0
  27. data/lib/creeper/middleware/server/timeout.rb +21 -0
  28. data/lib/creeper/paginator.rb +31 -0
  29. data/lib/creeper/processor.rb +116 -0
  30. data/lib/creeper/rails.rb +21 -0
  31. data/lib/creeper/redis_connection.rb +28 -0
  32. data/lib/creeper/testing.rb +44 -0
  33. data/lib/creeper/util.rb +45 -0
  34. data/lib/creeper/version.rb +1 -1
  35. data/lib/creeper/web.rb +248 -0
  36. data/lib/creeper/worker.rb +62 -313
  37. data/spec/dummy/.gitignore +15 -0
  38. data/spec/dummy/Gemfile +51 -0
  39. data/spec/dummy/README.rdoc +261 -0
  40. data/spec/dummy/Rakefile +7 -0
  41. data/spec/dummy/app/assets/images/rails.png +0 -0
  42. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  43. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  44. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  45. data/spec/dummy/app/controllers/work_controller.rb +71 -0
  46. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  47. data/spec/dummy/app/mailers/.gitkeep +0 -0
  48. data/spec/dummy/app/mailers/user_mailer.rb +9 -0
  49. data/spec/dummy/app/models/.gitkeep +0 -0
  50. data/spec/dummy/app/models/post.rb +8 -0
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/spec/dummy/app/views/user_mailer/greetings.html.erb +3 -0
  53. data/spec/dummy/app/views/work/index.html.erb +1 -0
  54. data/spec/dummy/app/workers/fast_worker.rb +10 -0
  55. data/spec/dummy/app/workers/hard_worker.rb +11 -0
  56. data/spec/dummy/app/workers/lazy_worker.rb +12 -0
  57. data/spec/dummy/app/workers/suicidal_worker.rb +33 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +68 -0
  60. data/spec/dummy/config/boot.rb +6 -0
  61. data/spec/dummy/config/creeper.yml +9 -0
  62. data/spec/dummy/config/database.yml +25 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +37 -0
  65. data/spec/dummy/config/environments/production.rb +67 -0
  66. data/spec/dummy/config/environments/test.rb +37 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/creeper.rb +8 -0
  69. data/spec/dummy/config/initializers/inflections.rb +15 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  71. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  72. data/spec/dummy/config/initializers/session_store.rb +8 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +5 -0
  75. data/spec/dummy/config/routes.rb +13 -0
  76. data/spec/dummy/db/migrate/20120123214055_create_posts.rb +10 -0
  77. data/spec/dummy/db/schema.rb +23 -0
  78. data/spec/dummy/db/seeds.rb +7 -0
  79. data/spec/dummy/lib/assets/.gitkeep +0 -0
  80. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  81. data/spec/dummy/log/.gitkeep +0 -0
  82. data/spec/dummy/public/404.html +26 -0
  83. data/spec/dummy/public/422.html +26 -0
  84. data/spec/dummy/public/500.html +25 -0
  85. data/spec/dummy/public/favicon.ico +0 -0
  86. data/spec/dummy/public/index.html +241 -0
  87. data/spec/dummy/public/robots.txt +5 -0
  88. data/spec/dummy/script/rails +6 -0
  89. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  90. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  91. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  92. data/spec/lib/creeper/cli_spec.rb +208 -0
  93. data/spec/lib/creeper/client_spec.rb +110 -0
  94. data/spec/lib/creeper/exception_handler_spec.rb +110 -0
  95. data/spec/lib/creeper/processor_spec.rb +92 -0
  96. data/spec/lib/creeper/testing_spec.rb +105 -0
  97. data/spec/lib/creeper_spec.rb +54 -120
  98. data/spec/spec_helper.rb +81 -7
  99. data/spec/support/config.yml +9 -0
  100. data/spec/support/fake_env.rb +0 -0
  101. data/spec/support/workers/base_worker.rb +11 -0
  102. data/spec/support/workers/my_worker.rb +4 -0
  103. data/spec/support/workers/queued_worker.rb +5 -0
  104. data/spec/support/workers/real_worker.rb +10 -0
  105. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  106. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  107. data/web/assets/javascripts/application.js +49 -0
  108. data/web/assets/javascripts/vendor/bootstrap.js +12 -0
  109. data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
  110. data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
  111. data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
  112. data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
  113. data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
  114. data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
  115. data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
  116. data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
  117. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
  118. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
  119. data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
  120. data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
  121. data/web/assets/javascripts/vendor/jquery.js +9266 -0
  122. data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
  123. data/web/assets/stylesheets/application.css +6 -0
  124. data/web/assets/stylesheets/layout.css +26 -0
  125. data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
  126. data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
  127. data/web/views/_paging.slim +15 -0
  128. data/web/views/_summary.slim +9 -0
  129. data/web/views/_workers.slim +14 -0
  130. data/web/views/index.slim +10 -0
  131. data/web/views/layout.slim +37 -0
  132. data/web/views/poll.slim +3 -0
  133. data/web/views/queue.slim +15 -0
  134. data/web/views/queues.slim +19 -0
  135. data/web/views/retries.slim +31 -0
  136. data/web/views/retry.slim +52 -0
  137. data/web/views/scheduled.slim +27 -0
  138. metadata +341 -23
  139. data/lib/creeper/celluloid_ext.rb +0 -42
  140. data/lib/creeper/creep.rb +0 -25
  141. data/lib/creeper/err_logger.rb +0 -37
  142. data/lib/creeper/launcher.rb +0 -44
  143. data/lib/creeper/out_logger.rb +0 -39
  144. data/spec/lib/creeper/session_spec.rb +0 -15
  145. 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
@@ -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