resque-integration 3.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +28 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/Appraisals +27 -0
  6. data/CHANGELOG.md +311 -0
  7. data/Gemfile +4 -0
  8. data/README.md +281 -0
  9. data/Rakefile +1 -0
  10. data/app/assets/javascripts/standalone/progress_bar.js +47 -0
  11. data/app/controllers/resque/jobs_controller.rb +42 -0
  12. data/app/controllers/resque/queues/info_controller.rb +9 -0
  13. data/app/controllers/resque/queues/status_controller.rb +38 -0
  14. data/app/views/shared/job_progress_bar.html.haml +17 -0
  15. data/bin/resque-status +59 -0
  16. data/config/routes.rb +13 -0
  17. data/config.ru +9 -0
  18. data/dip.yml +44 -0
  19. data/docker-compose.development.yml +12 -0
  20. data/docker-compose.drone.yml +6 -0
  21. data/docker-compose.yml +10 -0
  22. data/lib/generators/resque/integration/install/install_generator.rb +38 -0
  23. data/lib/resque/integration/backtrace.rb +29 -0
  24. data/lib/resque/integration/configuration.rb +238 -0
  25. data/lib/resque/integration/continuous.rb +75 -0
  26. data/lib/resque/integration/engine.rb +103 -0
  27. data/lib/resque/integration/extensions/job.rb +17 -0
  28. data/lib/resque/integration/extensions/worker.rb +17 -0
  29. data/lib/resque/integration/extensions.rb +8 -0
  30. data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
  31. data/lib/resque/integration/failure_backends.rb +7 -0
  32. data/lib/resque/integration/god.erb +99 -0
  33. data/lib/resque/integration/hooks.rb +72 -0
  34. data/lib/resque/integration/logs_rotator.rb +95 -0
  35. data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
  36. data/lib/resque/integration/ordered.rb +142 -0
  37. data/lib/resque/integration/priority.rb +89 -0
  38. data/lib/resque/integration/queues_info/age.rb +53 -0
  39. data/lib/resque/integration/queues_info/config.rb +96 -0
  40. data/lib/resque/integration/queues_info/size.rb +33 -0
  41. data/lib/resque/integration/queues_info.rb +55 -0
  42. data/lib/resque/integration/tasks/hooks.rake +49 -0
  43. data/lib/resque/integration/tasks/lock.rake +37 -0
  44. data/lib/resque/integration/tasks/resque.rake +101 -0
  45. data/lib/resque/integration/unique.rb +218 -0
  46. data/lib/resque/integration/version.rb +5 -0
  47. data/lib/resque/integration.rb +146 -0
  48. data/lib/resque-integration.rb +1 -0
  49. data/resque-integration.gemspec +40 -0
  50. data/spec/fixtures/resque_queues.yml +45 -0
  51. data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
  52. data/spec/resque/integration/configuration_spec.rb +147 -0
  53. data/spec/resque/integration/continuous_spec.rb +122 -0
  54. data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
  55. data/spec/resque/integration/ordered_spec.rb +87 -0
  56. data/spec/resque/integration/priority_spec.rb +94 -0
  57. data/spec/resque/integration/queues_info_spec.rb +402 -0
  58. data/spec/resque/integration/unique_spec.rb +184 -0
  59. data/spec/resque/integration_spec.rb +105 -0
  60. data/spec/shared/resque_inline.rb +10 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/vendor/assets/images/progressbar/white.gif +0 -0
  63. data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
  64. data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
  65. data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
  66. metadata +402 -0
@@ -0,0 +1,238 @@
1
+ # coding: utf-8
2
+
3
+ require 'yaml'
4
+ require 'ostruct'
5
+ require 'erb'
6
+
7
+ require 'active_support/core_ext/hash/keys'
8
+ require 'active_support/core_ext/hash/deep_merge'
9
+
10
+ module Resque
11
+ module Integration
12
+ class Configuration
13
+ # Worker entity
14
+ class Worker < OpenStruct
15
+ def initialize(queue, config)
16
+ data = {:queue => queue}
17
+
18
+ if config.is_a?(Hash)
19
+ data.merge!(config.symbolize_keys)
20
+ else
21
+ data[:count] = config
22
+ end
23
+
24
+ super(data)
25
+ end
26
+
27
+ # Returns workers count for given queue
28
+ def count
29
+ [super || 1, 1].max
30
+ end
31
+
32
+ # Returns hash of ENV variables that should be associated with this worker
33
+ def env
34
+ env = super || {}
35
+
36
+ env[:QUEUE] ||= queue
37
+ env[:JOBS_PER_FORK] ||= jobs_per_fork if jobs_per_fork
38
+ env[:MINUTES_PER_FORK] ||= minutes_per_fork if minutes_per_fork
39
+ env[:SHUFFLE] ||= 1 if shuffle
40
+
41
+ Hash[env.map { |k, v| [k, v.to_s] }]
42
+ end
43
+ end
44
+
45
+ # Failure notifier configuration
46
+ class Notifier < OpenStruct
47
+ def initialize(config)
48
+ super(config || {})
49
+ end
50
+
51
+ # Is notifier enabled
52
+ def enabled?
53
+ to.any? && enabled.nil? ? true : enabled
54
+ end
55
+
56
+ # Returns true if payload should be included into reports
57
+ def include_payload?
58
+ include_payload.nil? ?
59
+ true :
60
+ include_payload
61
+ end
62
+
63
+ # Returns recipients list
64
+ def to
65
+ super || []
66
+ end
67
+
68
+ # Returns sender address
69
+ def from
70
+ super || 'no_reply@gmail.com'
71
+ end
72
+
73
+ # Returns mailer method
74
+ def mail
75
+ (super || :alert).to_sym
76
+ end
77
+
78
+ # Returns mailer class
79
+ def mailer
80
+ super || 'ResqueFailedJobMailer::Mailer'
81
+ end
82
+ end
83
+
84
+ # Create configuration from given +paths+
85
+ def initialize(*paths)
86
+ @configuration = {}
87
+ paths.each { |f| load f }
88
+ end
89
+
90
+ # Returns Resque redis configuration
91
+ #
92
+ # @return [OpenStruct]
93
+ def redis
94
+ @redis ||= (self['redis'] || {}).symbolize_keys
95
+ end
96
+
97
+ # Returns workers configuration
98
+ #
99
+ # @return [Array<Worker>]
100
+ def workers
101
+ @workers ||= (self[:workers] || {}).map { |k, v| Worker.new(k, v) }
102
+ end
103
+
104
+ # Returns failure notifier config
105
+ def failure_notifier
106
+ @notifier ||= Notifier.new(self['failure.notifier'])
107
+ end
108
+
109
+ # Returns flag for cleaning on shutdown see https://github.com/resque/resque/issues/1167
110
+ def run_at_exit_hooks?
111
+ value = self['resque.run_at_exit_hooks']
112
+
113
+ if value.is_a?(String) && %w(n no false off disabled).include?(value)
114
+ value = false
115
+ end
116
+
117
+ value.nil? ? true : value
118
+ end
119
+
120
+ # Returns Resque polling interval
121
+ def interval
122
+ (self['resque.interval'] || 5).to_i
123
+ end
124
+
125
+ # Returns Resque verbosity level
126
+ def verbosity
127
+ (self['resque.verbosity'] || 0).to_i
128
+ end
129
+
130
+ # Returns Resque log level
131
+ def log_level
132
+ (self['resque.log_level'] || 1).to_i
133
+ end
134
+
135
+ # Returns path to resque log file
136
+ def log_file
137
+ self['resque.log_file'] || ::Rails.root.join('log/resque.log').to_s
138
+ end
139
+
140
+ def config_file
141
+ self['resque.config_file'] || ::Rails.root.join('config/resque.god').to_s
142
+ end
143
+
144
+ def pid_file
145
+ "#{pids}/resque-god.pid"
146
+ end
147
+
148
+ def pids
149
+ self['resque.pids'] || ::Rails.root.join('tmp/pids').to_s
150
+ end
151
+
152
+ def root
153
+ self['resque.root'] || ::Rails.root.to_s
154
+ end
155
+
156
+ # Путь до файла с расписание resque schedule
157
+ #
158
+ # Returns String
159
+ def schedule_file
160
+ self['resque.schedule_file'] || ::Rails.root.join('config', 'resque_schedule.yml')
161
+ end
162
+
163
+ # Есть ли расписание у приложения?
164
+ #
165
+ # Returns boolean
166
+ def schedule_exists?
167
+ return @schedule_exists if defined?(@schedule_exists)
168
+ @schedule_exists = File.exist?(schedule_file)
169
+ end
170
+
171
+ # Используется ли resque scheduler
172
+ #
173
+ # Returns boolean
174
+ def resque_scheduler?
175
+ value = self['resque.scheduler'] || true
176
+
177
+ if value.is_a?(String) && %w(n no false off disabled).include?(value)
178
+ value = false
179
+ end
180
+
181
+ value
182
+ end
183
+
184
+ # Returns maximum terminate timeout
185
+ def terminate_timeout
186
+ workers.map(&:stop_timeout).compact.max.to_i + 10
187
+ end
188
+
189
+ # Returns environment variables that should be associated with this configuration
190
+ def env
191
+ env = self['env'] || {}
192
+
193
+ env[:INTERVAL] ||= interval
194
+
195
+ Hash[env.map { |k, v| [k, v.to_s] }]
196
+ end
197
+
198
+ # Generate GOD configuration file
199
+ def to_god
200
+ template = ERB.new(File.read(File.join(File.dirname(__FILE__), 'god.erb')))
201
+ template.result(binding)
202
+ end
203
+
204
+ def god_log_file
205
+ self['resque.god_log_file'] || ::Rails.root.join('log/god.log').to_s
206
+ end
207
+
208
+ def god_log_level
209
+ self['resque.god_log_level'] || 'info'
210
+ end
211
+
212
+ private
213
+ def load(path)
214
+ if File.exists?(path)
215
+ input = File.read(path)
216
+ input = ERB.new(input).result if defined?(ERB)
217
+ config = YAML.load(input)
218
+
219
+ @configuration.merge!(config)
220
+ end
221
+ end
222
+
223
+ # get value from configuration
224
+ def [](path)
225
+ parts = path.to_s.split('.')
226
+ result = @configuration
227
+
228
+ parts.each do |k|
229
+ result = result[k]
230
+
231
+ break if result.nil?
232
+ end
233
+
234
+ result
235
+ end
236
+ end # class Configuration
237
+ end # module Integration
238
+ end # module Resque
@@ -0,0 +1,75 @@
1
+ module Resque
2
+ module Integration
3
+ # Continuous job can re-enqueue self with respect to resque-lock and resque-meta.
4
+ #
5
+ # @example
6
+ # class ResqueJob
7
+ # include Resque::Integration
8
+ #
9
+ # unique
10
+ # continuous
11
+ #
12
+ # def self.execute(id)
13
+ # chunk = Company.find(id).products.limit(1000)
14
+ #
15
+ # if chunk.size > 0
16
+ # heavy_work
17
+ # continue # it will re-enqueue the job with the same arguments. Avoid infinite loops!
18
+ # end
19
+ # end
20
+ # end
21
+ module Continuous
22
+ # Remove any locks if needed and re-enqueue job with the same arguments
23
+ def continue(*args)
24
+ @continued = args
25
+ end
26
+ private :continue # one should not call it from outside
27
+
28
+ # This callback resets Meta's finish flags
29
+ def after_perform_reset_meta(*)
30
+ if should_reset_meta?
31
+ meta_obj = meta
32
+
33
+ meta_obj.data.delete('succeeded')
34
+ meta_obj.data.delete('finished_at')
35
+
36
+ meta_obj.save
37
+ end
38
+ end
39
+
40
+ # Just to ensure that previous jobs won't affect current
41
+ def before_perform_continue(*)
42
+ @continued = nil
43
+ end
44
+
45
+ # `after` callbacks are executed after `around` callbacks
46
+ # so here we can re-enqueue the job, because lock (from resque-lock) already released
47
+ def after_perform_continue(*args)
48
+ if continued?
49
+ args = if @continued.any?
50
+ if unique?
51
+ meta_id = args.first # we should keep meta_id as first argument
52
+ [meta_id] + @continued
53
+ else
54
+ @continued
55
+ end
56
+ else
57
+ args
58
+ end
59
+
60
+ ::Resque.enqueue(self, *args)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def continued?
67
+ !@continued.nil?
68
+ end
69
+
70
+ def should_reset_meta?
71
+ continued? && unique? && meta
72
+ end
73
+ end # module Continuous
74
+ end # module Integration
75
+ end # module Resque
@@ -0,0 +1,103 @@
1
+ require 'redis'
2
+ require 'redis/version'
3
+ require 'rails/engine'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module Resque::Integration
7
+ # Rails engine
8
+ # @see http://guides.rubyonrails.org/engines.html
9
+ class Engine < Rails::Engine
10
+ rake_tasks do
11
+ load 'resque/integration/tasks/hooks.rake'
12
+ load 'resque/integration/tasks/resque.rake'
13
+ load 'resque/integration/tasks/lock.rake'
14
+ end
15
+
16
+ # Читает конфиг-файлы config/resque.yml и config/resque.local.yml,
17
+ # мерджит результаты и сохраняет в Resque.config
18
+ initializer 'resque-integration.config' do
19
+ paths = Rails.root.join('config', 'resque.yml'),
20
+ Rails.root.join('config', 'resque.local.yml')
21
+
22
+ Resque.config = Resque::Integration::Configuration.new(*paths.map(&:to_s))
23
+ end
24
+
25
+ # Читает конфиг-файл config/resque_schedule.yml
26
+ initializer 'resque-integration.schedule_config' do
27
+ if Resque.config.resque_scheduler? && Resque.config.schedule_exists?
28
+ config = ERB.new(File.read(Resque.config.schedule_file)).result
29
+ Resque.schedule = YAML.load(config)
30
+ end
31
+ end
32
+
33
+ # Устанавливает для Resque соединение с Редисом,
34
+ # данные берутся из конфига (см. выше)
35
+ initializer 'resque-integration.redis' do
36
+ redis = Resque.config.redis
37
+
38
+ if redis.any?
39
+ Resque.redis = Redis.new(redis)
40
+ Resque.redis.namespace = redis[:namespace] if redis[:namespace]
41
+ end
42
+ end
43
+
44
+ initializer 'resque-integration.logger' do
45
+ Resque.logger.level = Resque.config.log_level
46
+
47
+ case Resque.config.verbosity
48
+ when 1
49
+ Resque.logger.formatter = Resque::VerboseFormatter.new
50
+ when 2
51
+ Resque.logger.formatter = Resque::VeryVerboseFormatter.new
52
+ else
53
+ Resque.logger.formatter = Resque::QuietFormatter.new
54
+ end
55
+ end
56
+
57
+ # Конфигурирование плагина resque-failed-job-mailer.
58
+ # Данные берутся из конфига (см. выше)
59
+ initializer 'resque-integration.failure_notifier' do
60
+ notifier = Resque.config.failure_notifier
61
+
62
+ if notifier.enabled?
63
+ require 'resque_failed_job_mailer'
64
+
65
+ Resque::Failure::Notifier.configure do |config|
66
+ config.to = notifier.to
67
+ config.from = notifier.from
68
+ config.include_payload = notifier.include_payload?
69
+ config.mail = notifier.mail
70
+ config.mailer = notifier.mailer.constantize
71
+ end
72
+ end
73
+ end
74
+
75
+ # Глушим ошибки, по которым происходит автоматический перезапуск
76
+ initializer 'resque-integration.retrys' do
77
+ require 'resque/failure'
78
+ require 'resque/failure/redis'
79
+
80
+ Resque::Failure::MultipleWithRetrySuppression.classes = [
81
+ Resque::Failure::Redis,
82
+ Resque::Integration::FailureBackends::QueuesTotals
83
+ ]
84
+
85
+ if Resque.config.failure_notifier.enabled?
86
+ require 'resque_failed_job_mailer'
87
+
88
+ Resque::Failure::MultipleWithRetrySuppression.classes << Resque::Failure::Notifier
89
+ end
90
+
91
+ Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression
92
+ end
93
+
94
+ initializer "resque-integration.extensions" do
95
+ ::Resque::Worker.prepend ::Resque::Integration::Extensions::Worker
96
+ ::Resque::Job.singleton_class.prepend ::Resque::Integration::Extensions::Job
97
+ end
98
+
99
+ initializer 'resque-integration.application', before: :load_config_initializers do |app|
100
+ app.config.resque_job_status = {route_constraints: {domain: :current}, route_path: '/_job_'}
101
+ end
102
+ end # class Engine
103
+ end # module Resque::Integration
@@ -0,0 +1,17 @@
1
+ module Resque
2
+ module Integration
3
+ module Extensions
4
+ # Public: extension for proper determine queue
5
+ # when destroy job with priority
6
+ module Job
7
+ def destroy(queue, klass, *args)
8
+ if klass.respond_to?(:priority?) && klass.priority?
9
+ queue = klass.priority_queue(args.last)
10
+ end
11
+
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Resque
2
+ module Integration
3
+ module Extensions
4
+ module Worker
5
+ def queues
6
+ queues = super
7
+ shuffle? ? queues.shuffle : queues
8
+ end
9
+
10
+ def shuffle?
11
+ return @shuffle if defined?(@shuffle)
12
+ @shuffle = !ENV['SHUFFLE'].to_s.empty?
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Resque
2
+ module Integration
3
+ module Extensions
4
+ autoload :Worker, "resque/integration/extensions/worker"
5
+ autoload :Job, "resque/integration/extensions/job"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ module Resque
2
+ module Integration
3
+ module FailureBackends
4
+ class QueuesTotals < ::Resque::Failure::Base
5
+ REDIS_COUNTER_KEY = 'resque:integration:failure_backends:queues_totals'.freeze
6
+ MAX_COUNTER_VALUE = 10_000_000
7
+
8
+ private_constant :REDIS_COUNTER_KEY
9
+
10
+ def save
11
+ current_value = Resque.redis.hincrby(REDIS_COUNTER_KEY, queue, 1)
12
+ Resque.redis.hset(REDIS_COUNTER_KEY, queue, 1) if current_value >= MAX_COUNTER_VALUE
13
+ end
14
+
15
+ def self.queues
16
+ Resque.redis.hkeys(REDIS_COUNTER_KEY)
17
+ end
18
+
19
+ def self.count(queue = nil, _class_name = nil)
20
+ if queue.nil?
21
+ Resque.redis.hvals(REDIS_COUNTER_KEY).map(&:to_i).sum
22
+ else
23
+ Resque.redis.hget(REDIS_COUNTER_KEY, queue).to_i
24
+ end
25
+ end
26
+
27
+ def self.clear(queue = nil)
28
+ if queue.nil?
29
+ Resque.redis.del(REDIS_COUNTER_KEY)
30
+ else
31
+ Resque.redis.hdel(REDIS_COUNTER_KEY, queue)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Integration
3
+ module FailureBackends
4
+ autoload :QueuesTotals, 'resque/integration/failure_backends/queues_totals'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,99 @@
1
+ # THIS FILE IS AUTO-GENERATED. PLEASE DO NOT CHANGE IT!
2
+
3
+ bundle = '<%= `which bundle`.strip %>'
4
+ rails_root = '<%= root %>'
5
+ God.pid_file_directory = '<%= pids %>'
6
+ God.terminate_timeout = <%= terminate_timeout.to_i %>
7
+
8
+ <% workers.each do |worker| %>
9
+ <% worker.count.times do |i| %>
10
+ God.watch do |w|
11
+ w.dir = rails_root
12
+ w.name = 'resque-<%= worker.name || worker.queue %>-<%= i %>'
13
+ w.group = 'resque'
14
+ w.interval = 30.seconds
15
+ w.env = <%= Resque.config.env.merge(worker.env).merge(:RAILS_ENV => ::Rails.env, :BUNDLE_GEMFILE => "#{root}/Gemfile").stringify_keys! %>
16
+ w.start = "#{bundle} exec rake resque:work"
17
+ w.log = '<%= log_file %>'
18
+ w.stop_signal = 'QUIT'
19
+
20
+ <% if worker.stop_timeout %>
21
+ w.stop_timeout = <%= worker.stop_timeout %>
22
+ <% end %>
23
+
24
+ # determine the state on startup
25
+ w.transition(:init, { true => :up, false => :start }) do |on|
26
+ on.condition(:process_running) do |c|
27
+ c.running = true
28
+ end
29
+ end
30
+
31
+ # determine when process has finished starting
32
+ w.transition([:start, :restart], :up) do |on|
33
+ on.condition(:process_running) do |c|
34
+ c.running = true
35
+ c.interval = 5.seconds
36
+ end
37
+
38
+ # failsafe
39
+ on.condition(:tries) do |c|
40
+ c.times = 5
41
+ c.transition = :start
42
+ c.interval = 5.seconds
43
+ end
44
+ end
45
+
46
+ # start if process is not running
47
+ w.transition(:up, :start) do |on|
48
+ on.condition(:process_running) do |c|
49
+ c.running = false
50
+ end
51
+ end
52
+ # END OF WORKER CONFIGURATION
53
+ end
54
+ <% end %>
55
+ <% end %>
56
+
57
+ <% if resque_scheduler? %>
58
+ God.watch do |w|
59
+ w.dir = rails_root
60
+ w.name = 'resque-scheduler'
61
+ w.group = 'resque'
62
+ w.interval = 30.seconds
63
+ w.env = <%= Resque.config.env.merge(:RAILS_ENV => ::Rails.env, :BUNDLE_GEMFILE => "#{root}/Gemfile").stringify_keys! %>
64
+ w.start = "#{bundle} exec rake environment resque:scheduler"
65
+ w.log = '<%= log_file %>'
66
+ w.stop_signal = 'QUIT'
67
+ w.stop_timeout = 60.seconds
68
+
69
+ # determine the state on startup
70
+ w.transition(:init, { true => :up, false => :start }) do |on|
71
+ on.condition(:process_running) do |c|
72
+ c.running = true
73
+ end
74
+ end
75
+
76
+ # determine when process has finished starting
77
+ w.transition([:start, :restart], :up) do |on|
78
+ on.condition(:process_running) do |c|
79
+ c.running = true
80
+ c.interval = 5.seconds
81
+ end
82
+
83
+ # failsafe
84
+ on.condition(:tries) do |c|
85
+ c.times = 5
86
+ c.transition = :start
87
+ c.interval = 5.seconds
88
+ end
89
+ end
90
+
91
+ # start if process is not running
92
+ w.transition(:up, :start) do |on|
93
+ on.condition(:process_running) do |c|
94
+ c.running = false
95
+ end
96
+ end
97
+ # END OF SCHEDULE CONFIGURATION
98
+ end
99
+ <% end %>
@@ -0,0 +1,72 @@
1
+ # coding: utf-8
2
+
3
+ module Resque
4
+ module Integration
5
+ # Backport of resque-1.24.x hooks
6
+ # @see https://github.com/resque/resque/pull/680/files
7
+ module Hooks
8
+ # We do not want to patch Worker, so we declare callable Array
9
+ class CallableArray < Array
10
+ def call(*args)
11
+ each { |hook| hook.call(*args) }
12
+ end
13
+ end
14
+ # Call with a block to register a hook.
15
+ # Call with no arguments to return all registered hooks.
16
+ def before_first_fork(&block)
17
+ block ? register_hook(:before_first_fork, block) : hooks(:before_first_fork)
18
+ end
19
+
20
+ # Register a before_first_fork proc.
21
+ def before_first_fork=(block)
22
+ register_hook(:before_first_fork, block)
23
+ end
24
+
25
+ # Call with a block to register a hook.
26
+ # Call with no arguments to return all registered hooks.
27
+ def before_fork(&block)
28
+ block ? register_hook(:before_fork, block) : hooks(:before_fork)
29
+ end
30
+
31
+ # Register a before_fork proc.
32
+ def before_fork=(block)
33
+ register_hook(:before_fork, block)
34
+ end
35
+
36
+ # Call with a block to register a hook.
37
+ # Call with no arguments to return all registered hooks.
38
+ def after_fork(&block)
39
+ block ? register_hook(:after_fork, block) : hooks(:after_fork)
40
+ end
41
+
42
+ # Register an after_fork proc.
43
+ def after_fork=(block)
44
+ register_hook(:after_fork, block)
45
+ end
46
+
47
+ private
48
+
49
+ # Register a new proc as a hook. If the block is nil this is the
50
+ # equivalent of removing all hooks of the given name.
51
+ #
52
+ # `name` is the hook that the block should be registered with.
53
+ def register_hook(name, block)
54
+ return clear_hooks(name) if block.nil?
55
+
56
+ @hooks ||= {}
57
+ @hooks[name] ||= CallableArray.new
58
+ @hooks[name] << block
59
+ end
60
+
61
+ # Clear all hooks given a hook name.
62
+ def clear_hooks(name)
63
+ @hooks && @hooks[name] = CallableArray.new
64
+ end
65
+
66
+ # Retrieve all hooks of a given name.
67
+ def hooks(name)
68
+ (@hooks && @hooks[name]) || CallableArray.new
69
+ end
70
+ end # module Hooks
71
+ end # module Integration
72
+ end # module Resque