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,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