kulesa-sidekiq 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rvmrc +4 -0
- data/COMM-LICENSE +83 -0
- data/Changes.md +207 -0
- data/Gemfile +12 -0
- data/LICENSE +22 -0
- data/README.md +61 -0
- data/Rakefile +9 -0
- data/bin/client +7 -0
- data/bin/sidekiq +14 -0
- data/bin/sidekiqctl +74 -0
- data/config.ru +18 -0
- data/examples/chef/cookbooks/sidekiq/README.rdoc +11 -0
- data/examples/chef/cookbooks/sidekiq/recipes/default.rb +55 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/monitrc.conf.erb +8 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.erb +219 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.yml.erb +22 -0
- data/examples/clockwork.rb +44 -0
- data/examples/config.yml +10 -0
- data/examples/monitrc.conf +6 -0
- data/examples/por.rb +27 -0
- data/examples/scheduling.rb +37 -0
- data/examples/sinkiq.rb +59 -0
- data/examples/web-ui.png +0 -0
- data/lib/sidekiq.rb +98 -0
- data/lib/sidekiq/capistrano.rb +35 -0
- data/lib/sidekiq/cli.rb +214 -0
- data/lib/sidekiq/client.rb +72 -0
- data/lib/sidekiq/extensions/action_mailer.rb +26 -0
- data/lib/sidekiq/extensions/active_record.rb +27 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +21 -0
- data/lib/sidekiq/fetch.rb +76 -0
- data/lib/sidekiq/logging.rb +46 -0
- data/lib/sidekiq/manager.rb +163 -0
- data/lib/sidekiq/middleware/chain.rb +96 -0
- data/lib/sidekiq/middleware/client/unique_jobs.rb +36 -0
- data/lib/sidekiq/middleware/server/active_record.rb +13 -0
- data/lib/sidekiq/middleware/server/exception_handler.rb +38 -0
- data/lib/sidekiq/middleware/server/failure_jobs.rb +25 -0
- data/lib/sidekiq/middleware/server/logging.rb +31 -0
- data/lib/sidekiq/middleware/server/retry_jobs.rb +69 -0
- data/lib/sidekiq/middleware/server/timeout.rb +21 -0
- data/lib/sidekiq/middleware/server/unique_jobs.rb +17 -0
- data/lib/sidekiq/processor.rb +92 -0
- data/lib/sidekiq/rails.rb +21 -0
- data/lib/sidekiq/redis_connection.rb +27 -0
- data/lib/sidekiq/retry.rb +59 -0
- data/lib/sidekiq/testing.rb +44 -0
- data/lib/sidekiq/testing/inline.rb +37 -0
- data/lib/sidekiq/util.rb +40 -0
- data/lib/sidekiq/version.rb +3 -0
- data/lib/sidekiq/web.rb +185 -0
- data/lib/sidekiq/worker.rb +62 -0
- data/lib/sidekiq/yaml_patch.rb +21 -0
- data/myapp/.gitignore +15 -0
- data/myapp/Capfile +5 -0
- data/myapp/Gemfile +19 -0
- data/myapp/Rakefile +7 -0
- data/myapp/app/controllers/application_controller.rb +3 -0
- data/myapp/app/controllers/work_controller.rb +38 -0
- data/myapp/app/helpers/application_helper.rb +2 -0
- data/myapp/app/mailers/.gitkeep +0 -0
- data/myapp/app/mailers/user_mailer.rb +9 -0
- data/myapp/app/models/.gitkeep +0 -0
- data/myapp/app/models/post.rb +5 -0
- data/myapp/app/views/layouts/application.html.erb +14 -0
- data/myapp/app/views/user_mailer/greetings.html.erb +3 -0
- data/myapp/app/views/work/index.html.erb +1 -0
- data/myapp/app/workers/hard_worker.rb +10 -0
- data/myapp/config.ru +4 -0
- data/myapp/config/application.rb +59 -0
- data/myapp/config/boot.rb +6 -0
- data/myapp/config/database.yml +25 -0
- data/myapp/config/deploy.rb +15 -0
- data/myapp/config/environment.rb +5 -0
- data/myapp/config/environments/development.rb +38 -0
- data/myapp/config/environments/production.rb +67 -0
- data/myapp/config/environments/test.rb +37 -0
- data/myapp/config/initializers/backtrace_silencers.rb +7 -0
- data/myapp/config/initializers/inflections.rb +15 -0
- data/myapp/config/initializers/mime_types.rb +5 -0
- data/myapp/config/initializers/secret_token.rb +7 -0
- data/myapp/config/initializers/session_store.rb +8 -0
- data/myapp/config/initializers/sidekiq.rb +6 -0
- data/myapp/config/initializers/wrap_parameters.rb +14 -0
- data/myapp/config/locales/en.yml +5 -0
- data/myapp/config/routes.rb +10 -0
- data/myapp/db/migrate/20120123214055_create_posts.rb +10 -0
- data/myapp/db/seeds.rb +7 -0
- data/myapp/lib/assets/.gitkeep +0 -0
- data/myapp/lib/tasks/.gitkeep +0 -0
- data/myapp/log/.gitkeep +0 -0
- data/myapp/script/rails +6 -0
- data/sidekiq.gemspec +27 -0
- data/test/config.yml +9 -0
- data/test/fake_env.rb +0 -0
- data/test/helper.rb +16 -0
- data/test/test_cli.rb +168 -0
- data/test/test_client.rb +119 -0
- data/test/test_extensions.rb +69 -0
- data/test/test_manager.rb +51 -0
- data/test/test_middleware.rb +92 -0
- data/test/test_processor.rb +32 -0
- data/test/test_retry.rb +125 -0
- data/test/test_stats.rb +68 -0
- data/test/test_testing.rb +97 -0
- data/test/test_testing_inline.rb +75 -0
- data/test/test_web.rb +122 -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 +20 -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 +27 -0
- data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
- data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
- data/web/views/index.slim +48 -0
- data/web/views/layout.slim +26 -0
- data/web/views/queue.slim +11 -0
- data/web/views/retries.slim +29 -0
- data/web/views/retry.slim +52 -0
- metadata +371 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'sidekiq/extensions/generic_proxy'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Extensions
|
5
|
+
##
|
6
|
+
# Adds a 'delay' method to ActionMailer to offload arbitrary email
|
7
|
+
# delivery to Sidekiq. Example:
|
8
|
+
#
|
9
|
+
# UserMailer.delay.send_welcome_email(new_user)
|
10
|
+
class DelayedMailer
|
11
|
+
include Sidekiq::Worker
|
12
|
+
|
13
|
+
def perform(yml)
|
14
|
+
(target, method_name, args) = YAML.load(yml)
|
15
|
+
target.send(method_name, *args).deliver
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ActionMailer
|
20
|
+
def delay
|
21
|
+
Proxy.new(DelayedMailer, self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'sidekiq/extensions/generic_proxy'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Extensions
|
5
|
+
##
|
6
|
+
# Adds a 'delay' method to ActiveRecord to offload arbitrary method
|
7
|
+
# execution to Sidekiq. Examples:
|
8
|
+
#
|
9
|
+
# User.delay.delete_inactive
|
10
|
+
# User.recent_signups.each { |user| user.delay.mark_as_awesome }
|
11
|
+
class DelayedModel
|
12
|
+
include Sidekiq::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
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Extensions
|
3
|
+
class Proxy < (RUBY_VERSION < '1.9' ? Object : BasicObject)
|
4
|
+
def initialize(performable, target)
|
5
|
+
@performable = performable
|
6
|
+
@target = target
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(name, *args)
|
10
|
+
# Sidekiq has a limitation in that its message must be JSON.
|
11
|
+
# JSON can't round trip real Ruby objects so we use YAML to
|
12
|
+
# serialize the objects to a String. The YAML will be converted
|
13
|
+
# to JSON and then deserialized on the other side back into a
|
14
|
+
# Ruby object.
|
15
|
+
obj = [@target, name, args]
|
16
|
+
@performable.perform_async(::YAML.dump(obj))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'celluloid'
|
3
|
+
|
4
|
+
module Sidekiq
|
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 Sidekiq::Util
|
12
|
+
|
13
|
+
TIMEOUT = 1
|
14
|
+
|
15
|
+
def initialize(mgr, queues)
|
16
|
+
@mgr = mgr
|
17
|
+
@queues = queues.map { |q| "queue:#{q}" }
|
18
|
+
@unique_queues = @queues.uniq
|
19
|
+
end
|
20
|
+
|
21
|
+
# Fetching is straightforward: the Manager makes a fetch
|
22
|
+
# request for each idle processor when Sidekiq starts and
|
23
|
+
# then issues a new fetch request every time a Processor
|
24
|
+
# finishes a message.
|
25
|
+
#
|
26
|
+
# Because we have to shut down cleanly, we can't block
|
27
|
+
# forever and we can't loop forever. Instead we reschedule
|
28
|
+
# a new fetch if the current fetch turned up nothing.
|
29
|
+
def fetch
|
30
|
+
watchdog('Fetcher#fetch died') do
|
31
|
+
return if Sidekiq::Fetcher.done?
|
32
|
+
|
33
|
+
begin
|
34
|
+
queue = nil
|
35
|
+
msg = nil
|
36
|
+
Sidekiq.redis { |conn| queue, msg = conn.blpop(*queues_cmd) }
|
37
|
+
|
38
|
+
if msg
|
39
|
+
@mgr.assign!(msg, queue.gsub(/.*queue:/, ''))
|
40
|
+
else
|
41
|
+
after(0) { fetch }
|
42
|
+
end
|
43
|
+
rescue => ex
|
44
|
+
logger.error("Error fetching message: #{ex}")
|
45
|
+
logger.error(ex.backtrace.first)
|
46
|
+
sleep(TIMEOUT)
|
47
|
+
after(0) { fetch }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Ugh. Say hello to a bloody hack.
|
53
|
+
# Can't find a clean way to get the fetcher to just stop processing
|
54
|
+
# its mailbox when shutdown starts.
|
55
|
+
def self.done!
|
56
|
+
@done = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.done?
|
60
|
+
@done
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Creating the Redis#blpop command takes into account any
|
66
|
+
# configured queue weights. By default Redis#blpop returns
|
67
|
+
# data from the first queue that has pending elements. We
|
68
|
+
# recreate the queue command each time we invoke Redis#blpop
|
69
|
+
# to honor weights and avoid queue starvation.
|
70
|
+
def queues_cmd
|
71
|
+
queues = @queues.sample(@unique_queues.size).uniq
|
72
|
+
queues.concat(@unique_queues - queues)
|
73
|
+
queues << TIMEOUT
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Sidekiq
|
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[:sidekiq_context]
|
15
|
+
c ? " #{c}" : ''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.with_context(msg)
|
20
|
+
begin
|
21
|
+
Thread.current[:sidekiq_context] = msg
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
Thread.current[:sidekiq_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
|
+
Sidekiq::Logging.logger
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
require 'sidekiq/util'
|
5
|
+
require 'sidekiq/processor'
|
6
|
+
require 'sidekiq/fetch'
|
7
|
+
|
8
|
+
module Sidekiq
|
9
|
+
|
10
|
+
##
|
11
|
+
# The main router in the system. This
|
12
|
+
# manages the processor state and accepts messages
|
13
|
+
# from Redis to be dispatched to an idle processor.
|
14
|
+
#
|
15
|
+
class Manager
|
16
|
+
include Util
|
17
|
+
include Celluloid
|
18
|
+
|
19
|
+
trap_exit :processor_died
|
20
|
+
|
21
|
+
def initialize(options={})
|
22
|
+
logger.info "Booting sidekiq #{Sidekiq::VERSION} with Redis at #{redis {|x| x.client.id}}"
|
23
|
+
logger.info "Running in #{RUBY_DESCRIPTION}"
|
24
|
+
logger.debug { options.inspect }
|
25
|
+
@count = options[:concurrency] || 25
|
26
|
+
@done_callback = nil
|
27
|
+
|
28
|
+
@in_progress = {}
|
29
|
+
@done = false
|
30
|
+
@busy = []
|
31
|
+
@fetcher = Fetcher.new(current_actor, options[:queues])
|
32
|
+
@ready = @count.times.map { Processor.new_link(current_actor) }
|
33
|
+
procline
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop(options={})
|
37
|
+
watchdog('Manager#stop died') do
|
38
|
+
shutdown = options[:shutdown]
|
39
|
+
timeout = options[:timeout]
|
40
|
+
|
41
|
+
@done = true
|
42
|
+
Sidekiq::Fetcher.done!
|
43
|
+
@fetcher.terminate! if @fetcher.alive?
|
44
|
+
|
45
|
+
logger.info { "Shutting down #{@ready.size} quiet workers" }
|
46
|
+
@ready.each { |x| x.terminate if x.alive? }
|
47
|
+
@ready.clear
|
48
|
+
|
49
|
+
logger.debug { "Clearing workers in redis" }
|
50
|
+
Sidekiq.redis do |conn|
|
51
|
+
workers = conn.smembers('workers')
|
52
|
+
workers.each do |name|
|
53
|
+
conn.srem('workers', name) if name =~ /:#{process_id}-/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return after(0) { signal(:shutdown) } if @busy.empty?
|
58
|
+
logger.info { "Pausing up to #{timeout} seconds to allow workers to finish..." }
|
59
|
+
hard_shutdown_in timeout if shutdown
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def start
|
64
|
+
@ready.each { dispatch }
|
65
|
+
end
|
66
|
+
|
67
|
+
def when_done(&blk)
|
68
|
+
@done_callback = blk
|
69
|
+
end
|
70
|
+
|
71
|
+
def processor_done(processor)
|
72
|
+
watchdog('Manager#processor_done died') do
|
73
|
+
@done_callback.call(processor) if @done_callback
|
74
|
+
@in_progress.delete(processor.object_id)
|
75
|
+
@busy.delete(processor)
|
76
|
+
if stopped?
|
77
|
+
processor.terminate if processor.alive?
|
78
|
+
signal(:shutdown) if @busy.empty?
|
79
|
+
else
|
80
|
+
@ready << processor if processor.alive?
|
81
|
+
end
|
82
|
+
dispatch
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def processor_died(processor, reason)
|
87
|
+
watchdog("Manager#processor_died died") do
|
88
|
+
@in_progress.delete(processor.object_id)
|
89
|
+
@busy.delete(processor)
|
90
|
+
|
91
|
+
unless stopped?
|
92
|
+
@ready << Processor.new_link(current_actor)
|
93
|
+
dispatch
|
94
|
+
else
|
95
|
+
signal(:shutdown) if @busy.empty?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def assign(msg, queue)
|
101
|
+
watchdog("Manager#assign died") do
|
102
|
+
if stopped?
|
103
|
+
# Race condition between Manager#stop if Fetcher
|
104
|
+
# is blocked on redis and gets a message after
|
105
|
+
# all the ready Processors have been stopped.
|
106
|
+
# Push the message back to redis.
|
107
|
+
Sidekiq.redis do |conn|
|
108
|
+
conn.lpush("queue:#{queue}", msg)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
processor = @ready.pop
|
112
|
+
@in_progress[processor.object_id] = [msg, queue]
|
113
|
+
@busy << processor
|
114
|
+
processor.process!(Sidekiq.load_json(msg), queue)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def hard_shutdown_in(delay)
|
122
|
+
after(delay) do
|
123
|
+
watchdog("Manager#watch_for_shutdown died") do
|
124
|
+
# We've reached the timeout and we still have busy workers.
|
125
|
+
# They must die but their messages shall live on.
|
126
|
+
logger.info("Still waiting for #{@busy.size} busy workers")
|
127
|
+
|
128
|
+
Sidekiq.redis do |conn|
|
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 = @in_progress[processor.object_id]
|
134
|
+
conn.lpush("queue:#{queue}", msg)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
logger.info("Pushed #{@busy.size} messages back to Redis")
|
138
|
+
|
139
|
+
after(0) { signal(:shutdown) }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def dispatch
|
145
|
+
return if stopped?
|
146
|
+
# This is a safety check to ensure we haven't leaked
|
147
|
+
# processors somehow.
|
148
|
+
raise "BUG: No processors, cannot continue!" if @ready.empty? && @busy.empty?
|
149
|
+
raise "No ready processor!?" if @ready.empty?
|
150
|
+
|
151
|
+
@fetcher.fetch!
|
152
|
+
end
|
153
|
+
|
154
|
+
def stopped?
|
155
|
+
@done
|
156
|
+
end
|
157
|
+
|
158
|
+
def procline
|
159
|
+
$0 = "sidekiq #{Sidekiq::VERSION} [#{@busy.size} of #{@count} busy]#{stopped? ? ' stopping' : ''}"
|
160
|
+
after(5) { procline }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Sidekiq
|
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
|
+
# Sidekiq.client_middleware do |chain|
|
11
|
+
# chain.add MyClientHook
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# To modify middleware for the server, just call
|
15
|
+
# with another block:
|
16
|
+
#
|
17
|
+
# Sidekiq.server_middleware do |chain|
|
18
|
+
# chain.add MyServerHook
|
19
|
+
# chain.remove ActiveRecord
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# This is an example of a minimal server middleware:
|
23
|
+
#
|
24
|
+
# class MyServerHook
|
25
|
+
# def call(worker, msg, queue)
|
26
|
+
# puts "Before work"
|
27
|
+
# yield
|
28
|
+
# puts "After work"
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# This is an example of a minimal client middleware:
|
33
|
+
#
|
34
|
+
# class MyClientHook
|
35
|
+
# def call(msg, queue)
|
36
|
+
# puts "Before push"
|
37
|
+
# yield
|
38
|
+
# puts "After push"
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
module Middleware
|
43
|
+
class Chain
|
44
|
+
attr_reader :entries
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@entries = []
|
48
|
+
yield self if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove(klass)
|
52
|
+
entries.delete_if { |entry| entry.klass == klass }
|
53
|
+
end
|
54
|
+
|
55
|
+
def add(klass, *args)
|
56
|
+
entries << Entry.new(klass, *args) unless exists?(klass)
|
57
|
+
end
|
58
|
+
|
59
|
+
def exists?(klass)
|
60
|
+
entries.any? { |entry| entry.klass == klass }
|
61
|
+
end
|
62
|
+
|
63
|
+
def retrieve
|
64
|
+
entries.map(&:make_new)
|
65
|
+
end
|
66
|
+
|
67
|
+
def clear
|
68
|
+
entries.clear
|
69
|
+
end
|
70
|
+
|
71
|
+
def invoke(*args, &final_action)
|
72
|
+
chain = retrieve.dup
|
73
|
+
traverse_chain = lambda do
|
74
|
+
if chain.empty?
|
75
|
+
final_action.call
|
76
|
+
else
|
77
|
+
chain.shift.call(*args, &traverse_chain)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
traverse_chain.call
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Entry
|
85
|
+
attr_reader :klass
|
86
|
+
def initialize(klass, *args)
|
87
|
+
@klass = klass
|
88
|
+
@args = args
|
89
|
+
end
|
90
|
+
|
91
|
+
def make_new
|
92
|
+
@klass.new(*@args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|