resque-multi-job-forks 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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