lantins-resque-multi-job-forks 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Mick Staugaard
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,30 @@
1
+ = resque-multi-job-forks
2
+
3
+ If you have very frequent and fast resque jobs, the overhead of forking and running your after_fork hook, might get too big. Using this resque plugin, you can have your workers perform more than one job, before terminating.
4
+
5
+ You simply specify the number of minutes you want each fork to run using the MINUTES_PER_FORK environment variable:
6
+
7
+ QUEUE=* MINUTES_PER_FORK=5 rake resque:work
8
+
9
+ This will have each fork process jobs for 5 minutes, before terminating.
10
+
11
+ This plugin also defines a new hook, that gets called right before the fork terminates:
12
+
13
+ Resque.before_child_exit do |worker|
14
+ worker.log("#{worker.jobs_processed} were processed in this fork")
15
+ end
16
+
17
+
18
+ == Note on Patches/Pull Requests
19
+
20
+ * Fork the project.
21
+ * Make your feature addition or bug fix.
22
+ * Add tests for it. This is important so I don't break it in a
23
+ future version unintentionally.
24
+ * Commit, do not mess with rakefile, version, or history.
25
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
26
+ * Send me a pull request. Bonus points for topic branches.
27
+
28
+ == Copyright
29
+
30
+ Copyright (c) 2010 Mick Staugaard. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+
3
+ require 'fileutils'
4
+ require 'rake/testtask'
5
+ require 'yard/rake/yardoc_task'
6
+
7
+ task :default => :test
8
+
9
+ # Test task.
10
+ Rake::TestTask.new(:test) do |task|
11
+ task.test_files = FileList['test/test_*.rb']
12
+ task.verbose = true
13
+ end
14
+
15
+ # docs task.
16
+ YARD::Rake::YardocTask.new :yardoc do |t|
17
+ t.files = ['lib/**/*.rb']
18
+ t.options = ['--output-dir', "doc/",
19
+ '--files', 'LICENSE',
20
+ '--readme', 'README.rdoc',
21
+ '--title', 'lantins-resque-multi-job-forks documentation']
22
+ end
@@ -0,0 +1,87 @@
1
+ require 'resque'
2
+ require 'resque/worker'
3
+
4
+ module Resque
5
+ class Worker
6
+ attr_accessor :seconds_per_fork
7
+ attr_accessor :jobs_per_fork
8
+ attr_reader :jobs_processed
9
+
10
+ unless method_defined?(:shutdown_without_multi_job_forks)
11
+ def perform_with_multi_job_forks(job = nil)
12
+ perform_without_multi_job_forks(job)
13
+ hijack_fork unless fork_hijacked?
14
+ @jobs_processed += 1
15
+ end
16
+ alias_method :perform_without_multi_job_forks, :perform
17
+ alias_method :perform, :perform_with_multi_job_forks
18
+
19
+ def shutdown_with_multi_job_forks
20
+ release_fork if fork_hijacked? && fork_job_limit_reached?
21
+ shutdown_without_multi_job_forks
22
+ end
23
+ alias_method :shutdown_without_multi_job_forks, :shutdown?
24
+ alias_method :shutdown?, :shutdown_with_multi_job_forks
25
+ end
26
+
27
+ def fork_hijacked?
28
+ @release_fork_limit
29
+ end
30
+
31
+ def hijack_fork
32
+ log 'hijack fork.'
33
+ @suppressed_fork_hooks = [Resque.after_fork, Resque.before_fork]
34
+ Resque.after_fork = Resque.before_fork = nil
35
+ @release_fork_limit = fork_job_limit
36
+ @jobs_processed = 0
37
+ @cant_fork = true
38
+ end
39
+
40
+ def release_fork
41
+ log "jobs processed by child: #{jobs_processed}"
42
+ run_hook :before_child_exit, self
43
+ Resque.after_fork, Resque.before_fork = *@suppressed_fork_hooks
44
+ @release_fork_limit = @jobs_processed = @cant_fork = nil
45
+ log 'hijack over, counter terrorists win.'
46
+ end
47
+
48
+ def fork_job_limit
49
+ jobs_per_fork.nil? ? Time.now.to_i + seconds_per_fork : jobs_per_fork
50
+ end
51
+
52
+ def fork_job_limit_reached?
53
+ fork_job_limit_remaining <= 0 ? true : false
54
+ end
55
+
56
+ def fork_job_limit_remaining
57
+ jobs_per_fork.nil? ? @release_fork_limit - Time.now.to_i : jobs_per_fork - @jobs_processed
58
+ end
59
+
60
+ def seconds_per_fork
61
+ @seconds_per_fork ||= minutes_per_fork * 60
62
+ end
63
+
64
+ def minutes_per_fork
65
+ ENV['MINUTES_PER_FORK'].nil? ? 1 : ENV['MINUTES_PER_FORK'].to_i
66
+ end
67
+
68
+ def jobs_per_fork
69
+ @jobs_per_fork ||= ENV['JOBS_PER_FORK'].nil? ? nil : ENV['JOBS_PER_FORK'].to_i
70
+ end
71
+ end
72
+
73
+ # the `before_child_exit` hook will run in the child process
74
+ # right before the child process terminates
75
+ #
76
+ # Call with a block to set the hook.
77
+ # Call with no arguments to return the hook.
78
+ def self.before_child_exit(&block)
79
+ block ? (@before_child_exit = block) : @before_child_exit
80
+ end
81
+
82
+ # Set the before_child_exit proc.
83
+ def self.before_child_exit=(before_child_exit)
84
+ @before_child_exit = before_child_exit
85
+ end
86
+
87
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,57 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + '/../lib'
3
+
4
+ $TESTING = true
5
+
6
+ require 'rubygems'
7
+ require 'test/unit'
8
+ require 'resque-multi-job-forks'
9
+
10
+ # setup redis & resque.
11
+ redis = Redis.new(:db => 1)
12
+ Resque.redis = redis
13
+
14
+ # adds simple STDOUT logging to test workers.
15
+ # set `VERBOSE=true` when running the tests to view resques log output.
16
+ module Resque
17
+ class Worker
18
+ def log(msg)
19
+ puts "*** #{msg}" unless ENV['VERBOSE'].nil?
20
+ end
21
+ alias_method :log!, :log
22
+ end
23
+ end
24
+
25
+ # stores a record of the job processing sequence.
26
+ # you may wish to reset this in the test `setup` method.
27
+ $SEQUENCE = []
28
+
29
+ # test job, tracks sequence.
30
+ class SequenceJob
31
+ @queue = :jobs
32
+ def self.perform(i)
33
+ $SEQUENCE << "work_#{i}".to_sym
34
+ sleep(2)
35
+ end
36
+ end
37
+
38
+ class QuickSequenceJob
39
+ @queue = :jobs
40
+ def self.perform(i)
41
+ $SEQUENCE << "work_#{i}".to_sym
42
+ end
43
+ end
44
+
45
+
46
+ # test hooks, tracks sequence.
47
+ Resque.after_fork do
48
+ $SEQUENCE << :after_fork
49
+ end
50
+
51
+ Resque.before_fork do
52
+ $SEQUENCE << :before_fork
53
+ end
54
+
55
+ Resque.before_child_exit do |worker|
56
+ $SEQUENCE << "before_child_exit_#{worker.jobs_processed}".to_sym
57
+ end
@@ -0,0 +1,58 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestResqueMultiJobForks < Test::Unit::TestCase
4
+ def setup
5
+ $SEQUENCE = []
6
+ Resque.redis.flushdb
7
+ @worker = Resque::Worker.new(:jobs)
8
+ end
9
+
10
+ def test_timeout_limit_sequence_of_events
11
+ # only allow enough time for 3 jobs to process.
12
+ @worker.seconds_per_fork = 3
13
+
14
+ Resque.enqueue(SequenceJob, 1)
15
+ Resque.enqueue(SequenceJob, 2)
16
+ Resque.enqueue(SequenceJob, 3)
17
+ Resque.enqueue(SequenceJob, 4)
18
+
19
+ # make sure we don't take longer then 15 seconds.
20
+ begin
21
+ Timeout::timeout(15) { @worker.work(1) }
22
+ rescue Timeout::Error
23
+ end
24
+
25
+ # test the sequence is correct.
26
+ assert_equal([:before_fork, :after_fork, :work_1, :work_2, :work_3,
27
+ :before_child_exit_3, :before_fork, :after_fork, :work_4,
28
+ :before_child_exit_1], $SEQUENCE, 'correct sequence')
29
+ end
30
+
31
+ # test we can also limit fork job process by a job limit.
32
+ def test_job_limit_sequence_of_events
33
+ # only allow enough time for 3 jobs to process.
34
+ ENV['JOBS_PER_FORK'] = '20'
35
+
36
+ # queue 40 jobs.
37
+ (1..40).each { |i| Resque.enqueue(QuickSequenceJob, i) }
38
+
39
+ begin
40
+ Timeout::timeout(3) { @worker.work(1) }
41
+ rescue Timeout::Error
42
+ end
43
+
44
+ assert_equal :before_fork, $SEQUENCE[0], 'first before_fork call.'
45
+ assert_equal :after_fork, $SEQUENCE[1], 'first after_fork call.'
46
+ assert_equal :work_20, $SEQUENCE[21], '20th chunk of work.'
47
+ assert_equal :before_child_exit_20, $SEQUENCE[22], 'first before_child_exit call.'
48
+ assert_equal :before_fork, $SEQUENCE[23], 'final before_fork call.'
49
+ assert_equal :after_fork, $SEQUENCE[24], 'final after_fork call.'
50
+ assert_equal :work_40, $SEQUENCE[44], '40th chunk of work.'
51
+ assert_equal :before_child_exit_20, $SEQUENCE[45], 'final before_child_exit call.'
52
+ end
53
+
54
+ def teardown
55
+ # make sure we don't clobber any other tests.
56
+ ENV['JOBS_PER_FORK'] = nil
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lantins-resque-multi-job-forks
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
+ platform: ruby
12
+ authors:
13
+ - Mick Staugaard
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-15 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: resque
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 39
30
+ segments:
31
+ - 1
32
+ - 9
33
+ - 10
34
+ version: 1.9.10
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: test-unit
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: simplecov
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 19
60
+ segments:
61
+ - 0
62
+ - 3
63
+ - 0
64
+ version: 0.3.0
65
+ type: :development
66
+ version_requirements: *id003
67
+ description: " When your resque jobs are frequent and fast, the overhead of forking and\n running your after_fork might get too big.\n\n You may limit by either job limit or time limit per fork.\n"
68
+ email: mick@staugaard.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files: []
74
+
75
+ files:
76
+ - LICENSE
77
+ - Rakefile
78
+ - README.rdoc
79
+ - test/helper.rb
80
+ - test/test_resque-multi-job-forks.rb
81
+ - lib/resque-multi-job-forks.rb
82
+ has_rdoc: false
83
+ homepage: http://github.com/lantins/lantins-resque-multi-job-forks
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.3.7
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: A Resque plugin; allows workers to process multiple jobs per fork.
116
+ test_files: []
117
+