parallel 1.13.0 → 1.21.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.
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