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,30 @@
|
|
1
|
+
require 'creeper/extensions/generic_proxy'
|
2
|
+
|
3
|
+
module Creeper
|
4
|
+
module Extensions
|
5
|
+
##
|
6
|
+
# Adds a 'delay' method to ActiveRecord to offload arbitrary method
|
7
|
+
# execution to Creeper. Examples:
|
8
|
+
#
|
9
|
+
# User.delay.delete_inactive
|
10
|
+
# User.recent_signups.each { |user| user.delay.mark_as_awesome }
|
11
|
+
class DelayedModel
|
12
|
+
include Creeper::Worker
|
13
|
+
|
14
|
+
def perform(yml)
|
15
|
+
(target, method_name, args) = YAML.load(yml)
|
16
|
+
target.send(method_name, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActiveRecord
|
21
|
+
def delay
|
22
|
+
Proxy.new(DelayedModel, self)
|
23
|
+
end
|
24
|
+
def delay_for(interval)
|
25
|
+
Proxy.new(DelayedModel, self, Time.now.to_f + interval.to_f)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Creeper
|
2
|
+
module Extensions
|
3
|
+
class Proxy < BasicObject
|
4
|
+
def initialize(performable, target, at=nil)
|
5
|
+
@performable = performable
|
6
|
+
@target = target
|
7
|
+
@at = at
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args)
|
11
|
+
# Creeper has a limitation in that its message must be JSON.
|
12
|
+
# JSON can't round trip real Ruby objects so we use YAML to
|
13
|
+
# serialize the objects to a String. The YAML will be converted
|
14
|
+
# to JSON and then deserialized on the other side back into a
|
15
|
+
# Ruby object.
|
16
|
+
obj = [@target, name, args]
|
17
|
+
if @at
|
18
|
+
@performable.perform_at(@at, ::YAML.dump(obj))
|
19
|
+
else
|
20
|
+
@performable.perform_async(::YAML.dump(obj))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'creeper'
|
2
|
+
require 'celluloid'
|
3
|
+
|
4
|
+
module Creeper
|
5
|
+
##
|
6
|
+
# The Fetcher blocks on Redis, waiting for a message to process
|
7
|
+
# from the queues. It gets the message and hands it to the Manager
|
8
|
+
# to assign to a ready Processor.
|
9
|
+
class Fetcher
|
10
|
+
include Celluloid
|
11
|
+
include Creeper::Util
|
12
|
+
|
13
|
+
TIMEOUT = 1
|
14
|
+
|
15
|
+
def initialize(mgr, queues, strict)
|
16
|
+
@mgr = mgr
|
17
|
+
@strictly_ordered_queues = strict
|
18
|
+
@queues = queues.map { |q| "queue:#{q}" }
|
19
|
+
@unique_queues = @queues.uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
# Fetching is straightforward: the Manager makes a fetch
|
23
|
+
# request for each idle processor when Sidekiq starts and
|
24
|
+
# then issues a new fetch request every time a Processor
|
25
|
+
# finishes a message.
|
26
|
+
#
|
27
|
+
# Because we have to shut down cleanly, we can't block
|
28
|
+
# forever and we can't loop forever. Instead we reschedule
|
29
|
+
# a new fetch if the current fetch turned up nothing.
|
30
|
+
def fetch
|
31
|
+
watchdog('Fetcher#fetch died') do
|
32
|
+
return if Creeper::Fetcher.done?
|
33
|
+
|
34
|
+
begin
|
35
|
+
queue = nil
|
36
|
+
msg = nil
|
37
|
+
job = nil
|
38
|
+
conn = nil
|
39
|
+
|
40
|
+
conn = Creeper::BeanstalkConnection.create
|
41
|
+
|
42
|
+
begin
|
43
|
+
job = conn.reserve(TIMEOUT)
|
44
|
+
queue, msg = Creeper.load_json(job.body)
|
45
|
+
rescue Beanstalk::TimedOut
|
46
|
+
logger.debug("No message fetched after #{TIMEOUT} seconds") if $DEBUG
|
47
|
+
job.release rescue nil
|
48
|
+
conn.close rescue nil
|
49
|
+
sleep(TIMEOUT)
|
50
|
+
return after(0) { fetch }
|
51
|
+
end
|
52
|
+
|
53
|
+
if msg
|
54
|
+
@mgr.assign!(msg, queue.gsub(/.*queue:/, ''), job, conn)
|
55
|
+
else
|
56
|
+
after(0) { fetch }
|
57
|
+
end
|
58
|
+
rescue => ex
|
59
|
+
logger.error("Error fetching message: #{ex}")
|
60
|
+
logger.error(ex.backtrace.first)
|
61
|
+
job.release rescue nil
|
62
|
+
conn.close rescue nil
|
63
|
+
sleep(TIMEOUT)
|
64
|
+
after(0) { fetch }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Ugh. Say hello to a bloody hack.
|
70
|
+
# Can't find a clean way to get the fetcher to just stop processing
|
71
|
+
# its mailbox when shutdown starts.
|
72
|
+
def self.done!
|
73
|
+
@done = true
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.done?
|
77
|
+
@done
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Creating the Redis#blpop command takes into account any
|
83
|
+
# configured queue weights. By default Redis#blpop returns
|
84
|
+
# data from the first queue that has pending elements. We
|
85
|
+
# recreate the queue command each time we invoke Redis#blpop
|
86
|
+
# to honor weights and avoid queue starvation.
|
87
|
+
def queues_cmd
|
88
|
+
return @unique_queues.dup << TIMEOUT if @strictly_ordered_queues
|
89
|
+
queues = @queues.sample(@unique_queues.size).uniq
|
90
|
+
queues.concat(@unique_queues - queues)
|
91
|
+
queues << TIMEOUT
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Creeper
|
2
|
+
module Legacy
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def job_descriptions
|
7
|
+
@job_descriptions ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def enqueue(job, *args)
|
11
|
+
enqueue!(job, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def enqueue!(job, *args)
|
15
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
16
|
+
priority = options[:priority] || options[:pri] || 65536
|
17
|
+
delay = [ 0, options[:delay].to_i ].max
|
18
|
+
time_to_run = options[:time_to_run] || options[:ttr] || 120
|
19
|
+
|
20
|
+
klass = options[:class] || job_descriptions[job]
|
21
|
+
|
22
|
+
Creeper::Client.push({
|
23
|
+
'queue' => job,
|
24
|
+
'args' => args,
|
25
|
+
'class' => klass,
|
26
|
+
'delay' => delay,
|
27
|
+
'priority' => priority,
|
28
|
+
'time_to_run' => time_to_run
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
module WorkerMethods
|
35
|
+
|
36
|
+
def creeper_legacy_queue(tube = nil)
|
37
|
+
return @creeper_legacy_queue if tube.nil?
|
38
|
+
(@creeper_legacy_queue = tube).tap do
|
39
|
+
Creeper.job_descriptions[@creeper_legacy_queue] = self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Creeper
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
class Pretty < Logger::Formatter
|
8
|
+
# Provide a call() method that returns the formatted message.
|
9
|
+
def call(severity, time, program_name, message)
|
10
|
+
"#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
def context
|
14
|
+
c = Thread.current[:creeper_context]
|
15
|
+
c ? " #{c}" : ''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.with_context(msg)
|
20
|
+
begin
|
21
|
+
Thread.current[:creeper_context] = msg
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
Thread.current[:creeper_context] = nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.logger
|
29
|
+
@logger ||= begin
|
30
|
+
log = Logger.new(STDOUT)
|
31
|
+
log.level = Logger::INFO
|
32
|
+
log.formatter = Pretty.new
|
33
|
+
log
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.logger=(log)
|
38
|
+
@logger = (log ? log : Logger.new('/dev/null'))
|
39
|
+
end
|
40
|
+
|
41
|
+
def logger
|
42
|
+
Creeper::Logging.logger
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
3
|
+
require 'creeper/util'
|
4
|
+
require 'creeper/processor'
|
5
|
+
require 'creeper/fetch'
|
6
|
+
|
7
|
+
module Creeper
|
8
|
+
|
9
|
+
##
|
10
|
+
# The main router in the system. This
|
11
|
+
# manages the processor state and accepts messages
|
12
|
+
# from Redis to be dispatched to an idle processor.
|
13
|
+
#
|
14
|
+
class Manager
|
15
|
+
include Util
|
16
|
+
include Celluloid
|
17
|
+
|
18
|
+
trap_exit :processor_died
|
19
|
+
|
20
|
+
def initialize(options={})
|
21
|
+
logger.info "Booting creeper #{Creeper::VERSION} with Beanstalk at #{beanstalk { |x| x.instance_variable_get(:@addrs).join(', ')}} and Redis at #{redis {|x| x.client.id}}"
|
22
|
+
logger.info "Running in #{RUBY_DESCRIPTION}"
|
23
|
+
logger.debug { options.inspect }
|
24
|
+
@count = options[:concurrency] || 25
|
25
|
+
@done_callback = nil
|
26
|
+
|
27
|
+
@in_progress = {}
|
28
|
+
@done = false
|
29
|
+
@busy = []
|
30
|
+
@fetcher = Fetcher.new(current_actor, options[:queues], !!options[:strict])
|
31
|
+
@ready = @count.times.map { Processor.new_link(current_actor) }
|
32
|
+
procline
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop(options={})
|
36
|
+
watchdog('Manager#stop died') do
|
37
|
+
shutdown = options[:shutdown]
|
38
|
+
timeout = options[:timeout]
|
39
|
+
|
40
|
+
@done = true
|
41
|
+
Creeper::Fetcher.done!
|
42
|
+
@fetcher.terminate! if @fetcher.alive?
|
43
|
+
|
44
|
+
logger.info { "Shutting down #{@ready.size} quiet workers" }
|
45
|
+
@ready.each { |x| x.terminate if x.alive? }
|
46
|
+
@ready.clear
|
47
|
+
|
48
|
+
logger.debug { "Clearing workers in redis" }
|
49
|
+
Creeper.redis do |conn|
|
50
|
+
workers = conn.smembers('workers')
|
51
|
+
workers.each do |name|
|
52
|
+
conn.srem('workers', name) if name =~ /:#{process_id}-/
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return after(0) { signal(:shutdown) } if @busy.empty?
|
57
|
+
logger.info { "Pausing up to #{timeout} seconds to allow workers to finish..." }
|
58
|
+
hard_shutdown_in timeout if shutdown
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def start
|
63
|
+
@ready.each { dispatch }
|
64
|
+
end
|
65
|
+
|
66
|
+
def when_done(&blk)
|
67
|
+
@done_callback = blk
|
68
|
+
end
|
69
|
+
|
70
|
+
def processor_done(processor)
|
71
|
+
watchdog('Manager#processor_done died') do
|
72
|
+
@done_callback.call(processor) if @done_callback
|
73
|
+
@in_progress.delete(processor.object_id)
|
74
|
+
@busy.delete(processor)
|
75
|
+
if stopped?
|
76
|
+
processor.terminate if processor.alive?
|
77
|
+
signal(:shutdown) if @busy.empty?
|
78
|
+
else
|
79
|
+
@ready << processor if processor.alive?
|
80
|
+
end
|
81
|
+
dispatch
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def processor_died(processor, reason)
|
86
|
+
watchdog("Manager#processor_died died") do
|
87
|
+
@in_progress.delete(processor.object_id)
|
88
|
+
@busy.delete(processor)
|
89
|
+
|
90
|
+
unless stopped?
|
91
|
+
@ready << Processor.new_link(current_actor)
|
92
|
+
dispatch
|
93
|
+
else
|
94
|
+
signal(:shutdown) if @busy.empty?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def assign(msg, queue, job, conn)
|
100
|
+
watchdog("Manager#assign died") do
|
101
|
+
if stopped?
|
102
|
+
# Race condition between Manager#stop if Fetcher
|
103
|
+
# is blocked on redis and gets a message after
|
104
|
+
# all the ready Processors have been stopped.
|
105
|
+
# Push the message back to redis.
|
106
|
+
job.release rescue nil
|
107
|
+
conn.close rescue nil
|
108
|
+
# Creeper.redis do |conn|
|
109
|
+
# conn.lpush("queue:#{queue}", msg)
|
110
|
+
# end
|
111
|
+
else
|
112
|
+
processor = @ready.pop
|
113
|
+
@in_progress[processor.object_id] = [msg, queue, job, conn]
|
114
|
+
@busy << processor
|
115
|
+
processor.process!(msg, queue, job, conn)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def hard_shutdown_in(delay)
|
123
|
+
after(delay) do
|
124
|
+
watchdog("Manager#watch_for_shutdown died") do
|
125
|
+
# We've reached the timeout and we still have busy workers.
|
126
|
+
# They must die but their messages shall live on.
|
127
|
+
logger.info("Still waiting for #{@busy.size} busy workers")
|
128
|
+
|
129
|
+
@busy.each do |processor|
|
130
|
+
# processor is an actor proxy and we can't call any methods
|
131
|
+
# that would go to the actor (since it's busy). Instead
|
132
|
+
# we'll use the object_id to track the worker's data here.
|
133
|
+
msg, queue, job, conn = @in_progress[processor.object_id]
|
134
|
+
job.release rescue nil
|
135
|
+
conn.close rescue nil
|
136
|
+
# conn.lpush("queue:#{queue}", msg)
|
137
|
+
end
|
138
|
+
logger.info("Released #{@busy.size} jobs back to Beanstalk")
|
139
|
+
|
140
|
+
after(0) { signal(:shutdown) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def dispatch
|
146
|
+
return if stopped?
|
147
|
+
# This is a safety check to ensure we haven't leaked
|
148
|
+
# processors somehow.
|
149
|
+
raise "BUG: No processors, cannot continue!" if @ready.empty? && @busy.empty?
|
150
|
+
raise "No ready processor!?" if @ready.empty?
|
151
|
+
|
152
|
+
@fetcher.fetch!
|
153
|
+
end
|
154
|
+
|
155
|
+
def stopped?
|
156
|
+
@done
|
157
|
+
end
|
158
|
+
|
159
|
+
def procline
|
160
|
+
$0 = "creeper #{Creeper::VERSION} [#{@busy.size} of #{@count} busy]#{stopped? ? ' stopping' : ''}"
|
161
|
+
after(5) { procline }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Creeper
|
2
|
+
# Middleware is code configured to run before/after
|
3
|
+
# a message is processed. It is patterned after Rack
|
4
|
+
# middleware. Middleware exists for the client side
|
5
|
+
# (pushing jobs onto the queue) as well as the server
|
6
|
+
# side (when jobs are actually processed).
|
7
|
+
#
|
8
|
+
# To add middleware for the client:
|
9
|
+
#
|
10
|
+
# Creeper.configure_client do |config|
|
11
|
+
# config.client_middleware do |chain|
|
12
|
+
# chain.add MyClientHook
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# To modify middleware for the server, just call
|
17
|
+
# with another block:
|
18
|
+
#
|
19
|
+
# Creeper.configure_server do |config|
|
20
|
+
# config.server_middleware do |chain|
|
21
|
+
# chain.add MyServerHook
|
22
|
+
# chain.remove ActiveRecord
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# This is an example of a minimal server middleware:
|
27
|
+
#
|
28
|
+
# class MyServerHook
|
29
|
+
# def call(worker_instance, msg, queue)
|
30
|
+
# puts "Before work"
|
31
|
+
# yield
|
32
|
+
# puts "After work"
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# This is an example of a minimal client middleware:
|
37
|
+
#
|
38
|
+
# class MyClientHook
|
39
|
+
# def call(worker_class, msg, queue)
|
40
|
+
# puts "Before push"
|
41
|
+
# yield
|
42
|
+
# puts "After push"
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
module Middleware
|
47
|
+
class Chain
|
48
|
+
attr_reader :entries
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@entries = []
|
52
|
+
yield self if block_given?
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove(klass)
|
56
|
+
entries.delete_if { |entry| entry.klass == klass }
|
57
|
+
end
|
58
|
+
|
59
|
+
def add(klass, *args)
|
60
|
+
entries << Entry.new(klass, *args) unless exists?(klass)
|
61
|
+
end
|
62
|
+
|
63
|
+
def exists?(klass)
|
64
|
+
entries.any? { |entry| entry.klass == klass }
|
65
|
+
end
|
66
|
+
|
67
|
+
def retrieve
|
68
|
+
entries.map(&:make_new)
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear
|
72
|
+
entries.clear
|
73
|
+
end
|
74
|
+
|
75
|
+
def invoke(*args, &final_action)
|
76
|
+
chain = retrieve.dup
|
77
|
+
traverse_chain = lambda do
|
78
|
+
if chain.empty?
|
79
|
+
final_action.call
|
80
|
+
else
|
81
|
+
chain.shift.call(*args, &traverse_chain)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
traverse_chain.call
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Entry
|
89
|
+
attr_reader :klass
|
90
|
+
def initialize(klass, *args)
|
91
|
+
@klass = klass
|
92
|
+
@args = args
|
93
|
+
end
|
94
|
+
|
95
|
+
def make_new
|
96
|
+
@klass.new(*@args)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|