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,31 @@
|
|
1
|
+
module Creeper
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class Logging
|
5
|
+
|
6
|
+
def call(worker, msg, queue, job, conn)
|
7
|
+
Creeper::Logging.with_context("#{worker.class.to_s} JOB-#{job.id rescue nil} MSG-#{worker.object_id.to_s(36)}") do
|
8
|
+
begin
|
9
|
+
start = Time.now
|
10
|
+
logger.info { "start" }
|
11
|
+
yield
|
12
|
+
logger.info { "done: #{elapsed(start)} sec" }
|
13
|
+
rescue
|
14
|
+
logger.info { "fail: #{elapsed(start)} sec" }
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def elapsed(start)
|
21
|
+
(Time.now - start).to_f.round(3)
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
Creeper.logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Creeper
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
##
|
5
|
+
# Automatically retry jobs that fail in Creeper.
|
6
|
+
# Creeper's retry support assumes a typical development lifecycle:
|
7
|
+
# 0. push some code changes with a bug in it
|
8
|
+
# 1. bug causes message processing to fail, creeper's middleware captures
|
9
|
+
# the message and pushes it onto a retry queue
|
10
|
+
# 2. creeper retries messages in the retry queue multiple times with
|
11
|
+
# an exponential delay, the message continues to fail
|
12
|
+
# 3. after a few days, a developer deploys a fix. the message is
|
13
|
+
# reprocessed successfully.
|
14
|
+
# 4. if 3 never happens, creeper will eventually give up and throw the
|
15
|
+
# message away.
|
16
|
+
#
|
17
|
+
# A message looks like:
|
18
|
+
#
|
19
|
+
# { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'] }
|
20
|
+
#
|
21
|
+
# We'll add a bit more data to the message to support retries:
|
22
|
+
#
|
23
|
+
# * 'queue' - the queue to use
|
24
|
+
# * 'retry_count' - number of times we've retried so far.
|
25
|
+
# * 'error_message' - the message from the exception
|
26
|
+
# * 'error_class' - the exception class
|
27
|
+
# * 'failed_at' - the first time it failed
|
28
|
+
# * 'retried_at' - the last time it was retried
|
29
|
+
#
|
30
|
+
# We don't store the backtrace as that can add a lot of overhead
|
31
|
+
# to the message and everyone is using Airbrake, right?
|
32
|
+
class RetryJobs
|
33
|
+
include Creeper::Util
|
34
|
+
|
35
|
+
# delayed_job uses the same basic formula
|
36
|
+
MAX_COUNT = 25
|
37
|
+
DELAY = proc { |count| (count ** 4) + 15 }
|
38
|
+
|
39
|
+
def call(worker, msg, queue, job, conn)
|
40
|
+
yield
|
41
|
+
rescue => e
|
42
|
+
raise unless msg['retry']
|
43
|
+
|
44
|
+
msg['queue'] = queue
|
45
|
+
msg['error_message'] = e.message
|
46
|
+
msg['error_class'] = e.class.name
|
47
|
+
count = if msg['retry_count']
|
48
|
+
msg['retried_at'] = Time.now.utc
|
49
|
+
msg['retry_count'] += 1
|
50
|
+
else
|
51
|
+
msg['failed_at'] = Time.now.utc
|
52
|
+
msg['retry_count'] = 0
|
53
|
+
end
|
54
|
+
|
55
|
+
if msg['backtrace'] == true
|
56
|
+
msg['error_backtrace'] = e.backtrace
|
57
|
+
elsif msg['backtrace'].to_i != 0
|
58
|
+
msg['error_backtrace'] = e.backtrace[0..msg['backtrace'].to_i]
|
59
|
+
end
|
60
|
+
|
61
|
+
if count <= MAX_COUNT
|
62
|
+
delay = DELAY.call(count)
|
63
|
+
logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
64
|
+
retry_at = Time.now.to_f + delay
|
65
|
+
payload = Creeper.dump_json(msg)
|
66
|
+
Creeper.redis do |conn|
|
67
|
+
conn.zadd('retry', retry_at.to_s, payload)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# Goodbye dear message, you (re)tried your best I'm sure.
|
71
|
+
logger.debug { "Dropping message after hitting the retry maximum: #{msg}" }
|
72
|
+
end
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Creeper
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class Timeout
|
7
|
+
|
8
|
+
def call(worker, msg, queue, job, conn)
|
9
|
+
if msg['timeout'] && msg['timeout'].to_i != 0
|
10
|
+
::Timeout.timeout(msg['timeout'].to_i) do
|
11
|
+
yield
|
12
|
+
end
|
13
|
+
else
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Creeper
|
2
|
+
module Paginator
|
3
|
+
def page(key, pageidx=1, page_size=25)
|
4
|
+
current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
|
5
|
+
pageidx = current_page - 1
|
6
|
+
total_size = 0
|
7
|
+
items = []
|
8
|
+
starting = pageidx * page_size
|
9
|
+
ending = starting + page_size - 1
|
10
|
+
|
11
|
+
Creeper.redis do |conn|
|
12
|
+
type = conn.type(key)
|
13
|
+
|
14
|
+
case type
|
15
|
+
when 'zset'
|
16
|
+
total_size = conn.zcard(key)
|
17
|
+
items = conn.zrange(key, starting, ending, :with_scores => true)
|
18
|
+
when 'list'
|
19
|
+
total_size = conn.llen(key)
|
20
|
+
items = conn.lrange(key, starting, ending)
|
21
|
+
when 'none'
|
22
|
+
return [1, 0, []]
|
23
|
+
else
|
24
|
+
raise "can't page a #{type}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
[current_page, total_size, items]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'creeper/util'
|
3
|
+
|
4
|
+
require 'creeper/middleware/server/active_record'
|
5
|
+
require 'creeper/middleware/server/retry_jobs'
|
6
|
+
require 'creeper/middleware/server/logging'
|
7
|
+
require 'creeper/middleware/server/timeout'
|
8
|
+
|
9
|
+
module Creeper
|
10
|
+
##
|
11
|
+
# The Processor receives a message from the Manager and actually
|
12
|
+
# processes it. It instantiates the worker, runs the middleware
|
13
|
+
# chain and then calls Creeper::Worker#perform.
|
14
|
+
class Processor
|
15
|
+
include Util
|
16
|
+
include Celluloid
|
17
|
+
|
18
|
+
exclusive :process if ENV['CREEPER_EXCLUSIVE']
|
19
|
+
|
20
|
+
def self.default_middleware
|
21
|
+
Middleware::Chain.new do |m|
|
22
|
+
m.add Middleware::Server::Logging
|
23
|
+
m.add Middleware::Server::RetryJobs
|
24
|
+
m.add Middleware::Server::ActiveRecord
|
25
|
+
m.add Middleware::Server::Timeout
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(boss)
|
30
|
+
@boss = boss
|
31
|
+
end
|
32
|
+
|
33
|
+
def process(msgstr, queue, job, conn)
|
34
|
+
msg = Creeper.load_json(msgstr) rescue msgstr
|
35
|
+
klass = Creeper.job_descriptions[queue]
|
36
|
+
klass ||= constantize(msg['class'])
|
37
|
+
worker = klass.new
|
38
|
+
|
39
|
+
stats(worker, msg, queue) do
|
40
|
+
Creeper.server_middleware.invoke(worker, msg, queue, job, conn) do
|
41
|
+
args = msg['args']
|
42
|
+
args ||= [msg]
|
43
|
+
worker.perform(*cloned(args))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
job.delete rescue nil
|
47
|
+
@boss.processor_done!(current_actor)
|
48
|
+
rescue => ex
|
49
|
+
job.bury rescue nil
|
50
|
+
handle_exception(ex, msg || { :message => msgstr })
|
51
|
+
raise
|
52
|
+
ensure
|
53
|
+
conn.close rescue nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# See http://github.com/tarcieri/celluloid/issues/22
|
57
|
+
def inspect
|
58
|
+
"#<Processor #{to_s}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
@str ||= "#{hostname}:#{process_id}-#{Thread.current.object_id}:default"
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def stats(worker, msg, queue)
|
68
|
+
redis do |conn|
|
69
|
+
conn.multi do
|
70
|
+
conn.sadd('workers', self)
|
71
|
+
conn.setex("worker:#{self}:started", EXPIRY, Time.now.to_s)
|
72
|
+
hash = {:queue => queue, :payload => msg, :run_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z")}
|
73
|
+
conn.setex("worker:#{self}", EXPIRY, Creeper.dump_json(hash))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
dying = false
|
78
|
+
begin
|
79
|
+
yield
|
80
|
+
rescue Exception
|
81
|
+
dying = true
|
82
|
+
redis do |conn|
|
83
|
+
conn.multi do
|
84
|
+
conn.incrby("stat:failed", 1)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
raise
|
88
|
+
ensure
|
89
|
+
redis do |conn|
|
90
|
+
conn.multi do
|
91
|
+
conn.srem("workers", self)
|
92
|
+
conn.del("worker:#{self}")
|
93
|
+
conn.del("worker:#{self}:started")
|
94
|
+
conn.incrby("stat:processed", 1)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Singleton classes are not clonable.
|
101
|
+
SINGLETON_CLASSES = [ NilClass, TrueClass, FalseClass, Symbol, Fixnum, Float ].freeze
|
102
|
+
|
103
|
+
# Clone the arguments passed to the worker so that if
|
104
|
+
# the message fails, what is pushed back onto Redis hasn't
|
105
|
+
# been mutated by the worker.
|
106
|
+
def cloned(ary)
|
107
|
+
ary.map do |val|
|
108
|
+
SINGLETON_CLASSES.include?(val.class) ? val : val.clone
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def hostname
|
113
|
+
@h ||= `hostname`.strip
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Creeper
|
2
|
+
def self.hook_rails!
|
3
|
+
return unless Creeper.options[:enable_rails_extensions]
|
4
|
+
if defined?(ActiveRecord)
|
5
|
+
ActiveRecord::Base.extend(Creeper::Extensions::ActiveRecord)
|
6
|
+
ActiveRecord::Base.send(:include, Creeper::Extensions::ActiveRecord)
|
7
|
+
end
|
8
|
+
|
9
|
+
if defined?(ActionMailer)
|
10
|
+
ActionMailer::Base.extend(Creeper::Extensions::ActionMailer)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Rails < ::Rails::Engine
|
15
|
+
config.autoload_paths << File.expand_path("#{config.root}/app/workers") if File.exist?("#{config.root}/app/workers")
|
16
|
+
|
17
|
+
initializer 'creeper' do
|
18
|
+
Creeper.hook_rails!
|
19
|
+
end
|
20
|
+
end if defined?(::Rails)
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
require 'redis'
|
3
|
+
require 'redis/namespace'
|
4
|
+
|
5
|
+
module Creeper
|
6
|
+
class RedisConnection
|
7
|
+
def self.create(options={})
|
8
|
+
url = options[:url] || ENV['REDISTOGO_URL'] || 'redis://localhost:6379/0'
|
9
|
+
driver = options[:driver] || 'ruby'
|
10
|
+
# need a connection for Fetcher and Retry
|
11
|
+
size = options[:size] || (Creeper.server? ? (Creeper.options[:concurrency] + 2) : 5)
|
12
|
+
|
13
|
+
ConnectionPool.new(:timeout => 1, :size => size) do
|
14
|
+
build_client(url, options[:namespace], driver)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.build_client(url, namespace, driver)
|
19
|
+
client = Redis.connect(:url => url, :driver => driver)
|
20
|
+
if namespace
|
21
|
+
Redis::Namespace.new(namespace, :redis => client)
|
22
|
+
else
|
23
|
+
client
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private_class_method :build_client
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Creeper
|
2
|
+
module Worker
|
3
|
+
|
4
|
+
##
|
5
|
+
# The Creeper testing infrastructure overrides perform_async
|
6
|
+
# so that it does not actually touch the network. Instead it
|
7
|
+
# stores the asynchronous jobs in a per-class array so that
|
8
|
+
# their presence/absence can be asserted by your tests.
|
9
|
+
#
|
10
|
+
# This is similar to ActionMailer's :test delivery_method and its
|
11
|
+
# ActionMailer::Base.deliveries array.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'creeper/testing'
|
16
|
+
#
|
17
|
+
# assert_equal 0, HardWorker.jobs.size
|
18
|
+
# HardWorker.perform_async(:something)
|
19
|
+
# assert_equal 1, HardWorker.jobs.size
|
20
|
+
# assert_equal :something, HardWorker.jobs[0]['args'][0]
|
21
|
+
#
|
22
|
+
# assert_equal 0, Creeper::Extensions::DelayedMailer.jobs.size
|
23
|
+
# MyMailer.delayed.send_welcome_email('foo@example.com')
|
24
|
+
# assert_equal 1, Creeper::Extensions::DelayedMailer.jobs.size
|
25
|
+
#
|
26
|
+
module ClassMethods
|
27
|
+
alias_method :client_push_old, :client_push
|
28
|
+
def client_push(opts)
|
29
|
+
jobs << opts
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def jobs
|
34
|
+
@pushed ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def drain
|
38
|
+
while job = jobs.shift do
|
39
|
+
new.perform(*job['args'])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/creeper/util.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'creeper/exception_handler'
|
2
|
+
|
3
|
+
module Creeper
|
4
|
+
##
|
5
|
+
# This module is part of Creeper core and not intended for extensions.
|
6
|
+
#
|
7
|
+
module Util
|
8
|
+
include ExceptionHandler
|
9
|
+
|
10
|
+
EXPIRY = 60 * 60
|
11
|
+
|
12
|
+
def constantize(camel_cased_word)
|
13
|
+
names = camel_cased_word.split('::')
|
14
|
+
names.shift if names.empty? || names.first.empty?
|
15
|
+
|
16
|
+
constant = Object
|
17
|
+
names.each do |name|
|
18
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
19
|
+
end
|
20
|
+
constant
|
21
|
+
end
|
22
|
+
|
23
|
+
def watchdog(last_words)
|
24
|
+
yield
|
25
|
+
rescue => ex
|
26
|
+
handle_exception(ex, { :context => last_words })
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
Creeper.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def beanstalk(&block)
|
34
|
+
Creeper.beanstalk(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def redis(&block)
|
38
|
+
Creeper.redis(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_id
|
42
|
+
Process.pid
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/creeper/version.rb
CHANGED