resque-multi-job-forks 0.4.4 → 0.4.5
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.
- checksums.yaml +5 -5
- data/lib/resque-multi-job-forks.rb +26 -10
- data/test/helper.rb +8 -32
- data/test/test_resque-multi-job-forks.rb +85 -21
- data/test/test_rss_reader.rb +5 -2
- metadata +23 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e14fad0b41eaab14a736ac76d9e234314c97279b190d90ab935a74717bc18921
|
|
4
|
+
data.tar.gz: fc1a9a67f6a6825180044316144b70923f771d454e5c6338c86ccd22aecb3fb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2da4375dc2b26da0c670e402954c0b5496a6ce46c3100eabe4f9991d50cd987b0c8d4e5745fdb72f1632f35934f112eb42df6a141c7d838904ed12419f0e8a7d
|
|
7
|
+
data.tar.gz: cc8c72fa05245eae3aa8a19811b54279ee00f6257fb8093942c9fd7c8d62336fb4ce1e7242de96e997772baff15486838cd3418ca0436d21ec38dd6f4cb4a3fe
|
|
@@ -15,7 +15,16 @@ module Resque
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
if multi_jobs_per_fork? && !method_defined?(:shutdown_without_multi_job_forks)
|
|
18
|
+
def work_with_multi_job_forks(*args)
|
|
19
|
+
pid # forces @pid to be set in the parent
|
|
20
|
+
work_without_multi_job_forks(*args)
|
|
21
|
+
release_and_exit! unless is_parent_process?
|
|
22
|
+
end
|
|
23
|
+
alias_method :work_without_multi_job_forks, :work
|
|
24
|
+
alias_method :work, :work_with_multi_job_forks
|
|
25
|
+
|
|
18
26
|
def perform_with_multi_job_forks(job = nil)
|
|
27
|
+
trap('QUIT') { shutdown } unless fork_hijacked?
|
|
19
28
|
perform_without_multi_job_forks(job)
|
|
20
29
|
hijack_fork unless fork_hijacked?
|
|
21
30
|
@jobs_processed += 1
|
|
@@ -31,15 +40,15 @@ module Resque
|
|
|
31
40
|
alias_method :shutdown?, :shutdown_with_multi_job_forks?
|
|
32
41
|
|
|
33
42
|
def shutdown_with_multi_job_forks
|
|
34
|
-
shutdown_without_multi_job_forks
|
|
35
43
|
shutdown_child if is_parent_process?
|
|
44
|
+
shutdown_without_multi_job_forks
|
|
36
45
|
end
|
|
37
46
|
alias_method :shutdown_without_multi_job_forks, :shutdown
|
|
38
47
|
alias_method :shutdown, :shutdown_with_multi_job_forks
|
|
39
48
|
|
|
40
49
|
def pause_processing_with_multi_job_forks
|
|
41
|
-
pause_processing_without_multi_job_forks
|
|
42
50
|
shutdown_child if is_parent_process?
|
|
51
|
+
pause_processing_without_multi_job_forks
|
|
43
52
|
end
|
|
44
53
|
alias_method :pause_processing_without_multi_job_forks, :pause_processing
|
|
45
54
|
alias_method :pause_processing, :pause_processing_with_multi_job_forks
|
|
@@ -49,7 +58,7 @@ module Resque
|
|
|
49
58
|
working_on_without_worker_registration(job)
|
|
50
59
|
end
|
|
51
60
|
alias_method :working_on_without_worker_registration, :working_on
|
|
52
|
-
alias_method :working_on, :working_on_with_worker_registration
|
|
61
|
+
alias_method :working_on, :working_on_with_worker_registration
|
|
53
62
|
|
|
54
63
|
# Reconnect only once
|
|
55
64
|
def reconnect_with_multi_job_forks
|
|
@@ -62,18 +71,25 @@ module Resque
|
|
|
62
71
|
alias_method :reconnect, :reconnect_with_multi_job_forks
|
|
63
72
|
end
|
|
64
73
|
|
|
65
|
-
# Need to tell the child to shutdown since it might be looping performing
|
|
66
|
-
#
|
|
74
|
+
# Need to tell the child to shutdown since it might be looping performing
|
|
75
|
+
# multiple jobs per fork. The QUIT signal normally does a graceful shutdown,
|
|
76
|
+
# and is re-registered in children (term_child normally unregisters it).
|
|
67
77
|
def shutdown_child
|
|
68
78
|
begin
|
|
69
|
-
|
|
79
|
+
log! "multi_jobs_per_fork: Sending QUIT signal to #{@child}"
|
|
80
|
+
Process.kill('QUIT', @child)
|
|
70
81
|
rescue Errno::ESRCH
|
|
71
82
|
nil
|
|
72
83
|
end
|
|
73
84
|
end
|
|
74
85
|
|
|
75
86
|
def is_parent_process?
|
|
76
|
-
@child
|
|
87
|
+
@child || @pid == Process.pid
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def release_and_exit!
|
|
91
|
+
release_fork if fork_hijacked?
|
|
92
|
+
run_at_exit_hooks ? exit : exit!(true)
|
|
77
93
|
end
|
|
78
94
|
|
|
79
95
|
def fork_hijacked?
|
|
@@ -87,7 +103,7 @@ module Resque
|
|
|
87
103
|
@release_fork_limit = fork_job_limit
|
|
88
104
|
@jobs_processed = 0
|
|
89
105
|
@cant_fork = true
|
|
90
|
-
trap('
|
|
106
|
+
trap('QUIT') { shutdown }
|
|
91
107
|
end
|
|
92
108
|
|
|
93
109
|
def release_fork
|
|
@@ -100,7 +116,7 @@ module Resque
|
|
|
100
116
|
end
|
|
101
117
|
|
|
102
118
|
def fork_job_limit
|
|
103
|
-
jobs_per_fork.nil? ? Time.now.
|
|
119
|
+
jobs_per_fork.nil? ? Time.now.to_f + seconds_per_fork : jobs_per_fork
|
|
104
120
|
end
|
|
105
121
|
|
|
106
122
|
def fork_job_limit_reached?
|
|
@@ -108,7 +124,7 @@ module Resque
|
|
|
108
124
|
end
|
|
109
125
|
|
|
110
126
|
def fork_job_limit_remaining
|
|
111
|
-
jobs_per_fork.nil? ? @release_fork_limit - Time.now.
|
|
127
|
+
jobs_per_fork.nil? ? @release_fork_limit - Time.now.to_f : jobs_per_fork - @jobs_processed
|
|
112
128
|
end
|
|
113
129
|
|
|
114
130
|
def seconds_per_fork
|
data/test/helper.rb
CHANGED
|
@@ -12,72 +12,48 @@ require 'resque-multi-job-forks'
|
|
|
12
12
|
require 'timeout'
|
|
13
13
|
|
|
14
14
|
# setup redis & resque.
|
|
15
|
-
redis = Redis.new(:db => 1)
|
|
16
|
-
Resque.redis = redis
|
|
15
|
+
$redis = Redis.new(:db => 1)
|
|
16
|
+
Resque.redis = $redis
|
|
17
17
|
|
|
18
18
|
# adds simple STDOUT logging to test workers.
|
|
19
19
|
# set `VERBOSE=true` when running the tests to view resques log output.
|
|
20
20
|
module Resque
|
|
21
21
|
class Worker
|
|
22
|
+
|
|
22
23
|
def log(msg)
|
|
23
24
|
puts "*** #{msg}" unless ENV['VERBOSE'].nil?
|
|
24
25
|
end
|
|
25
26
|
alias_method :log!, :log
|
|
26
27
|
|
|
27
|
-
# Processes a given job in the child.
|
|
28
|
-
def perform_without_multi_job_forks(job)
|
|
29
|
-
begin
|
|
30
|
-
# 'will_fork?' returns false in test mode, but since we need to test
|
|
31
|
-
# if the :after_fork hook runs, we ignore 'will_fork?' here.
|
|
32
|
-
run_hook :after_fork, job # if will_fork?
|
|
33
|
-
job.perform
|
|
34
|
-
rescue Object => e
|
|
35
|
-
log "#{job.inspect} failed: #{e.inspect}"
|
|
36
|
-
begin
|
|
37
|
-
job.fail(e)
|
|
38
|
-
rescue Object => e
|
|
39
|
-
log "Received exception when reporting failure: #{e.inspect}"
|
|
40
|
-
end
|
|
41
|
-
failed!
|
|
42
|
-
else
|
|
43
|
-
log "done: #{job.inspect}"
|
|
44
|
-
ensure
|
|
45
|
-
yield job if block_given?
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
28
|
end
|
|
49
29
|
end
|
|
50
30
|
|
|
51
|
-
# stores a record of the job processing sequence.
|
|
52
|
-
# you may wish to reset this in the test `setup` method.
|
|
53
|
-
$SEQUENCE = []
|
|
54
|
-
|
|
55
31
|
# test job, tracks sequence.
|
|
56
32
|
class SequenceJob
|
|
57
33
|
@queue = :jobs
|
|
58
34
|
def self.perform(i)
|
|
59
|
-
$SEQUENCE << "work_#{i}".to_sym
|
|
60
35
|
sleep(2)
|
|
36
|
+
$SEQ_WRITER.print "work_#{i}\n"
|
|
61
37
|
end
|
|
62
38
|
end
|
|
63
39
|
|
|
64
40
|
class QuickSequenceJob
|
|
65
41
|
@queue = :jobs
|
|
66
42
|
def self.perform(i)
|
|
67
|
-
$
|
|
43
|
+
$SEQ_WRITER.print "work_#{i}\n"
|
|
68
44
|
end
|
|
69
45
|
end
|
|
70
46
|
|
|
71
47
|
|
|
72
48
|
# test hooks, tracks sequence.
|
|
73
49
|
Resque.after_fork do
|
|
74
|
-
$
|
|
50
|
+
$SEQ_WRITER.print "after_fork\n"
|
|
75
51
|
end
|
|
76
52
|
|
|
77
53
|
Resque.before_fork do
|
|
78
|
-
$
|
|
54
|
+
$SEQ_WRITER.print "before_fork\n"
|
|
79
55
|
end
|
|
80
56
|
|
|
81
57
|
Resque.before_child_exit do |worker|
|
|
82
|
-
$
|
|
58
|
+
$SEQ_WRITER.print "before_child_exit_#{worker.jobs_processed}\n"
|
|
83
59
|
end
|
|
@@ -2,8 +2,8 @@ require File.join(File.expand_path(File.dirname(__FILE__)), '/helper')
|
|
|
2
2
|
|
|
3
3
|
class TestResqueMultiJobForks < Test::Unit::TestCase
|
|
4
4
|
def setup
|
|
5
|
-
$
|
|
6
|
-
|
|
5
|
+
$SEQ_READER, $SEQ_WRITER = IO.pipe
|
|
6
|
+
$redis.flushdb
|
|
7
7
|
@worker = Resque::Worker.new(:jobs)
|
|
8
8
|
end
|
|
9
9
|
|
|
@@ -16,39 +16,103 @@ class TestResqueMultiJobForks < Test::Unit::TestCase
|
|
|
16
16
|
Resque.enqueue(SequenceJob, 3)
|
|
17
17
|
Resque.enqueue(SequenceJob, 4)
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
19
|
+
# INTERVAL=0 will exit when no more jobs left
|
|
20
|
+
@worker.work(0)
|
|
21
|
+
$SEQ_WRITER.close
|
|
22
|
+
sequence = $SEQ_READER.each_line.map {|l| l.strip.to_sym }
|
|
24
23
|
|
|
25
24
|
# test the sequence is correct.
|
|
26
25
|
assert_equal([:before_fork, :after_fork, :work_1, :work_2, :work_3,
|
|
27
26
|
:before_child_exit_3, :before_fork, :after_fork, :work_4,
|
|
28
|
-
:before_child_exit_1],
|
|
27
|
+
:before_child_exit_1], sequence, 'correct sequence')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_graceful_shutdown_during_first_job
|
|
31
|
+
# enough time for all jobs to process.
|
|
32
|
+
@worker.seconds_per_fork = 60
|
|
33
|
+
|
|
34
|
+
Resque.enqueue(SequenceJob, 1)
|
|
35
|
+
Resque.enqueue(SequenceJob, 2)
|
|
36
|
+
t = Thread.new do
|
|
37
|
+
sleep 1 # before first job can complete
|
|
38
|
+
@worker.shutdown
|
|
39
|
+
end
|
|
40
|
+
# INTERVAL=0 will exit when no more jobs left
|
|
41
|
+
@worker.work(0)
|
|
42
|
+
$SEQ_WRITER.close
|
|
43
|
+
sequence = $SEQ_READER.each_line.map {|l| l.strip.to_sym }
|
|
44
|
+
|
|
45
|
+
# test the sequence is correct.
|
|
46
|
+
assert_equal([:before_fork, :after_fork, :work_1,
|
|
47
|
+
:before_child_exit_1], sequence, 'correct sequence')
|
|
48
|
+
t.join
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_immediate_shutdown_during_first_job
|
|
52
|
+
# enough time for all jobs to process.
|
|
53
|
+
@worker.seconds_per_fork = 60
|
|
54
|
+
@worker.term_child = false
|
|
55
|
+
|
|
56
|
+
Resque.enqueue(SequenceJob, 1)
|
|
57
|
+
Resque.enqueue(SequenceJob, 2)
|
|
58
|
+
t = Thread.new do
|
|
59
|
+
sleep 0.5 # before first job can complete
|
|
60
|
+
Process.kill("INT", @worker.pid) # triggers shutdown! in main thread
|
|
61
|
+
end
|
|
62
|
+
# INTERVAL=0 will exit when no more jobs left
|
|
63
|
+
@worker.work(0)
|
|
64
|
+
$SEQ_WRITER.close
|
|
65
|
+
sequence = $SEQ_READER.each_line.map {|l| l.strip.to_sym }
|
|
66
|
+
|
|
67
|
+
# test the sequence is correct.
|
|
68
|
+
assert_equal([:before_fork, :after_fork], sequence, 'correct sequence')
|
|
69
|
+
t.join
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_sigterm_shutdown_during_first_job
|
|
73
|
+
# enough time for all jobs to process.
|
|
74
|
+
@worker.seconds_per_fork = 60
|
|
75
|
+
@worker.term_child = true
|
|
76
|
+
@worker.term_timeout = 0.5
|
|
77
|
+
|
|
78
|
+
Resque.enqueue(SequenceJob, 1)
|
|
79
|
+
Resque.enqueue(SequenceJob, 2)
|
|
80
|
+
t = Thread.new do
|
|
81
|
+
sleep 1.0 # before first job can complete
|
|
82
|
+
Process.kill("INT", @worker.pid) # triggers shutdown! in main thread
|
|
83
|
+
end
|
|
84
|
+
# INTERVAL=0 will exit when no more jobs left
|
|
85
|
+
@worker.work(0)
|
|
86
|
+
$SEQ_WRITER.close
|
|
87
|
+
sequence = $SEQ_READER.each_line.map {|l| l.strip.to_sym }
|
|
88
|
+
|
|
89
|
+
# test the sequence is correct.
|
|
90
|
+
assert_equal([:before_fork, :after_fork,
|
|
91
|
+
:before_child_exit_1], sequence, 'correct sequence')
|
|
92
|
+
t.join
|
|
29
93
|
end
|
|
30
94
|
|
|
31
95
|
# test we can also limit fork job process by a job limit.
|
|
32
96
|
def test_job_limit_sequence_of_events
|
|
33
|
-
# only allow
|
|
97
|
+
# only allow 20 jobs per fork
|
|
34
98
|
ENV['JOBS_PER_FORK'] = '20'
|
|
35
99
|
|
|
36
100
|
# queue 40 jobs.
|
|
37
101
|
(1..40).each { |i| Resque.enqueue(QuickSequenceJob, i) }
|
|
38
102
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
103
|
+
# INTERVAL=0 will exit when no more jobs left
|
|
104
|
+
@worker.work(0)
|
|
105
|
+
$SEQ_WRITER.close
|
|
106
|
+
sequence = $SEQ_READER.each_line.map {|l| l.strip.to_sym }
|
|
43
107
|
|
|
44
|
-
assert_equal :before_fork,
|
|
45
|
-
assert_equal :after_fork,
|
|
46
|
-
assert_equal :work_20,
|
|
47
|
-
assert_equal :before_child_exit_20,
|
|
48
|
-
assert_equal :before_fork,
|
|
49
|
-
assert_equal :after_fork,
|
|
50
|
-
assert_equal :work_40,
|
|
51
|
-
assert_equal :before_child_exit_20,
|
|
108
|
+
assert_equal :before_fork, sequence[0], 'first before_fork call.'
|
|
109
|
+
assert_equal :after_fork, sequence[1], 'first after_fork call.'
|
|
110
|
+
assert_equal :work_20, sequence[21], '20th chunk of work.'
|
|
111
|
+
assert_equal :before_child_exit_20, sequence[22], 'first before_child_exit call.'
|
|
112
|
+
assert_equal :before_fork, sequence[23], 'final before_fork call.'
|
|
113
|
+
assert_equal :after_fork, sequence[24], 'final after_fork call.'
|
|
114
|
+
assert_equal :work_40, sequence[44], '40th chunk of work.'
|
|
115
|
+
assert_equal :before_child_exit_20, sequence[45], 'final before_child_exit call.'
|
|
52
116
|
end
|
|
53
117
|
|
|
54
118
|
def teardown
|
data/test/test_rss_reader.rb
CHANGED
|
@@ -7,17 +7,20 @@ class TestRssReader < Test::Unit::TestCase
|
|
|
7
7
|
@object.extend Resque::Plugins::MultiJobForks::RssReader
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# support before/after ruby 2.4 without deprecation notice
|
|
11
|
+
IntegerClass = 1.class
|
|
12
|
+
|
|
10
13
|
def test_current_process_rss
|
|
11
14
|
rss = @object.rss
|
|
12
15
|
# not a very strict test, but have you ever seen a ruby process < 1Mb?
|
|
13
16
|
# the "real" test is manual verification via top/ps/htop
|
|
14
|
-
assert_equal
|
|
17
|
+
assert_equal IntegerClass, rss.class
|
|
15
18
|
assert @object.rss > 1000
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def test_rss_other_processes
|
|
19
22
|
rss = @object.rss(1) # init is guaranteed to exist
|
|
20
|
-
assert_equal
|
|
23
|
+
assert_equal IntegerClass, rss.class
|
|
21
24
|
assert @object.rss > 1000
|
|
22
25
|
end
|
|
23
26
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-multi-job-forks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mick Staugaard
|
|
@@ -10,28 +10,22 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 2019-02-10 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: resque
|
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
|
18
18
|
requirements:
|
|
19
|
-
- - "
|
|
20
|
-
- !ruby/object:Gem::Version
|
|
21
|
-
version: '1.24'
|
|
22
|
-
- - "<"
|
|
19
|
+
- - "~>"
|
|
23
20
|
- !ruby/object:Gem::Version
|
|
24
|
-
version:
|
|
21
|
+
version: 1.26.0
|
|
25
22
|
type: :runtime
|
|
26
23
|
prerelease: false
|
|
27
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
28
25
|
requirements:
|
|
29
|
-
- - "
|
|
26
|
+
- - "~>"
|
|
30
27
|
- !ruby/object:Gem::Version
|
|
31
|
-
version:
|
|
32
|
-
- - "<"
|
|
33
|
-
- !ruby/object:Gem::Version
|
|
34
|
-
version: '1.27'
|
|
28
|
+
version: 1.26.0
|
|
35
29
|
- !ruby/object:Gem::Dependency
|
|
36
30
|
name: json
|
|
37
31
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -74,6 +68,20 @@ dependencies:
|
|
|
74
68
|
- - ">="
|
|
75
69
|
- !ruby/object:Gem::Version
|
|
76
70
|
version: '0'
|
|
71
|
+
- !ruby/object:Gem::Dependency
|
|
72
|
+
name: rake
|
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
type: :development
|
|
79
|
+
prerelease: false
|
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
77
85
|
description: When your resque jobs are frequent and fast, the overhead of forking
|
|
78
86
|
and running your after_fork might get too big.
|
|
79
87
|
email:
|
|
@@ -89,7 +97,7 @@ files:
|
|
|
89
97
|
- test/helper.rb
|
|
90
98
|
- test/test_resque-multi-job-forks.rb
|
|
91
99
|
- test/test_rss_reader.rb
|
|
92
|
-
homepage:
|
|
100
|
+
homepage: https://github.com/stulentsev/resque-multi-job-forks
|
|
93
101
|
licenses: []
|
|
94
102
|
metadata: {}
|
|
95
103
|
post_install_message:
|
|
@@ -107,12 +115,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
107
115
|
- !ruby/object:Gem::Version
|
|
108
116
|
version: '0'
|
|
109
117
|
requirements: []
|
|
110
|
-
|
|
111
|
-
rubygems_version: 2.6.13
|
|
118
|
+
rubygems_version: 3.0.1
|
|
112
119
|
signing_key:
|
|
113
120
|
specification_version: 4
|
|
114
121
|
summary: Have your resque workers process more that one job
|
|
115
122
|
test_files:
|
|
116
|
-
- test/test_resque-multi-job-forks.rb
|
|
117
123
|
- test/helper.rb
|
|
124
|
+
- test/test_resque-multi-job-forks.rb
|
|
118
125
|
- test/test_rss_reader.rb
|