parallel 0.3.7 → 0.4.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.7
1
+ 0.4.0
@@ -3,7 +3,9 @@ require 'thread' # to get Thread.exclusive
3
3
  class Parallel
4
4
  VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
5
5
 
6
- def self.in_threads(count = 2)
6
+ def self.in_threads(options={:count => 2})
7
+ count, options = extract_count_from_options(options)
8
+
7
9
  out = []
8
10
  threads = []
9
11
 
@@ -17,40 +19,18 @@ class Parallel
17
19
  out
18
20
  end
19
21
 
20
- def self.in_processes(count = nil)
22
+ def self.in_processes(options = {}, &block)
23
+ count, options = extract_count_from_options(options)
21
24
  count ||= processor_count
25
+ preserve_results = (options[:preserve_results] != false)
22
26
 
23
- # Start writing results into n pipes
24
- reads = []
25
- writes = []
26
- pids = []
27
- count.times do |i|
28
- reads[i], writes[i] = IO.pipe
29
- # activate copy on write friendly GC of REE
30
- GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
31
- pids << Process.fork do
32
- Marshal.dump(yield(i), writes[i]) # Serialize result
33
- end
34
- end
35
-
36
- kill_on_ctrl_c(pids)
37
-
38
- # Collect results from pipes simultanously
39
- # otherwise pipes get stuck when to much is written (buffer full)
40
- out = []
41
- in_threads(count) do |i|
42
- writes[i].close
43
- while text = reads[i].gets
44
- out[i] = out[i].to_s + text
45
- end
46
- reads[i].close
47
- end
48
-
49
- out.map{|x| Marshal.load(x) } # Deserialize results
27
+ pipes = fork_and_start_writing(count, :preserve_results => preserve_results, &block)
28
+ out = read_from_pipes(pipes)
29
+ out.map{|x| deserialize(x) } if preserve_results
50
30
  end
51
31
 
52
32
  def self.each(array, options={}, &block)
53
- map(array, options, &block)
33
+ map(array, options.merge(:preserve_results => false), &block)
54
34
  array
55
35
  end
56
36
 
@@ -74,7 +54,7 @@ class Parallel
74
54
  loop do
75
55
  index = Thread.exclusive{ current+=1 }
76
56
  break if index >= array.size
77
- results[index] = *send(method, 1){ yield array[index] }
57
+ results[index] = *send(method, options.merge(:count => 1)){ yield array[index] }
78
58
  end
79
59
  end
80
60
 
@@ -92,7 +72,63 @@ class Parallel
92
72
 
93
73
  private
94
74
 
75
+ # Collect results from pipes simultanously
76
+ # otherwise pipes get stuck when to much is written (buffer full)
77
+ def self.read_from_pipes(reads)
78
+ out = []
79
+ in_threads(reads.size) do |i|
80
+ out[i] = ''
81
+ while text = reads[i].gets
82
+ out[i] += text
83
+ end
84
+ reads[i].close
85
+ end
86
+ out
87
+ end
88
+
89
+ # fork and start writing results into n pipes
90
+ def self.fork_and_start_writing(count, options, &block)
91
+ reads = []
92
+ pids = []
93
+ count.times do |i|
94
+ reads[i], write = IO.pipe
95
+ pids << do_in_new_process(i, options.merge(:write_to => (options[:preserve_results] ? write : nil)), &block)
96
+ write.close
97
+ end
98
+ kill_on_ctrl_c(pids)
99
+ reads
100
+ end
101
+
102
+ def self.do_in_new_process(work_item, options)
103
+ # activate copy on write friendly GC of REE
104
+ GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
105
+ Process.fork do
106
+ result = yield(work_item)
107
+ serialize(result, options) if options[:write_to]
108
+ end
109
+ end
110
+
111
+ def self.serialize(something, options)
112
+ Marshal.dump(something, options[:write_to])
113
+ end
114
+
115
+ def self.deserialize(something)
116
+ Marshal.load(something)
117
+ end
118
+
119
+ # options is either a Interger or a Hash with :count
120
+ def self.extract_count_from_options(options)
121
+ if options.is_a?(Hash)
122
+ count = options[:count]
123
+ else
124
+ count = options
125
+ options = {}
126
+ end
127
+ [count, options]
128
+ end
129
+
95
130
  # split an array into groups of size items
131
+ # (copied from ActiveSupport, to not require it)
96
132
  def self.in_groups_of(array, size)
97
133
  results = []
98
134
  loop do
@@ -106,7 +142,7 @@ class Parallel
106
142
  results
107
143
  end
108
144
 
109
- #handle user interrupt (Ctrl+c)
145
+ # kill all these processes (children) if user presses Ctrl+c
110
146
  def self.kill_on_ctrl_c(pids)
111
147
  Signal.trap :SIGINT do
112
148
  $stderr.puts 'Parallel execution interrupted, exiting ...'
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{parallel}
8
- s.version = "0.3.7"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Michael Grosser"]
12
- s.date = %q{2009-12-19}
12
+ s.date = %q{2010-01-17}
13
13
  s.email = %q{grosser.michael@gmail.com}
14
14
  s.extra_rdoc_files = [
15
15
  "README.markdown"
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  "VERSION",
21
21
  "lib/parallel.rb",
22
22
  "parallel.gemspec",
23
+ "spec/cases/no_dump_with_each.rb",
23
24
  "spec/cases/parallel_each.rb",
24
25
  "spec/cases/parallel_high_fork_rate.rb",
25
26
  "spec/cases/parallel_influence_outside_data.rb",
@@ -47,6 +48,7 @@ Gem::Specification.new do |s|
47
48
  "spec/cases/parallel_map_uneven.rb",
48
49
  "spec/cases/parallel_map_range.rb",
49
50
  "spec/cases/parallel_with_set_processes.rb",
51
+ "spec/cases/no_dump_with_each.rb",
50
52
  "spec/cases/parallel_start_and_kill.rb",
51
53
  "spec/cases/parallel_raise.rb",
52
54
  "spec/cases/parallel_sleeping_2.rb",
@@ -0,0 +1,16 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ class NotDumpable
4
+ def marshal_dump
5
+ raise "NOOOO"
6
+ end
7
+
8
+ def to_s
9
+ 'not dumpable'
10
+ end
11
+ end
12
+
13
+ Parallel.each([NotDumpable.new]) do |x|
14
+ print 'not dumpable'
15
+ x
16
+ end
@@ -106,6 +106,10 @@ describe Parallel do
106
106
  it "returns original array, works like map" do
107
107
  `ruby spec/cases/parallel_each.rb`.should == '-b--c--d--a-a b c d'
108
108
  end
109
+
110
+ it "does not use marshal_dump" do
111
+ `ruby spec/cases/no_dump_with_each.rb 2>&1`.should == 'not dumpable'
112
+ end
109
113
  end
110
114
 
111
115
  describe :in_groups_of do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.7
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-19 00:00:00 +01:00
12
+ date: 2010-01-17 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -27,6 +27,7 @@ files:
27
27
  - VERSION
28
28
  - lib/parallel.rb
29
29
  - parallel.gemspec
30
+ - spec/cases/no_dump_with_each.rb
30
31
  - spec/cases/parallel_each.rb
31
32
  - spec/cases/parallel_high_fork_rate.rb
32
33
  - spec/cases/parallel_influence_outside_data.rb
@@ -76,6 +77,7 @@ test_files:
76
77
  - spec/cases/parallel_map_uneven.rb
77
78
  - spec/cases/parallel_map_range.rb
78
79
  - spec/cases/parallel_with_set_processes.rb
80
+ - spec/cases/no_dump_with_each.rb
79
81
  - spec/cases/parallel_start_and_kill.rb
80
82
  - spec/cases/parallel_raise.rb
81
83
  - spec/cases/parallel_sleeping_2.rb