parallel 1.13.0 → 1.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89d859597b00942e642e88797290b1407a87615c0bb9756b8a73a3c976410aac
4
- data.tar.gz: 40537d9251999345344d79a6ef06946257301af2ddbe5484e1c97e6dea28d356
3
+ metadata.gz: 4dd167566b23262ceacbd640fda95c5e8d0d8149cc430f51ada53765a927cb18
4
+ data.tar.gz: 6a21c00b1109d5c665a471aec4a7af7d213ded2fc9123132b6576ee65ceb1758
5
5
  SHA512:
6
- metadata.gz: 45a8ae3ecf5ded1f1c53aaf98cc5bb45b6e1c60e51997143a2420433f4a94d0de4e339a6a9be8f276432f8bdff359fdeb10fdc5e8aa10e34874973c5c84384c4
7
- data.tar.gz: 9b2a309db6cd24a199fa2fd99279b4015df984c09a65896e6a56a11f080d691fceabb38684606f2c2e3291794ea50cac764c82fbb93d7c32a22720b93960c2e4
6
+ metadata.gz: 1f8a810c3b7fa77e8dd2b2539238b856b9b4ea40b5df49cf2cc4af2afbd2a73d9b6f34baab614c41e7b3b8bfe1917413bfbd014f647c5b4b96e7243d5433523d
7
+ data.tar.gz: 0bef1c7dc19b4738e228985512ee6cce1915f67ee29161b743821f69366d561ed8c3c5cbd4d9e6829342108fe9f10329563770d1978a660a04c58c0d9e2c8461
@@ -1,90 +1,42 @@
1
- if RUBY_VERSION.to_f >= 2.2
2
- require 'etc'
3
- end
1
+ # frozen_string_literal: true
2
+ require 'etc'
4
3
 
5
4
  module Parallel
5
+ # TODO: inline this method into parallel.rb and kill physical_processor_count in next major release
6
6
  module ProcessorCount
7
- # Number of processors seen by the OS and used for process scheduling.
8
- #
9
- # * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev
10
- # * BSD: /sbin/sysctl
11
- # * Cygwin: /proc/cpuinfo
12
- # * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl
13
- # * HP-UX: /usr/sbin/ioscan
14
- # * IRIX: /usr/sbin/sysconf
15
- # * Linux: /proc/cpuinfo
16
- # * Minix 3+: /proc/cpuinfo
17
- # * Solaris: /usr/sbin/psrinfo
18
- # * Tru64 UNIX: /usr/sbin/psrinfo
19
- # * UnixWare: /usr/sbin/psrinfo
20
- #
7
+ # Number of processors seen by the OS, used for process scheduling
21
8
  def processor_count
22
- @processor_count ||= begin
23
- if defined?(Etc) && Etc.respond_to?(:nprocessors)
24
- Etc.nprocessors
25
- else
26
- os_name = RbConfig::CONFIG["target_os"]
27
- if os_name =~ /mingw|mswin/
28
- require 'win32ole'
29
- result = WIN32OLE.connect("winmgmts://").ExecQuery(
30
- "select NumberOfLogicalProcessors from Win32_Processor")
31
- result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
32
- elsif File.readable?("/proc/cpuinfo")
33
- IO.read("/proc/cpuinfo").scan(/^processor/).size
34
- elsif File.executable?("/usr/bin/hwprefs")
35
- IO.popen("/usr/bin/hwprefs thread_count").read.to_i
36
- elsif File.executable?("/usr/sbin/psrinfo")
37
- IO.popen("/usr/sbin/psrinfo").read.scan(/^.*on-*line/).size
38
- elsif File.executable?("/usr/sbin/ioscan")
39
- IO.popen("/usr/sbin/ioscan -kC processor") do |out|
40
- out.read.scan(/^.*processor/).size
41
- end
42
- elsif File.executable?("/usr/sbin/pmcycles")
43
- IO.popen("/usr/sbin/pmcycles -m").read.count("\n")
44
- elsif File.executable?("/usr/sbin/lsdev")
45
- IO.popen("/usr/sbin/lsdev -Cc processor -S 1").read.count("\n")
46
- elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i
47
- IO.popen("/usr/sbin/sysconf NPROC_ONLN").read.to_i
48
- elsif File.executable?("/usr/sbin/sysctl")
49
- IO.popen("/usr/sbin/sysctl -n hw.ncpu").read.to_i
50
- elsif File.executable?("/sbin/sysctl")
51
- IO.popen("/sbin/sysctl -n hw.ncpu").read.to_i
52
- else
53
- $stderr.puts "Unknown platform: " + RbConfig::CONFIG["target_os"]
54
- $stderr.puts "Assuming 1 processor."
55
- 1
56
- end
57
- end
58
- end
9
+ @processor_count ||= Integer(ENV['PARALLEL_PROCESSOR_COUNT'] || Etc.nprocessors)
59
10
  end
60
11
 
61
12
  # Number of physical processor cores on the current system.
62
- #
63
13
  def physical_processor_count
64
14
  @physical_processor_count ||= begin
65
- ppc = case RbConfig::CONFIG["target_os"]
66
- when /darwin1/
67
- IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
68
- when /linux/
69
- cores = {} # unique physical ID / core ID combinations
70
- phy = 0
71
- IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
72
- if ln.start_with?("physical")
73
- phy = ln[/\d+/]
74
- elsif ln.start_with?("core")
75
- cid = phy + ":" + ln[/\d+/]
76
- cores[cid] = true if not cores[cid]
15
+ ppc =
16
+ case RbConfig::CONFIG["target_os"]
17
+ when /darwin[12]/
18
+ IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
19
+ when /linux/
20
+ cores = {} # unique physical ID / core ID combinations
21
+ phy = 0
22
+ IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
23
+ if ln.start_with?("physical")
24
+ phy = ln[/\d+/]
25
+ elsif ln.start_with?("core")
26
+ cid = "#{phy}:#{ln[/\d+/]}"
27
+ cores[cid] = true unless cores[cid]
28
+ end
77
29
  end
30
+ cores.count
31
+ when /mswin|mingw/
32
+ require 'win32ole'
33
+ result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
34
+ "select NumberOfCores from Win32_Processor"
35
+ )
36
+ result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
37
+ else
38
+ processor_count
78
39
  end
79
- cores.count
80
- when /mswin|mingw/
81
- require 'win32ole'
82
- result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
83
- "select NumberOfCores from Win32_Processor")
84
- result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
85
- else
86
- processor_count
87
- end
88
40
  # fall back to logical count if physical info is invalid
89
41
  ppc > 0 ? ppc : processor_count
90
42
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Parallel
2
- VERSION = Version = '1.13.0'
3
+ VERSION = Version = '1.21.0' # rubocop:disable Naming/ConstantName
3
4
  end
data/lib/parallel.rb CHANGED
@@ -1,31 +1,40 @@
1
+ # frozen_string_literal: true
1
2
  require 'rbconfig'
2
3
  require 'parallel/version'
3
4
  require 'parallel/processor_count'
4
5
 
5
6
  module Parallel
6
- extend Parallel::ProcessorCount
7
+ extend ProcessorCount
8
+
9
+ Stop = Object.new.freeze
7
10
 
8
11
  class DeadWorker < StandardError
9
12
  end
10
13
 
11
14
  class Break < StandardError
15
+ attr_reader :value
16
+
17
+ def initialize(value = nil)
18
+ super()
19
+ @value = value
20
+ end
12
21
  end
13
22
 
14
- class Kill < StandardError
23
+ class Kill < Break
15
24
  end
16
25
 
17
26
  class UndumpableException < StandardError
18
27
  attr_reader :backtrace
28
+
19
29
  def initialize(original)
20
30
  super "#{original.class}: #{original.message}"
21
31
  @backtrace = original.backtrace
22
32
  end
23
33
  end
24
34
 
25
- Stop = Object.new
26
-
27
35
  class ExceptionWrapper
28
36
  attr_reader :exception
37
+
29
38
  def initialize(exception)
30
39
  # Remove the bindings stack added by the better_errors gem,
31
40
  # because it cannot be marshalled
@@ -36,7 +45,7 @@ module Parallel
36
45
  @exception =
37
46
  begin
38
47
  Marshal.dump(exception) && exception
39
- rescue
48
+ rescue StandardError
40
49
  UndumpableException.new(exception)
41
50
  end
42
51
  end
@@ -45,8 +54,11 @@ module Parallel
45
54
  class Worker
46
55
  attr_reader :pid, :read, :write
47
56
  attr_accessor :thread
57
+
48
58
  def initialize(read, write, pid)
49
- @read, @write, @pid = read, write, pid
59
+ @read = read
60
+ @write = write
61
+ @pid = pid
50
62
  end
51
63
 
52
64
  def stop
@@ -73,7 +85,7 @@ module Parallel
73
85
  rescue EOFError
74
86
  raise DeadWorker
75
87
  end
76
- raise result.exception if ExceptionWrapper === result
88
+ raise result.exception if result.is_a?(ExceptionWrapper)
77
89
  result
78
90
  end
79
91
 
@@ -102,7 +114,7 @@ module Parallel
102
114
  item, index = @mutex.synchronize do
103
115
  return if @stopped
104
116
  item = @lambda.call
105
- @stopped = (item == Parallel::Stop)
117
+ @stopped = (item == Stop)
106
118
  return if @stopped
107
119
  [item, @index += 1]
108
120
  end
@@ -140,7 +152,7 @@ module Parallel
140
152
  end
141
153
 
142
154
  def queue_wrapper(array)
143
- array.respond_to?(:num_waiting) && array.respond_to?(:pop) && lambda { array.pop(false) }
155
+ array.respond_to?(:num_waiting) && array.respond_to?(:pop) && -> { array.pop(false) }
144
156
  end
145
157
  end
146
158
 
@@ -156,7 +168,7 @@ module Parallel
156
168
 
157
169
  if @to_be_killed.empty?
158
170
  old_interrupt = trap_interrupt(signal) do
159
- $stderr.puts 'Parallel execution interrupted, exiting ...'
171
+ warn 'Parallel execution interrupted, exiting ...'
160
172
  @to_be_killed.flatten.each { |pid| kill(pid) }
161
173
  end
162
174
  end
@@ -200,35 +212,44 @@ module Parallel
200
212
  end
201
213
 
202
214
  class << self
203
- def in_threads(options={:count => 2})
204
- count, _ = extract_count_from_options(options)
205
- Array.new(count) do |i|
206
- Thread.new { yield(i) }
207
- end.map!(&:value)
215
+ def in_threads(options = { count: 2 })
216
+ threads = []
217
+ count, = extract_count_from_options(options)
218
+
219
+ Thread.handle_interrupt(Exception => :never) do
220
+ Thread.handle_interrupt(Exception => :immediate) do
221
+ count.times do |i|
222
+ threads << Thread.new { yield(i) }
223
+ end
224
+ threads.map(&:value)
225
+ end
226
+ ensure
227
+ threads.each(&:kill)
228
+ end
208
229
  end
209
230
 
210
231
  def in_processes(options = {}, &block)
211
232
  count, options = extract_count_from_options(options)
212
233
  count ||= processor_count
213
- map(0...count, options.merge(:in_processes => count), &block)
234
+ map(0...count, options.merge(in_processes: count), &block)
214
235
  end
215
236
 
216
- def each(array, options={}, &block)
217
- map(array, options.merge(:preserve_results => false), &block)
237
+ def each(array, options = {}, &block)
238
+ map(array, options.merge(preserve_results: false), &block)
218
239
  end
219
240
 
220
241
  def any?(*args, &block)
221
242
  raise "You must provide a block when calling #any?" if block.nil?
222
- !each(*args) { |*a| raise Parallel::Kill if block.call(*a) }
243
+ !each(*args) { |*a| raise Kill if block.call(*a) }
223
244
  end
224
245
 
225
246
  def all?(*args, &block)
226
247
  raise "You must provide a block when calling #all?" if block.nil?
227
- !!each(*args) { |*a| raise Parallel::Kill unless block.call(*a) }
248
+ !!each(*args) { |*a| raise Kill unless block.call(*a) }
228
249
  end
229
250
 
230
- def each_with_index(array, options={}, &block)
231
- each(array, options.merge(:with_index => true), &block)
251
+ def each_with_index(array, options = {}, &block)
252
+ each(array, options.merge(with_index: true), &block)
232
253
  end
233
254
 
234
255
  def map(source, options = {}, &block)
@@ -236,8 +257,8 @@ module Parallel
236
257
  options[:mutex] = Mutex.new
237
258
 
238
259
  if options[:in_processes] && options[:in_threads]
239
- raise ArgumentError.new("Please specify only one of `in_processes` or `in_threads`.")
240
- elsif RUBY_PLATFORM =~ /java/ and not options[:in_processes]
260
+ raise ArgumentError, "Please specify only one of `in_processes` or `in_threads`."
261
+ elsif RUBY_PLATFORM =~ (/java/) && !(options[:in_processes])
241
262
  method = :in_threads
242
263
  size = options[method] || processor_count
243
264
  elsif options[:in_threads]
@@ -259,26 +280,33 @@ module Parallel
259
280
  options[:return_results] = (options[:preserve_results] != false || !!options[:finish])
260
281
  add_progress_bar!(job_factory, options)
261
282
 
262
- results = if size == 0
263
- work_direct(job_factory, options, &block)
264
- elsif method == :in_threads
265
- work_in_threads(job_factory, options.merge(:count => size), &block)
266
- else
267
- work_in_processes(job_factory, options.merge(:count => size), &block)
268
- end
269
- if results
270
- options[:return_results] ? results : source
271
- end
283
+ result =
284
+ if size == 0
285
+ work_direct(job_factory, options, &block)
286
+ elsif method == :in_threads
287
+ work_in_threads(job_factory, options.merge(count: size), &block)
288
+ else
289
+ work_in_processes(job_factory, options.merge(count: size), &block)
290
+ end
291
+
292
+ return result.value if result.is_a?(Break)
293
+ raise result if result.is_a?(Exception)
294
+ options[:return_results] ? result : source
295
+ end
296
+
297
+ def map_with_index(array, options = {}, &block)
298
+ map(array, options.merge(with_index: true), &block)
272
299
  end
273
300
 
274
- def map_with_index(array, options={}, &block)
275
- map(array, options.merge(:with_index => true), &block)
301
+ def flat_map(*args, &block)
302
+ map(*args, &block).flatten(1)
276
303
  end
277
304
 
278
305
  def worker_number
279
306
  Thread.current[:parallel_worker_number]
280
307
  end
281
308
 
309
+ # TODO: this does not work when doing threads in forks, so should remove and yield the number instead if needed
282
310
  def worker_number=(worker_num)
283
311
  Thread.current[:parallel_worker_number] = worker_num
284
312
  end
@@ -321,10 +349,10 @@ module Parallel
321
349
  call_with_index(item, index, options, &block)
322
350
  end
323
351
  end
324
- rescue
352
+ rescue StandardError
325
353
  exception = $!
326
354
  end
327
- handle_exception(exception, results)
355
+ exception || results
328
356
  ensure
329
357
  self.worker_number = nil
330
358
  end
@@ -345,21 +373,17 @@ module Parallel
345
373
  call_with_index(item, index, options, &block)
346
374
  end
347
375
  results_mutex.synchronize { results[index] = result }
348
- rescue
376
+ rescue StandardError
349
377
  exception = $!
350
378
  end
351
379
  end
352
380
  end
353
381
 
354
- handle_exception(exception, results)
382
+ exception || results
355
383
  end
356
384
 
357
385
  def work_in_processes(job_factory, options, &blk)
358
- workers = if options[:isolation]
359
- [] # we create workers per job and not beforehand
360
- else
361
- create_workers(job_factory, options, &blk)
362
- end
386
+ workers = create_workers(job_factory, options, &blk)
363
387
  results = []
364
388
  results_mutex = Mutex.new # arrays are not thread-safe
365
389
  exception = nil
@@ -367,6 +391,8 @@ module Parallel
367
391
  UserInterruptHandler.kill_on_ctrl_c(workers.map(&:pid), options) do
368
392
  in_threads(options) do |i|
369
393
  worker = workers[i]
394
+ worker.thread = Thread.current
395
+ worked = false
370
396
 
371
397
  begin
372
398
  loop do
@@ -375,44 +401,43 @@ module Parallel
375
401
  break unless index
376
402
 
377
403
  if options[:isolation]
378
- worker = replace_worker(job_factory, workers, i, options, blk)
404
+ worker = replace_worker(job_factory, workers, i, options, blk) if worked
405
+ worked = true
406
+ worker.thread = Thread.current
379
407
  end
380
408
 
381
- worker.thread = Thread.current
382
-
383
409
  begin
384
410
  result = with_instrumentation item, index, options do
385
411
  worker.work(job_factory.pack(item, index))
386
412
  end
387
413
  results_mutex.synchronize { results[index] = result } # arrays are not threads safe on jRuby
388
- rescue
389
- exception = $!
390
- if Parallel::Kill === exception
414
+ rescue StandardError => e
415
+ exception = e
416
+ if exception.is_a?(Kill)
391
417
  (workers - [worker]).each do |w|
392
- w.thread.kill unless w.thread.nil?
418
+ w.thread&.kill
393
419
  UserInterruptHandler.kill(w.pid)
394
420
  end
395
421
  end
396
422
  end
397
423
  end
398
424
  ensure
399
- worker.stop if worker
425
+ worker.stop
400
426
  end
401
427
  end
402
428
  end
403
-
404
- handle_exception(exception, results)
429
+ exception || results
405
430
  end
406
431
 
407
- def replace_worker(job_factory, workers, i, options, blk)
432
+ def replace_worker(job_factory, workers, index, options, blk)
408
433
  options[:mutex].synchronize do
409
434
  # old worker is no longer used ... stop it
410
- worker = workers[i]
411
- worker.stop if worker
435
+ worker = workers[index]
436
+ worker.stop
412
437
 
413
438
  # create a new replacement worker
414
439
  running = workers - [worker]
415
- workers[i] = worker(job_factory, options.merge(started_workers: running, worker_number: i), &blk)
440
+ workers[index] = worker(job_factory, options.merge(started_workers: running, worker_number: index), &blk)
416
441
  end
417
442
  end
418
443
 
@@ -454,21 +479,25 @@ module Parallel
454
479
  until read.eof?
455
480
  data = Marshal.load(read)
456
481
  item, index = job_factory.unpack(data)
457
- result = begin
458
- call_with_index(item, index, options, &block)
459
- rescue
460
- ExceptionWrapper.new($!)
482
+
483
+ result =
484
+ begin
485
+ call_with_index(item, index, options, &block)
486
+ # https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
487
+ rescue NoMemoryError, SignalException, Interrupt, SystemExit # rubocop:disable Lint/ShadowedException
488
+ raise $!
489
+ rescue Exception # # rubocop:disable Lint/RescueException
490
+ ExceptionWrapper.new($!)
491
+ end
492
+
493
+ begin
494
+ Marshal.dump(result, write)
495
+ rescue Errno::EPIPE
496
+ return # parent thread already dead
461
497
  end
462
- Marshal.dump(result, write)
463
498
  end
464
499
  end
465
500
 
466
- def handle_exception(exception, results)
467
- return nil if [Parallel::Break, Parallel::Kill].include? exception.class
468
- raise exception if exception
469
- results
470
- end
471
-
472
501
  # options is either a Integer or a Hash with :count
473
502
  def extract_count_from_options(options)
474
503
  if options.is_a?(Hash)
@@ -483,10 +512,10 @@ module Parallel
483
512
  def call_with_index(item, index, options, &block)
484
513
  args = [item]
485
514
  args << index if options[:with_index]
515
+ results = block.call(*args)
486
516
  if options[:return_results]
487
- block.call(*args)
517
+ results
488
518
  else
489
- block.call(*args)
490
519
  nil # avoid GC overhead of passing large results around
491
520
  end
492
521
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-17 00:00:00.000000000 Z
11
+ date: 2021-09-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: michael@grosser.it
@@ -23,7 +23,11 @@ files:
23
23
  homepage: https://github.com/grosser/parallel
24
24
  licenses:
25
25
  - MIT
26
- metadata: {}
26
+ metadata:
27
+ bug_tracker_uri: https://github.com/grosser/parallel/issues
28
+ documentation_uri: https://github.com/grosser/parallel/blob/v1.21.0/Readme.md
29
+ source_code_uri: https://github.com/grosser/parallel/tree/v1.21.0
30
+ wiki_uri: https://github.com/grosser/parallel/wiki
27
31
  post_install_message:
28
32
  rdoc_options: []
29
33
  require_paths:
@@ -32,15 +36,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
32
36
  requirements:
33
37
  - - ">="
34
38
  - !ruby/object:Gem::Version
35
- version: 1.9.3
39
+ version: '2.5'
36
40
  required_rubygems_version: !ruby/object:Gem::Requirement
37
41
  requirements:
38
42
  - - ">="
39
43
  - !ruby/object:Gem::Version
40
44
  version: '0'
41
45
  requirements: []
42
- rubyforge_project:
43
- rubygems_version: 2.7.6
46
+ rubygems_version: 3.2.16
44
47
  signing_key:
45
48
  specification_version: 4
46
49
  summary: Run any kind of code in parallel processes