in-parallel 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/in_parallel/version.rb +1 -1
- data/lib/in_parallel.rb +90 -55
- data/lib/parallel_enumerable.rb +3 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6277b0e376bda7d26d7f16760f16764569bdf01
|
4
|
+
data.tar.gz: f0798ecc56cd7e02cd1784fbc28f4b19a3e76118
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3cb0370b0254ce1a801b32d7fcab2bb80acfeec6d0253a8752560c1da8ddf53de94e8a71d3e61369d204be44617451d26f909126ee484b61d0302c1b47d377d
|
7
|
+
data.tar.gz: 2f769c82a0fb813eb3ebfbeb0f09a8fe1a00086ea1e0220bca5913c1c47d5a617d625070627d9e7a619fdb42aeb9a370c9a6aedb53b333111fee2d47840644cc
|
data/in_parallel/version.rb
CHANGED
data/lib/in_parallel.rb
CHANGED
@@ -4,8 +4,8 @@ module InParallel
|
|
4
4
|
# How many seconds between outputting to stdout that we are waiting for child processes.
|
5
5
|
# 0 or < 0 means no signaling.
|
6
6
|
@@signal_interval = 30
|
7
|
+
@@timeout = 1800
|
7
8
|
@@process_infos = []
|
8
|
-
@@raise_error = nil
|
9
9
|
def self.process_infos
|
10
10
|
@@process_infos
|
11
11
|
end
|
@@ -21,42 +21,30 @@ module InParallel
|
|
21
21
|
@@main_pid
|
22
22
|
end
|
23
23
|
|
24
|
+
def self.timeout
|
25
|
+
@@timeout
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.timeout=(value)
|
29
|
+
@@timeout = value
|
30
|
+
end
|
31
|
+
|
24
32
|
# Example - will spawn 2 processes, (1 for each method) wait until they both complete, and log STDOUT:
|
25
33
|
# InParallel.run_in_parallel {
|
26
34
|
# @result_1 = method1
|
27
35
|
# @result_2 = method2
|
28
36
|
# }
|
29
37
|
# NOTE: Only supports assigning instance variables within the block, not local variables
|
30
|
-
def self.run_in_parallel(&block)
|
38
|
+
def self.run_in_parallel(timeout = @@timeout, kill_all_on_error = false, &block)
|
31
39
|
if Process.respond_to?(:fork)
|
32
40
|
proxy = BlankBindingParallelProxy.new(block.binding)
|
33
41
|
proxy.instance_eval(&block)
|
34
|
-
return wait_for_processes(proxy, block.binding)
|
42
|
+
return wait_for_processes(proxy, block.binding, timeout, kill_all_on_error)
|
35
43
|
end
|
36
44
|
puts 'Warning: Fork is not supported on this OS, executing block normally'
|
37
45
|
block.call
|
38
46
|
end
|
39
47
|
|
40
|
-
# Private method to lookup results from the results_map and replace the
|
41
|
-
# temp values with actual return values
|
42
|
-
def self.result_lookup(proxy_obj, target_obj, results_map)
|
43
|
-
target_obj = eval('self', target_obj)
|
44
|
-
proxy_obj ||= target_obj
|
45
|
-
vars = (proxy_obj.instance_variables)
|
46
|
-
results = []
|
47
|
-
results_map.keys.each { |tmp_result|
|
48
|
-
results << results_map[tmp_result]
|
49
|
-
vars.each {|var|
|
50
|
-
if proxy_obj.instance_variable_get(var) == tmp_result
|
51
|
-
target_obj.instance_variable_set(var, results_map[tmp_result])
|
52
|
-
break
|
53
|
-
end
|
54
|
-
}
|
55
|
-
}
|
56
|
-
results
|
57
|
-
end
|
58
|
-
private_class_method :result_lookup
|
59
|
-
|
60
48
|
# Example - Will spawn a process in the background to run puppet agent on two agents and return immediately:
|
61
49
|
# Parallel.run_in_background {
|
62
50
|
# @result_1 = method1
|
@@ -91,24 +79,35 @@ module InParallel
|
|
91
79
|
|
92
80
|
# Waits for all processes to complete and logs STDOUT and STDERR in chunks from any processes
|
93
81
|
# that were triggered from this Parallel class
|
94
|
-
|
82
|
+
# @param [Object] proxy - The instance of the proxy class that the method was executed within
|
83
|
+
# (probably only useful when called by run_in_background)
|
84
|
+
# @param [Object] binding - The binding of the block to assign return values to instance variables
|
85
|
+
# (probably only useful when called by run_in_background)
|
86
|
+
# @param [Int] timeout Time in seconds to wait before giving up on a child process
|
87
|
+
# @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately -
|
88
|
+
# killing all other forked processes - when one process errors.
|
89
|
+
def self.wait_for_processes(proxy = self, binding = nil, timeout = nil, kill_all_on_error = false)
|
90
|
+
raise_error = nil
|
91
|
+
timeout ||= @@timeout
|
95
92
|
trap(:INT) do
|
96
93
|
puts "Warning, recieved interrupt. Processing child results and exiting."
|
97
|
-
|
98
|
-
# Send INT to each child process so it returns and can print stdout and stderr to console before exiting.
|
99
|
-
Process.kill("INT", process_info[:pid])
|
100
|
-
}
|
94
|
+
kill_child_processes
|
101
95
|
end
|
102
96
|
return unless Process.respond_to?(:fork)
|
103
97
|
# Custom process to wait so that we can do things like time out, and kill child processes if
|
104
98
|
# one process returns with an error before the others complete.
|
105
|
-
results_map =
|
106
|
-
|
99
|
+
results_map = Array.new(@@process_infos.count)
|
100
|
+
start_time = Time.now
|
101
|
+
timer = start_time
|
107
102
|
while !@@process_infos.empty? do
|
108
103
|
if @@signal_interval > 0 && Time.now > timer + @@signal_interval
|
109
104
|
puts 'Waiting for child processes.'
|
110
105
|
timer = Time.now
|
111
106
|
end
|
107
|
+
if Time.now > start_time + timeout
|
108
|
+
kill_child_processes
|
109
|
+
raise_error = ::RuntimeError.new("Child process ran longer than timeout of #{timeout}")
|
110
|
+
end
|
112
111
|
@@process_infos.each {|process_info|
|
113
112
|
# wait up to half a second for each thread to see if it is complete, if not, check the next thread.
|
114
113
|
# returns immediately if the process has completed.
|
@@ -121,48 +120,42 @@ module InParallel
|
|
121
120
|
puts File.new(process_info[:std_out], 'r').readlines
|
122
121
|
puts "------ Completed output for #{process_info[:method_sym]} - #{process_info[:pid]}\n"
|
123
122
|
result = process_info[:result].read
|
124
|
-
|
123
|
+
marshalled_result = (result.nil? || result.empty?) ? result : Marshal.load(result)
|
124
|
+
if marshalled_result.is_a?(Exception)
|
125
|
+
raise_error = marshalled_result.dup
|
126
|
+
kill_child_processes if kill_all_on_error
|
127
|
+
marshalled_result = nil
|
128
|
+
end
|
129
|
+
results_map[process_info[:index]] = {process_info[:tmp_result] => marshalled_result}
|
125
130
|
File.delete(process_info[:std_out])
|
126
131
|
# Kill all other processes and let them log their stdout before re-raising
|
127
132
|
# if a child process raised an error.
|
128
|
-
if results_map[process_info[:tmp_result]].is_a?(Exception)
|
129
|
-
@@process_infos.each{|p_info|
|
130
|
-
begin
|
131
|
-
Process.kill('SIGINT', p_info[:pid]) unless p_info[:pid] == process_info[:pid]
|
132
|
-
rescue StandardError
|
133
|
-
end
|
134
|
-
}
|
135
|
-
end
|
136
133
|
ensure
|
137
134
|
# close the read end pipe
|
138
135
|
process_info[:result].close unless process_info[:result].closed?
|
139
136
|
@@process_infos.delete(process_info)
|
140
|
-
@@raise_error = results_map[process_info[:tmp_result]] if results_map[process_info[:tmp_result]].is_a?(Exception)
|
141
|
-
break
|
142
137
|
end
|
143
138
|
end
|
144
139
|
}
|
145
140
|
end
|
146
141
|
|
147
|
-
# Reset the error in case the error is rescued
|
148
|
-
begin
|
149
|
-
raise @@raise_error unless @@raise_error.nil?
|
150
|
-
ensure
|
151
|
-
@@raise_error = nil
|
152
|
-
end
|
153
|
-
|
154
142
|
results = []
|
155
143
|
|
156
144
|
# pass in the 'self' from the block.binding which is the instance of the class
|
157
145
|
# that contains the initial binding call.
|
158
|
-
# This gives us access to the
|
146
|
+
# This gives us access to the instance variables from that context.
|
159
147
|
results = result_lookup(proxy, binding, results_map) if binding
|
160
148
|
|
149
|
+
# If there are background_objs AND results, don't return the background obj results
|
150
|
+
# (which would mess up expected results from each_in_parallel),
|
151
|
+
# but do process their results in case they are assigned to instance variables
|
161
152
|
@@background_objs.each {|obj|
|
162
|
-
|
153
|
+
result_lookup(obj[:proxy], obj[:target], results_map)
|
163
154
|
}
|
164
155
|
@@background_objs.clear
|
165
156
|
|
157
|
+
raise raise_error unless raise_error.nil?
|
158
|
+
|
166
159
|
return results
|
167
160
|
end
|
168
161
|
|
@@ -176,6 +169,7 @@ module InParallel
|
|
176
169
|
exit_status = 0
|
177
170
|
trap(:INT) do
|
178
171
|
puts("Warning: Interrupt received in child process; exiting #{Process.pid}")
|
172
|
+
kill_child_processes
|
179
173
|
return
|
180
174
|
end
|
181
175
|
write_file = File.new("tmp/parallel_process_#{Process.pid}", 'w')
|
@@ -228,12 +222,45 @@ module InParallel
|
|
228
222
|
:method_sym => method_sym,
|
229
223
|
:std_out => "tmp/parallel_process_#{pid}",
|
230
224
|
:result => read_result,
|
231
|
-
:tmp_result => "unresolved_parallel_result_#{@@result_id}"
|
225
|
+
:tmp_result => "unresolved_parallel_result_#{@@result_id}",
|
226
|
+
:index => @@process_infos.count }
|
232
227
|
@@process_infos.push(process_info)
|
233
228
|
@@result_id += 1
|
234
229
|
process_info
|
235
230
|
end
|
236
231
|
|
232
|
+
def self.kill_child_processes
|
233
|
+
@@process_infos.each { |process_info|
|
234
|
+
# Send INT to each child process so it returns and can print stdout and stderr to console before exiting.
|
235
|
+
begin
|
236
|
+
Process.kill("INT", process_info[:pid])
|
237
|
+
rescue Errno::ESRCH
|
238
|
+
# If one of the other processes has completed in the very short time before we try to kill it, handle the exception
|
239
|
+
end
|
240
|
+
}
|
241
|
+
end
|
242
|
+
private_class_method :kill_child_processes
|
243
|
+
|
244
|
+
# Private method to lookup results from the results_map and replace the
|
245
|
+
# temp values with actual return values
|
246
|
+
def self.result_lookup(proxy_obj, target_obj, results_map)
|
247
|
+
target_obj = eval('self', target_obj)
|
248
|
+
proxy_obj ||= target_obj
|
249
|
+
vars = (proxy_obj.instance_variables)
|
250
|
+
results = []
|
251
|
+
results_map.each { |tmp_result|
|
252
|
+
results << tmp_result.values[0]
|
253
|
+
vars.each {|var|
|
254
|
+
if proxy_obj.instance_variable_get(var) == tmp_result.keys[0]
|
255
|
+
target_obj.instance_variable_set(var, tmp_result.values[0])
|
256
|
+
break
|
257
|
+
end
|
258
|
+
}
|
259
|
+
}
|
260
|
+
results
|
261
|
+
end
|
262
|
+
private_class_method :result_lookup
|
263
|
+
|
237
264
|
# Proxy class used to wrap each method execution in a block and run it in parallel
|
238
265
|
# A block from Parallel.run_in_parallel is executed with a binding of an instance of this class
|
239
266
|
class BlankBindingParallelProxy < BasicObject
|
@@ -264,10 +291,14 @@ module InParallel
|
|
264
291
|
# }
|
265
292
|
# NOTE - Only instance variables can be assigned the return values of the methods within the block.
|
266
293
|
# Local variables will not be assigned any values.
|
294
|
+
# @param [Int] timeout Time in seconds to wait before giving up on a child process
|
295
|
+
# @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately -
|
296
|
+
# killing all other forked processes - when one process errors.
|
267
297
|
# @param [Block] block This method will yield to a block of code passed by the caller
|
268
298
|
# @return [Array<Result>, Result] the return values of each method within the block
|
269
|
-
def run_in_parallel(&block)
|
270
|
-
InParallelExecutor.
|
299
|
+
def run_in_parallel(timeout=nil, kill_all_on_error = false, &block)
|
300
|
+
timeout ||= InParallelExecutor.timeout
|
301
|
+
InParallelExecutor.run_in_parallel(timeout, kill_all_on_error, &block)
|
271
302
|
end
|
272
303
|
|
273
304
|
# Forks a process for each method within a block and returns immediately
|
@@ -297,8 +328,12 @@ module InParallel
|
|
297
328
|
|
298
329
|
# Waits for all processes started by run_in_background to complete execution, then prints STDOUT
|
299
330
|
# and assigns return values to instance variables. See :run_in_background
|
331
|
+
# @param [Int] timeout Time in seconds to wait before giving up on a child process
|
332
|
+
# @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately -
|
333
|
+
# killing all other forked processes - when one process errors.
|
300
334
|
# @return [Array<Result>, Result] the temporary return values of each method within the block
|
301
|
-
def wait_for_processes
|
302
|
-
InParallelExecutor.
|
335
|
+
def wait_for_processes(timeout=nil, kill_all_on_error = false)
|
336
|
+
timeout ||= InParallelExecutor.timeout
|
337
|
+
InParallelExecutor.wait_for_processes(nil, nil, timeout, kill_all_on_error)
|
303
338
|
end
|
304
339
|
end
|
data/lib/parallel_enumerable.rb
CHANGED
@@ -8,8 +8,9 @@ module Enumerable
|
|
8
8
|
# my_method(int)
|
9
9
|
# }
|
10
10
|
# @param [String] identifier - Optional identifier for logging purposes only. Will use the block location by default.
|
11
|
+
# @param [Int] timeout - Seconds to wait for a forked process to complete before timing out
|
11
12
|
# @return [Array<Object>] results - the return value of each block execution.
|
12
|
-
def each_in_parallel(identifier=nil, &block)
|
13
|
+
def each_in_parallel(identifier=nil, timeout=(InParallel::InParallelExecutor.timeout), kill_all_on_error = false, &block)
|
13
14
|
if Process.respond_to?(:fork) && count > 1
|
14
15
|
identifier ||= "#{caller_locations[0]}"
|
15
16
|
each do |item|
|
@@ -17,7 +18,7 @@ module Enumerable
|
|
17
18
|
puts "'each_in_parallel' forked process for '#{identifier}' - PID = '#{out[:pid]}'\n"
|
18
19
|
end
|
19
20
|
# return the array of values, no need to look up from the map.
|
20
|
-
return InParallel::InParallelExecutor.wait_for_processes(nil, block.binding)
|
21
|
+
return InParallel::InParallelExecutor.wait_for_processes(nil, block.binding, timeout, kill_all_on_error)
|
21
22
|
end
|
22
23
|
puts 'Warning: Fork is not supported on this OS, executing block normally' unless Process.respond_to? :fork
|
23
24
|
block.call
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: in-parallel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- samwoods1
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
11
|
+
date: 2016-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|