resque-integration 3.4.1

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