Skip to content

Commit 4d8e564

Browse files
panthomakosmrzor
authored andcommitted
Enable registering of multiple Resque hooks.
Calling `Resque.before_fork {}` would register a hook, but if any other gem, plugin or file also tried to set a before fork hook, the original would be replaced by the new hook. An example of this is the rpm_contrib gem which allows New Relic instrumentation of Resque jobs. The gem defines a `before_first_fork` hook and an `after_fork` hook. This results in conflicts if a user wants to define their own hooks of the same type. This fix allows multiple hooks to be registered and executes all of them instead of simply executing the last registered hook. I've consolidated the related tests into a single file and added some tests to ensure that the hooks are called, called at the right time, and that multiple hooks can be registered.
1 parent 4eabd83 commit 4d8e564

File tree

5 files changed

+171
-23
lines changed

5 files changed

+171
-23
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Gemfile.lock
22
doc/
33
test/dump.rdb
4+
test/dump-cluster.rdb

lib/resque.rb

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,41 +85,46 @@ def redis_id
8585
# changes you make will be permanent for the lifespan of the
8686
# worker.
8787
#
88-
# Call with a block to set the hook.
89-
# Call with no arguments to return the hook.
88+
# Call with a block to register a hook.
89+
# Call with no arguments to return all registered hooks.
9090
def before_first_fork(&block)
91-
block ? (@before_first_fork = block) : @before_first_fork
91+
block ? register_hook(:before_first_fork, block) : hooks(:before_first_fork)
9292
end
9393

94-
# Set a proc that will be called in the parent process before the
95-
# worker forks for the first time.
96-
attr_writer :before_first_fork
94+
# Register a before_first_fork proc.
95+
def before_first_fork=(block)
96+
register_hook(:before_first_fork, block)
97+
end
9798

9899
# The `before_fork` hook will be run in the **parent** process
99100
# before every job, so be careful- any changes you make will be
100101
# permanent for the lifespan of the worker.
101102
#
102-
# Call with a block to set the hook.
103-
# Call with no arguments to return the hook.
103+
# Call with a block to register a hook.
104+
# Call with no arguments to return all registered hooks.
104105
def before_fork(&block)
105-
block ? (@before_fork = block) : @before_fork
106+
block ? register_hook(:before_fork, block) : hooks(:before_fork)
106107
end
107108

108-
# Set the before_fork proc.
109-
attr_writer :before_fork
109+
# Register a before_fork proc.
110+
def before_fork=(block)
111+
register_hook(:before_fork, block)
112+
end
110113

111114
# The `after_fork` hook will be run in the child process and is passed
112115
# the current job. Any changes you make, therefore, will only live as
113116
# long as the job currently being processed.
114117
#
115-
# Call with a block to set the hook.
116-
# Call with no arguments to return the hook.
118+
# Call with a block to register a hook.
119+
# Call with no arguments to return all registered hooks.
117120
def after_fork(&block)
118-
block ? (@after_fork = block) : @after_fork
121+
block ? register_hook(:after_fork, block) : hooks(:after_fork)
119122
end
120123

121-
# Set the after_fork proc.
122-
attr_writer :after_fork
124+
# Register an after_fork proc.
125+
def after_fork=(block)
126+
register_hook(:after_fork, block)
127+
end
123128

124129
# The `before_pause` hook will be run in the parent process before the
125130
# worker has paused processing (via #pause_processing or SIGUSR2).
@@ -403,5 +408,29 @@ def keys
403408
key.sub("#{redis.namespace}:", '')
404409
end
405410
end
411+
412+
private
413+
414+
# Register a new proc as a hook. If the block is nil this is the
415+
# equivalent of removing all hooks of the given name.
416+
#
417+
# `name` is the hook that the block should be registered with.
418+
def register_hook(name, block)
419+
return clear_hooks(name) if block.nil?
420+
421+
@hooks ||= {}
422+
@hooks[name] ||= []
423+
@hooks[name] << block
424+
end
425+
426+
# Clear all hooks given a hook name.
427+
def clear_hooks(name)
428+
@hooks && @hooks[name] = []
429+
end
430+
431+
# Retrieve all hooks of a given name.
432+
def hooks(name)
433+
(@hooks && @hooks[name]) || []
434+
end
406435
end
407436

lib/resque/worker.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,14 @@ def register_worker
405405

406406
# Runs a named hook, passing along any arguments.
407407
def run_hook(name, *args)
408-
return unless hook = Resque.send(name)
409-
msg = "Running #{name} hook"
408+
return unless hooks = Resque.send(name)
409+
msg = "Running #{name} hooks"
410410
msg << " with #{args.inspect}" if args.any?
411411
log msg
412412

413-
args.any? ? hook.call(*args) : hook.call
413+
hooks.each do |hook|
414+
args.any? ? hook.call(*args) : hook.call
415+
end
414416
end
415417

416418
# Unregisters ourself as a worker. Useful when shutting down.

test/resque_hook_test.rb

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
require 'test_helper'
2+
3+
context "Resque Hooks" do
4+
setup do
5+
Resque.redis.flushall
6+
7+
Resque.before_first_fork = nil
8+
Resque.before_fork = nil
9+
Resque.after_fork = nil
10+
11+
@worker = Resque::Worker.new(:jobs)
12+
13+
$called = false
14+
15+
class CallNotifyJob
16+
def self.perform
17+
$called = true
18+
end
19+
end
20+
end
21+
22+
test 'retrieving hooks if none have been set' do
23+
assert_equal [], Resque.before_first_fork
24+
assert_equal [], Resque.before_fork
25+
assert_equal [], Resque.after_fork
26+
end
27+
28+
test 'it calls before_first_fork once' do
29+
counter = 0
30+
31+
Resque.before_first_fork { counter += 1 }
32+
2.times { Resque::Job.create(:jobs, CallNotifyJob) }
33+
34+
assert_equal(0, counter)
35+
@worker.work(0)
36+
assert_equal(1, counter)
37+
end
38+
39+
test 'it calls before_fork before each job' do
40+
counter = 0
41+
42+
Resque.before_fork { counter += 1 }
43+
2.times { Resque::Job.create(:jobs, CallNotifyJob) }
44+
45+
assert_equal(0, counter)
46+
@worker.work(0)
47+
assert_equal(2, counter)
48+
end
49+
50+
test 'it calls after_fork after each job' do
51+
counter = 0
52+
53+
Resque.after_fork { counter += 1 }
54+
2.times { Resque::Job.create(:jobs, CallNotifyJob) }
55+
56+
assert_equal(0, counter)
57+
@worker.work(0)
58+
assert_equal(2, counter)
59+
end
60+
61+
test 'it calls before_first_fork before forking' do
62+
Resque.before_first_fork { assert(!$called) }
63+
64+
Resque::Job.create(:jobs, CallNotifyJob)
65+
@worker.work(0)
66+
end
67+
68+
test 'it calls before_fork before forking' do
69+
Resque.before_fork { assert(!$called) }
70+
71+
Resque::Job.create(:jobs, CallNotifyJob)
72+
@worker.work(0)
73+
end
74+
75+
test 'it calls after_fork after forking' do
76+
Resque.after_fork { assert($called) }
77+
78+
Resque::Job.create(:jobs, CallNotifyJob)
79+
@worker.work(0)
80+
end
81+
82+
test 'it registeres multiple before_first_forks' do
83+
first = false
84+
second = false
85+
86+
Resque.before_first_fork { first = true }
87+
Resque.before_first_fork { second = true }
88+
Resque::Job.create(:jobs, CallNotifyJob)
89+
90+
assert(!first && !second)
91+
@worker.work(0)
92+
assert(first && second)
93+
end
94+
95+
test 'it registers multiple before_forks' do
96+
first = false
97+
second = false
98+
99+
Resque.before_fork { first = true }
100+
Resque.before_fork { second = true }
101+
Resque::Job.create(:jobs, CallNotifyJob)
102+
103+
assert(!first && !second)
104+
@worker.work(0)
105+
assert(first && second)
106+
end
107+
108+
test 'it registers multiple after_forks' do
109+
first = false
110+
second = false
111+
112+
Resque.after_fork { first = true }
113+
Resque.after_fork { second = true }
114+
Resque::Job.create(:jobs, CallNotifyJob)
115+
116+
assert(!first && !second)
117+
@worker.work(0)
118+
assert(first && second)
119+
end
120+
end

test/worker_test.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
Resque.redis = Resque.redis # reset state in Resque object
66
Resque.redis.flushall
77

8-
Resque.before_first_fork = nil
9-
Resque.before_fork = nil
10-
Resque.after_fork = nil
11-
128
@worker = Resque::Worker.new(:jobs)
139
Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
1410
end

0 commit comments

Comments
 (0)