forkomatic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.6.4"
11
+ gem "rcov"
12
+ gem "rdoc"
13
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jon Durbin
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.
@@ -0,0 +1,19 @@
1
+ = forkomatic
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to forkomatic
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2012 Jon Durbin. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "forkomatic"
18
+ gem.homepage = "http://github.com/gdi/forkomatic"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Easily run multiple processes continuously in parallel.}
21
+ gem.description = %Q{Easily create parallel processes to do work. The process count can be dynamic (between min_children and max_children), can loop continuously (by setting max_iterations => nil), etc}
22
+ gem.email = "jond@greenviewdata.com"
23
+ gem.authors = ["Jon Durbin"]
24
+ gem.version = '0.0.1'
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ test.rcov_opts << '--exclude "gems/*"'
42
+ end
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 = "forkomatic #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
@@ -0,0 +1,145 @@
1
+ class Forkomatic
2
+ class Job
3
+ attr_accessor :pid
4
+
5
+ def initialize
6
+ self.pid = nil
7
+ end
8
+
9
+ def work!
10
+ sleep 1
11
+ end
12
+ end
13
+
14
+ attr_accessor :max_children
15
+ attr_accessor :work_interval
16
+ attr_accessor :max_iterations
17
+ attr_accessor :jobs
18
+ attr_accessor :wait_for_children
19
+
20
+ # Initialize the runners.
21
+ def initialize(args)
22
+ params = {}
23
+ if args.is_a?(String)
24
+ # Load config from a file.
25
+ params = load_config(args)
26
+ elsif args.is_a?(Integer)
27
+ # Given an integer, forkomatic will only run N runners 1 time.
28
+ params[:wait_for_children] = true
29
+ params[:max_children] = args
30
+ params[:work_interval] = 0
31
+ params[:max_iterations] = 1
32
+ elsif args.is_a?(Hash)
33
+ # Specify the parameters directly.
34
+ params = args
35
+ end
36
+ t = params
37
+ params.inject({}) {|t, (key, val)| t[key.to_sym] = val; t}
38
+ self.jobs = []
39
+ self.max_children = params[:max_children] || 1
40
+ self.work_interval = params[:work_interval].nil? || params[:work_interval] < 0 ? 0 : params[:work_interval]
41
+ self.max_iterations = params[:max_iterations]
42
+ self.wait_for_children = params[:wait_for_children].nil? ? true : params[:wait_for_children]
43
+ end
44
+
45
+ # Load a configuration from a file.
46
+ def load_config(config_file)
47
+ params = {}
48
+ # Allowed options.
49
+ options = ['max_children', 'work_interval', 'max_iterations', 'wait_for_children']
50
+ begin
51
+ # Try to read the config file, and store the values.
52
+ data = File.open(config_file, "r").read.split(/\n/)
53
+ data.each do |line|
54
+ if line =~ /^\s*([a-zA-Z_]+)\s+([0-9]+)/
55
+ config_item = $1
56
+ config_value = $2
57
+ # Make sure option is valid.
58
+ if options.map(&:downcase).include?(config_item.downcase)
59
+ params[config_item.to_sym] = config_value.to_i
60
+ end
61
+ end
62
+ end
63
+ rescue => e
64
+ puts "Error loading config file: #{e.to_s}"
65
+ end
66
+ params
67
+ end
68
+
69
+ # Kill all child processes and shutdown.
70
+ def shutdown(all)
71
+ child_pids.each do |pid|
72
+ begin
73
+ Process.kill("TERM", pid)
74
+ rescue => e
75
+ puts e.to_s
76
+ end
77
+ end
78
+ exit if all
79
+ end
80
+
81
+ # Do work.
82
+ def run
83
+ Signal.trap("INT") { shutdown(true) }
84
+ iteration = 0
85
+ while (@max_iterations.nil? || iteration < @max_iterations) do
86
+ iteration += 1
87
+ current_jobs = build_jobs(available)
88
+ current_jobs.each do |job|
89
+ pid = Process.fork do
90
+ job.work!
91
+ end
92
+ job.pid = pid
93
+ @jobs.push(job)
94
+ end
95
+ sleep @work_interval if @work_interval > 0
96
+ end
97
+ Process.waitall if @wait_for_children
98
+ end
99
+
100
+ # Create workers.
101
+ def build_jobs(count)
102
+ (1..count).each.collect {Forkomatic::Job.new}
103
+ end
104
+
105
+ # Reap child processes that finished.
106
+ def reap(pid)
107
+ return true if pid.nil?
108
+ begin
109
+ return true if Process.waitpid(pid, Process::WNOHANG)
110
+ rescue Errno::ECHILD
111
+ return true
112
+ rescue => e
113
+ puts "ERROR: #{e.to_s}"
114
+ end
115
+ return false
116
+ end
117
+
118
+ # Try to reap all child processes.
119
+ def reap_all
120
+ finished = []
121
+ @jobs.each do |job|
122
+ if reap(job.pid)
123
+ finished.push(job.pid)
124
+ end
125
+ end
126
+ @jobs.delete_if {|job| finished.include?(job.pid)}
127
+ end
128
+
129
+ # See how many children are available.
130
+ def available
131
+ # Initialize if need be.
132
+ return @max_children if @jobs.nil? || @jobs.empty?
133
+ # Reap children runners without waiting.
134
+ reap_all
135
+ @max_children - @jobs.length
136
+ end
137
+
138
+ # Get a list of current process IDs.
139
+ def child_pids
140
+ reap_all
141
+ pids = []
142
+ @jobs.each {|job| pids.push(job.pid) if job.pid}
143
+ pids
144
+ end
145
+ end
@@ -0,0 +1 @@
1
+ max_children 2
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Forkomatic do
4
+ before do
5
+ @forkomatic = Forkomatic.new({:max_children => 5, :wait_for_children => false, :max_iterations => 1})
6
+ end
7
+
8
+ describe '.new' do
9
+ it "should create a forkomatic from the params" do
10
+ @forkomatic.should be_a_kind_of(Forkomatic)
11
+ end
12
+ it "should set max_iterations to 1" do
13
+ @forkomatic.max_iterations.should == 1
14
+ end
15
+ it "should create a forkomatic intialized with an integer" do
16
+ @forkomatic.available.should == 5
17
+ end
18
+ it "should create a forkomatic initialized with a hash" do
19
+ @test = Forkomatic.new({'max_runners' => 1})
20
+ @test.available.should == 1
21
+ end
22
+ it "should create a forkomatic initalized with a file path to a configuration" do
23
+ @test = Forkomatic.new(File.dirname(__FILE__) + '/../fixtures/config.txt')
24
+ @test.available.should == 2
25
+ end
26
+ it "should not wait for children if :wait_for_children is false" do
27
+ @forkomatic.wait_for_children.should == false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'forkomatic'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'forkomatic'
3
+
4
+ class Tester < Forkomatic
5
+ class Tester::Job < Forkomatic::Job
6
+ # Add a data accessor...
7
+ attr_accessor :data
8
+
9
+ # Overload the initialize function to include this worker's data section.
10
+ def initialize(data)
11
+ self.data = data
12
+ end
13
+
14
+ # Overload the work! function.
15
+ def work!
16
+ puts @data
17
+ sleep rand(5)
18
+ end
19
+ end
20
+
21
+ # Overload the build_jobs function to give each job useful work and data to work with.
22
+ def build_jobs(count)
23
+ puts "Existing children : #{child_pids.length}"
24
+ puts "New this iteration: #{count}"
25
+ (1..count).each.collect { Tester::Job.new(rand(20)) }
26
+ end
27
+ end
28
+
29
+ # Create a new forkomatic instance with 3 child workers.
30
+ f = Tester.new({:max_children => 10, :work_interval => 1})
31
+ # Do the work
32
+ f.run
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'forkomatic'
3
+
4
+ $range = 1000000
5
+ $data = (1..$range).each.collect
6
+ $search_key = rand($range)
7
+ $found = false
8
+
9
+ class Tester < Forkomatic
10
+ class Tester::Job < Forkomatic::Job
11
+ # Beginning and end index to search
12
+ attr_accessor :start
13
+ attr_accessor :stop
14
+
15
+ # Overload the initialize function to include this worker's data section.
16
+ def initialize(start, stop)
17
+ self.start = start
18
+ self.stop = stop
19
+ end
20
+
21
+ # Overload the work! function.
22
+ def work!
23
+ (@start..@stop).each {|i| return if $found; d = $data[i]; $found = true and puts "Found #{d}" if d == $search_key }
24
+ end
25
+ end
26
+
27
+ # Overload the build_jobs function to give each job useful work and data to work with.
28
+ def build_jobs(count)
29
+ (1..count).each.collect {|i| Tester::Job.new((i - 1) * ($range / count), i * ($range / count) - 1) }
30
+ end
31
+ end
32
+
33
+ # Time execution with 1 child.
34
+ (1..4).each do |i|
35
+ start = Time.now
36
+ f = Tester.new(i)
37
+ f.run
38
+ stop = Time.now
39
+ puts "Forks: #{i}, Execution time: #{(stop - start) * 1000}ms"
40
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'forkomatic'
3
+
4
+ class Tester < Forkomatic
5
+ class Tester::Job < Forkomatic::Job
6
+ # Add a data accessor...
7
+ attr_accessor :data
8
+
9
+ # Overload the initialize function to include this worker's data section.
10
+ def initialize(data, start, stop)
11
+ self.data = (start..stop).collect {|i| data[i]}
12
+ end
13
+
14
+ # Overload the work! function.
15
+ def work!
16
+ @data.each {|d| puts d}
17
+ end
18
+ end
19
+
20
+ # Overload the build_jobs function to give each job useful work and data to work with.
21
+ def build_jobs(count)
22
+ data = (1..100).each.collect
23
+ i = 0
24
+ (1..count).each.collect { i += 1; Tester::Job.new(data, i * (20 / count), (i + 1) * (20 / count) - 1)}
25
+ end
26
+ end
27
+
28
+ # Create a new forkomatic instance with 20 child workers.
29
+ f = Tester.new(20)
30
+ # Do the work
31
+ f.run
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forkomatic
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Jon Durbin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-01 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :development
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 23
29
+ segments:
30
+ - 1
31
+ - 0
32
+ - 0
33
+ version: 1.0.0
34
+ name: bundler
35
+ version_requirements: *id001
36
+ prerelease: false
37
+ - !ruby/object:Gem::Dependency
38
+ type: :development
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 7
45
+ segments:
46
+ - 1
47
+ - 6
48
+ - 4
49
+ version: 1.6.4
50
+ name: jeweler
51
+ version_requirements: *id002
52
+ prerelease: false
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
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
+ name: rcov
65
+ version_requirements: *id003
66
+ prerelease: false
67
+ - !ruby/object:Gem::Dependency
68
+ type: :development
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
+ name: rdoc
79
+ version_requirements: *id004
80
+ prerelease: false
81
+ description: Easily create parallel processes to do work. The process count can be dynamic (between min_children and max_children), can loop continuously (by setting max_iterations => nil), etc
82
+ email: jond@greenviewdata.com
83
+ executables: []
84
+
85
+ extensions: []
86
+
87
+ extra_rdoc_files:
88
+ - LICENSE.txt
89
+ - README.rdoc
90
+ files:
91
+ - .document
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.rdoc
95
+ - Rakefile
96
+ - lib/forkomatic.rb
97
+ - spec/fixtures/config.txt
98
+ - spec/lib/forkomatic_spec.rb
99
+ - spec/spec_helper.rb
100
+ - test/continuous_test.rb
101
+ - test/parallel_search.rb
102
+ - test/simple_test.rb
103
+ has_rdoc: true
104
+ homepage: http://github.com/gdi/forkomatic
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project:
133
+ rubygems_version: 1.6.2
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Easily run multiple processes continuously in parallel.
137
+ test_files: []
138
+