diff --git a/History.txt b/History.txt index 64bf8d6..5efdba2 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,10 @@ +=== 0.0.2 / 2009-06-29 + +* 2 bugfixes + + * Forkify now works correctly returns array values on RedHat machines + * More tests! + === 0.0.1 / 2009-06-23 * 1 major enhancement diff --git a/Manifest.txt b/Manifest.txt index e5ac35a..77f8057 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -6,3 +6,4 @@ lib/forkify.rb test/test_forkify.rb examples/a.rb examples/b.rb +examples/c.rb diff --git a/README.txt b/README.txt index f589aa7..7034245 100644 --- a/README.txt +++ b/README.txt @@ -22,7 +22,7 @@ == REQUIREMENTS: -* None +* Testy - only for running the tests == INSTALL: diff --git a/Rakefile b/Rakefile index cf8b6ec..5c546d9 100644 --- a/Rakefile +++ b/Rakefile @@ -4,12 +4,13 @@ require 'rubygems' require 'hoe' require './lib/forkify.rb' -FORKIFY_VERSION = "0.0.1" +FORKIFY_VERSION = "0.0.2" Hoe.spec('forkify') do version = FORKIFY_VERSION developer('Lee Hinman', 'lee@writequit.org') - self.rubyforge_name = 'hinmanm' + rubyforge_name = 'hinmanm' end + # vim: syntax=Ruby diff --git a/examples/c.rb b/examples/c.rb new file mode 100644 index 0000000..e0b7c42 --- /dev/null +++ b/examples/c.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# vim: set ts=2 sw=2 filetype=Ruby +# +# This example shows a problem with the current implementation of forkify: +# if a fork finishes work, it will still wait for the other forks to finish +# in the process pool before forking new processes for work. +# +# I hope to remedy this as soon as I figure out a good solution for it. + +require 'forkify' + +[1, 1, 1, 1, 5, 1].forkify(5) { |n| puts n; sleep(n); n } + +# 0.04s user 0.06s system 1% cpu 6.031 total +# (would be possible to run in a little over 5) diff --git a/forkify.gemspec b/forkify.gemspec index 2535eed..61e15ef 100644 --- a/forkify.gemspec +++ b/forkify.gemspec @@ -2,17 +2,17 @@ Gem::Specification.new do |s| s.name = %q{forkify} - s.version = "0.0.1" + s.version = "0.0.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Lee Hinman"] - s.date = %q{2009-06-23} + s.date = %q{2009-06-29} s.description = %q{forkify.rb makes it easy to process a bunch of data using 'n' worker processes. It is based off of forkoff and threadify by Ara Howard. It aims to be safe to use on Ruby 1.8.6+ and Ruby 1.9.1+} s.email = ["lee@writequit.org"] s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"] - s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/forkify.rb", "test/test_forkify.rb", "examples/a.rb", "examples/b.rb"] + s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/forkify.rb", "test/test_forkify.rb", "examples/a.rb", "examples/b.rb", "examples/c.rb"] s.homepage = %q{http://github.com/dakrone/forkify} s.rdoc_options = ["--main", "README.txt"] s.require_paths = ["lib"] diff --git a/lib/forkify.rb b/lib/forkify.rb index d1e40ba..a572724 100644 --- a/lib/forkify.rb +++ b/lib/forkify.rb @@ -1,9 +1,27 @@ -#require 'pp' +FORKIFY_DEBUG = false +require 'pp' if FORKIFY_DEBUG module Enumerable + + # + # Forkify will process _block_'s actions using processes. If no number of processes is + # given, the default of 5 will be used. If there are less than _procs_ number of items + # in the +Enumerable+ type, less processes will be spawned. + # + # It should be noted that forkify will *always* return an +Array+ at this time, so be + # careful with +Hash+ objects. + # + # = Examples + # + # [1, 2, 3].forkify { |n| n*2 } => [2, 4, 6] + # + # {:a => 1, :b => 2, :c => 3}.forkify { |k, v| [v, k] } => [[1, :a], [2, :b], [3, :c]] + # + # 10.times.forkify(10) { sleep(1) } => [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] (runs for less than 2 seconds) + # def forkify procs = 5, &block - #puts "Class: #{self.class}" - if Array === self + puts "Forkify Class: #{self.class}" if FORKIFY_DEBUG + if self === Array items = self else begin @@ -27,9 +45,9 @@ def forkify procs = 5, &block rpipes = [] num_procs.times do |i| - #puts "Fork # #{i}" + puts "Fork # #{i}" if FORKIFY_DEBUG r, w = IO.pipe - #pp "r, w: #{r} #{w}" + pp "r, w: #{r} #{w}" if FORKIFY_DEBUG wpipes << w rpipes << r pid = fork @@ -52,19 +70,27 @@ def forkify procs = 5, &block offset += num_procs - #pp "Waiting for pids: #{pids}" + pp "Waiting for pids: #{pids.inspect}" if FORKIFY_DEBUG pids.each { |p| Process.waitpid(p) } - datawaiting_pipes = Kernel.select(rpipes, wpipes, nil, 2) - readwaiting_pipes = datawaiting_pipes[0] - writewaiting_pipes = datawaiting_pipes[1] - #pp "data: #{datawaiting_pipes}" - #pp "read: #{readwaiting_pipes}" - #pp "write: #{writewaiting_pipes}" - unless readwaiting_pipes.size != writewaiting_pipes.size - readwaiting_pipes.size.times do |i| - r = readwaiting_pipes[i] - w = writewaiting_pipes[i] + # 1 select version + #datawaiting_pipes = Kernel.select(rpipes, wpipes, nil, 2) + #readwaiting_pipes = datawaiting_pipes[0] + #writewaiting_pipes = datawaiting_pipes[1] + + # Switch to 2 selects instead of 1 + #readwaiting_pipes = Kernel.select(rpipes, nil, nil, 2)[0] + #writewaiting_pipes = Kernel.select(nil, wpipes, nil, 2)[1] + + # Finally settled on going through the pipes instead of select for Linux bug + unless rpipes.size != wpipes.size + rpipes.size.times do |i| + r = rpipes[i] + w = wpipes[i] + + pp "read: #{readwaiting_pipes}" if FORKIFY_DEBUG + pp "write: #{writewaiting_pipes}" if FORKIFY_DEBUG + w.close data = '' while ( buf = r.read(8192) ) @@ -72,6 +98,7 @@ def forkify procs = 5, &block end result = Marshal.load( data ) r.close + pp "Pushing result: #{result}" if FORKIFY_DEBUG results << result end end diff --git a/test/test_forkify.rb b/test/test_forkify.rb index bf71dab..d4b6dc9 100644 --- a/test/test_forkify.rb +++ b/test/test_forkify.rb @@ -15,4 +15,17 @@ r = [1, 2, 3].forkify { |n| n * 2 } t.check :array_results, :expect => [2, 4, 6], :actual => r end + + test 'hash results' do |t| + r = {:a => 1, :b => 2, :c => 3}.forkify { |k, v| [k, v*2] } + t.check :hash_contains_a, :expect => true, :actual => r.include?([:a, 2]) + t.check :hash_contains_b, :expect => true, :actual => r.include?([:b, 4]) + t.check :hash_contains_c, :expect => true, :actual => r.include?([:c, 6]) + t.check :hash_length, :expect => 3, :actual => r.size + end + + test 'array of nils' do |t| + r = [nil, nil].forkify { |n| n } + t.check :nil_array, :expect => [nil, nil], :actual => r + end end