in-parallel 0.1.8 → 0.1.9
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.
- 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
|