resque-multi-job-forks 0.2.0 → 0.3.0

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.
@@ -2,6 +2,74 @@ require 'resque'
2
2
  require 'resque/worker'
3
3
 
4
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
+
5
73
  # the `before_child_exit` hook will run in the child process
6
74
  # right before the child process terminates
7
75
  #
@@ -16,45 +84,4 @@ module Resque
16
84
  @before_child_exit = before_child_exit
17
85
  end
18
86
 
19
- class Worker
20
- attr_accessor :jobs_per_fork
21
- attr_reader :jobs_processed
22
-
23
- unless method_defined?(:done_working_without_multi_job_forks)
24
- def process_with_multi_job_forks(job = nil)
25
- @jobs_processed ||= 0
26
- @kill_fork_at ||= Time.now.to_i + (ENV['MINUTES_PER_FORK'].to_i * 60)
27
- process_without_multi_job_forks(job)
28
- end
29
- alias_method :process_without_multi_job_forks, :process
30
- alias_method :process, :process_with_multi_job_forks
31
-
32
- def done_working_with_multi_job_forks
33
- done_working_without_multi_job_forks
34
-
35
- @jobs_processed += 1
36
-
37
- if @jobs_processed == 1
38
- old_after_fork = Resque.after_fork
39
- Resque.after_fork = nil
40
-
41
- while Time.now.to_i < @kill_fork_at
42
- if job = reserve
43
- process(job)
44
- else
45
- sleep(1)
46
- end
47
- end
48
-
49
- Resque.after_fork = old_after_fork
50
-
51
- run_hook :before_child_exit, self
52
- @jobs_processed = nil
53
- @kill_fork_at = nil
54
- end
55
- end
56
- alias_method :done_working_without_multi_job_forks, :done_working
57
- alias_method :done_working, :done_working_with_multi_job_forks
58
- end
59
- end
60
- end
87
+ end
data/test/helper.rb CHANGED
@@ -1,8 +1,60 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
-
4
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
6
4
  $TESTING = true
7
5
 
6
+ require 'rubygems'
7
+ require 'bundler'
8
+ Bundler.setup
9
+ Bundler.require
10
+ require 'test/unit'
8
11
  require 'resque-multi-job-forks'
12
+
13
+ # setup redis & resque.
14
+ redis = Redis.new(:db => 1)
15
+ Resque.redis = redis
16
+
17
+ # adds simple STDOUT logging to test workers.
18
+ # set `VERBOSE=true` when running the tests to view resques log output.
19
+ module Resque
20
+ class Worker
21
+ def log(msg)
22
+ puts "*** #{msg}" unless ENV['VERBOSE'].nil?
23
+ end
24
+ alias_method :log!, :log
25
+ end
26
+ end
27
+
28
+ # stores a record of the job processing sequence.
29
+ # you may wish to reset this in the test `setup` method.
30
+ $SEQUENCE = []
31
+
32
+ # test job, tracks sequence.
33
+ class SequenceJob
34
+ @queue = :jobs
35
+ def self.perform(i)
36
+ $SEQUENCE << "work_#{i}".to_sym
37
+ sleep(2)
38
+ end
39
+ end
40
+
41
+ class QuickSequenceJob
42
+ @queue = :jobs
43
+ def self.perform(i)
44
+ $SEQUENCE << "work_#{i}".to_sym
45
+ end
46
+ end
47
+
48
+
49
+ # test hooks, tracks sequence.
50
+ Resque.after_fork do
51
+ $SEQUENCE << :after_fork
52
+ end
53
+
54
+ Resque.before_fork do
55
+ $SEQUENCE << :before_fork
56
+ end
57
+
58
+ Resque.before_child_exit do |worker|
59
+ $SEQUENCE << "before_child_exit_#{worker.jobs_processed}".to_sym
60
+ end
@@ -1,40 +1,58 @@
1
- require 'helper'
2
-
3
- class SomeJob
4
- def self.perform(i)
5
- $SEQUENCE << "work_#{i}".to_sym
6
- puts 'working...'
7
- sleep(25)
8
- end
9
- end
10
-
11
- Resque.after_fork do
12
- $SEQUENCE << :after_fork
13
- end
14
-
15
- Resque.before_child_exit do |worker|
16
- $SEQUENCE << "before_child_exit_#{worker.jobs_processed}".to_sym
17
- end
1
+ require File.dirname(__FILE__) + '/helper'
18
2
 
19
3
  class TestResqueMultiJobForks < Test::Unit::TestCase
20
4
  def setup
21
5
  $SEQUENCE = []
6
+ Resque.redis.flushdb
7
+ @worker = Resque::Worker.new(:jobs)
22
8
  end
23
-
24
- def test_sequence_of_events
25
- Resque.redis.flush_all
26
-
27
- ENV['MINUTES_PER_FORK'] = '1'
28
9
 
29
- worker = Resque::Worker.new(:jobs)
30
-
31
- Resque::Job.create(:jobs, SomeJob, 1)
32
- Resque::Job.create(:jobs, SomeJob, 2)
33
- Resque::Job.create(:jobs, SomeJob, 3)
34
- Resque::Job.create(:jobs, SomeJob, 4)
35
-
36
- worker.work(0)
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
37
30
 
38
- assert_equal([:after_fork, :work_1, :work_2, :work_3, :before_child_exit_3, :after_fork, :work_4, :before_child_exit_1], $SEQUENCE)
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
39
57
  end
40
58
  end
metadata CHANGED
@@ -1,53 +1,93 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-multi-job-forks
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 19
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
- - 2
8
+ - 3
8
9
  - 0
9
- version: 0.2.0
10
+ version: 0.3.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Mick Staugaard
14
+ - Luke Antins
13
15
  autorequire:
14
16
  bindir: bin
15
17
  cert_chain: []
16
18
 
17
- date: 2010-04-22 00:00:00 -07:00
19
+ date: 2010-12-17 00:00:00 -08:00
18
20
  default_executable:
19
21
  dependencies:
20
22
  - !ruby/object:Gem::Dependency
21
23
  name: resque
22
24
  prerelease: false
23
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
24
27
  requirements:
25
- - - <
28
+ - - ~>
26
29
  - !ruby/object:Gem::Version
30
+ hash: 63
27
31
  segments:
28
32
  - 1
29
- - 8
33
+ - 10
30
34
  - 0
31
- version: 1.8.0
35
+ version: 1.10.0
32
36
  type: :runtime
33
37
  version_requirements: *id001
34
- description: |-
35
- When your resque jobs are frequent and fast,
36
- the overhead of forking and running your after_fork might get too big.
37
- email: mick@staugaard.com
38
+ - !ruby/object:Gem::Dependency
39
+ name: json
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rake
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: bundler
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ description: When your resque jobs are frequent and fast, the overhead of forking and running your after_fork might get too big.
81
+ email:
82
+ - mick@zendesk.com
83
+ - luke@lividpenguin.com
38
84
  executables: []
39
85
 
40
86
  extensions: []
41
87
 
42
- extra_rdoc_files:
43
- - LICENSE
44
- - README.rdoc
88
+ extra_rdoc_files: []
89
+
45
90
  files:
46
- - .document
47
- - .gitignore
48
- - LICENSE
49
- - README.rdoc
50
- - Rakefile
51
91
  - lib/resque-multi-job-forks.rb
52
92
  - test/helper.rb
53
93
  - test/test_resque-multi-job-forks.rb
@@ -56,28 +96,32 @@ homepage: http://github.com/staugaard/resque-multi-job-forks
56
96
  licenses: []
57
97
 
58
98
  post_install_message:
59
- rdoc_options:
60
- - --charset=UTF-8
99
+ rdoc_options: []
100
+
61
101
  require_paths:
62
102
  - lib
63
103
  required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
64
105
  requirements:
65
106
  - - ">="
66
107
  - !ruby/object:Gem::Version
108
+ hash: 3
67
109
  segments:
68
110
  - 0
69
111
  version: "0"
70
112
  required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
71
114
  requirements:
72
115
  - - ">="
73
116
  - !ruby/object:Gem::Version
117
+ hash: 3
74
118
  segments:
75
119
  - 0
76
120
  version: "0"
77
121
  requirements: []
78
122
 
79
123
  rubyforge_project:
80
- rubygems_version: 1.3.6
124
+ rubygems_version: 1.3.7
81
125
  signing_key:
82
126
  specification_version: 3
83
127
  summary: Have your resque workers process more that one job
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/.gitignore DELETED
@@ -1,22 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
- *.gemspec
21
-
22
- ## PROJECT::SPECIFIC
data/LICENSE DELETED
@@ -1,20 +0,0 @@
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 DELETED
@@ -1,30 +0,0 @@
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 DELETED
@@ -1,54 +0,0 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "resque-multi-job-forks"
8
- gem.version = "0.2.0"
9
- gem.summary = %Q{Have your resque workers process more that one job}
10
- gem.description = %Q{When your resque jobs are frequent and fast,
11
- the overhead of forking and running your after_fork might get too big.}
12
- gem.email = "mick@staugaard.com"
13
- gem.homepage = "http://github.com/staugaard/resque-multi-job-forks"
14
- gem.authors = ["Mick Staugaard"]
15
- gem.add_dependency "resque", "< 1.8.0"
16
- end
17
- Jeweler::GemcutterTasks.new
18
- rescue LoadError
19
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
- end
21
-
22
- require 'rake/testtask'
23
- Rake::TestTask.new(:test) do |test|
24
- test.libs << 'lib' << 'test'
25
- test.pattern = 'test/**/test_*.rb'
26
- test.verbose = true
27
- end
28
-
29
- begin
30
- require 'rcov/rcovtask'
31
- Rcov::RcovTask.new do |test|
32
- test.libs << 'test'
33
- test.pattern = 'test/**/test_*.rb'
34
- test.verbose = true
35
- end
36
- rescue LoadError
37
- task :rcov do
38
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
- end
40
- end
41
-
42
- task :test => :check_dependencies
43
-
44
- task :default => :test
45
-
46
- require 'rake/rdoctask'
47
- Rake::RDocTask.new do |rdoc|
48
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
-
50
- rdoc.rdoc_dir = 'rdoc'
51
- rdoc.title = "resque-multi-job-forks #{version}"
52
- rdoc.rdoc_files.include('README*')
53
- rdoc.rdoc_files.include('lib/**/*.rb')
54
- end