parallel 1.19.1 → 1.22.1

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: da9740ffe51b7c2867eb4a51b9477aa0195c475ba27342793a3a6f7aa209b9ab
4
- data.tar.gz: cc98ae0e7c35557d064abf720263258a0340c30ec405d30342636f1667fc32c1
3
+ metadata.gz: 7efbfe49c3df93ae88464b4bd4ce85d1aaccc9c173e1a81e7cf9100fe182982c
4
+ data.tar.gz: 7e82ff83bd44c96da760d83ab665baed41bc62d53f010987951c805cf41723e4
5
5
  SHA512:
6
- metadata.gz: 9c71c1888523f4d9ed6c3050c8c06d425330d955c5bc30be331f93a46b346984b489ba78ded574bb7197a3c9ac34076924e90d21765085d6fdfc5dce1d0ef27f
7
- data.tar.gz: 458976b6feaf2e34201ffb06a855940581eafe97306c638ccb2e583f8ed975a174a56e482a84e7d6a2c2a806ee94cf23adf66d43fc48b133433d275fea872d58
6
+ metadata.gz: a81cae96dd4dcfa1d287ce3e5893c830de3cb8e305236db4170db81f94de021f187da0d21462b7df5e99310002c14984fbb00aef818936c13d0ae834c1c1b2e7
7
+ data.tar.gz: 79c099a5f1f8bdad3d9f187829213d242087779605f14fb421f0172c1de1a8f853b4f13045935410a989d845d08e736e9552c8e3b9a6c3763d2dfdd27ede7daf
@@ -1,39 +1,41 @@
1
- require 'etc'
2
-
1
+ # frozen_string_literal: true
3
2
  module Parallel
4
3
  # TODO: inline this method into parallel.rb and kill physical_processor_count in next major release
5
4
  module ProcessorCount
6
5
  # Number of processors seen by the OS, used for process scheduling
7
6
  def processor_count
7
+ require 'etc'
8
8
  @processor_count ||= Integer(ENV['PARALLEL_PROCESSOR_COUNT'] || Etc.nprocessors)
9
9
  end
10
10
 
11
11
  # Number of physical processor cores on the current system.
12
12
  def physical_processor_count
13
13
  @physical_processor_count ||= begin
14
- ppc = case RbConfig::CONFIG["target_os"]
15
- when /darwin1/
16
- IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
17
- when /linux/
18
- cores = {} # unique physical ID / core ID combinations
19
- phy = 0
20
- IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
21
- if ln.start_with?("physical")
22
- phy = ln[/\d+/]
23
- elsif ln.start_with?("core")
24
- cid = phy + ":" + ln[/\d+/]
25
- cores[cid] = true if not cores[cid]
14
+ ppc =
15
+ case RbConfig::CONFIG["target_os"]
16
+ when /darwin[12]/
17
+ IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
18
+ when /linux/
19
+ cores = {} # unique physical ID / core ID combinations
20
+ phy = 0
21
+ File.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
22
+ if ln.start_with?("physical")
23
+ phy = ln[/\d+/]
24
+ elsif ln.start_with?("core")
25
+ cid = "#{phy}:#{ln[/\d+/]}"
26
+ cores[cid] = true unless cores[cid]
27
+ end
26
28
  end
29
+ cores.count
30
+ when /mswin|mingw/
31
+ require 'win32ole'
32
+ result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
33
+ "select NumberOfCores from Win32_Processor"
34
+ )
35
+ result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
36
+ else
37
+ processor_count
27
38
  end
28
- cores.count
29
- when /mswin|mingw/
30
- require 'win32ole'
31
- result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
32
- "select NumberOfCores from Win32_Processor")
33
- result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
34
- else
35
- processor_count
36
- end
37
39
  # fall back to logical count if physical info is invalid
38
40
  ppc > 0 ? ppc : processor_count
39
41
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Parallel
2
- VERSION = Version = '1.19.1'
3
+ VERSION = Version = '1.22.1' # 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.freeze
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,45 +212,44 @@ module Parallel
200
212
  end
201
213
 
202
214
  class << self
203
- def in_threads(options={:count => 2})
215
+ def in_threads(options = { count: 2 })
216
+ threads = []
217
+ count, = extract_count_from_options(options)
218
+
204
219
  Thread.handle_interrupt(Exception => :never) do
205
- begin
206
- threads = []
207
- count, _ = extract_count_from_options(options)
220
+ Thread.handle_interrupt(Exception => :immediate) do
208
221
  count.times do |i|
209
222
  threads << Thread.new { yield(i) }
210
223
  end
211
- Thread.handle_interrupt(Exception => :immediate) do
212
- threads.map(&:value)
213
- end
214
- ensure
215
- threads.each(&:kill)
224
+ threads.map(&:value)
216
225
  end
226
+ ensure
227
+ threads.each(&:kill)
217
228
  end
218
229
  end
219
230
 
220
231
  def in_processes(options = {}, &block)
221
232
  count, options = extract_count_from_options(options)
222
233
  count ||= processor_count
223
- map(0...count, options.merge(:in_processes => count), &block)
234
+ map(0...count, options.merge(in_processes: count), &block)
224
235
  end
225
236
 
226
- def each(array, options={}, &block)
227
- map(array, options.merge(:preserve_results => false), &block)
237
+ def each(array, options = {}, &block)
238
+ map(array, options.merge(preserve_results: false), &block)
228
239
  end
229
240
 
230
241
  def any?(*args, &block)
231
242
  raise "You must provide a block when calling #any?" if block.nil?
232
- !each(*args) { |*a| raise Parallel::Kill if block.call(*a) }
243
+ !each(*args) { |*a| raise Kill if block.call(*a) }
233
244
  end
234
245
 
235
246
  def all?(*args, &block)
236
247
  raise "You must provide a block when calling #all?" if block.nil?
237
- !!each(*args) { |*a| raise Parallel::Kill unless block.call(*a) }
248
+ !!each(*args) { |*a| raise Kill unless block.call(*a) }
238
249
  end
239
250
 
240
- def each_with_index(array, options={}, &block)
241
- 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)
242
253
  end
243
254
 
244
255
  def map(source, options = {}, &block)
@@ -246,13 +257,16 @@ module Parallel
246
257
  options[:mutex] = Mutex.new
247
258
 
248
259
  if options[:in_processes] && options[:in_threads]
249
- raise ArgumentError.new("Please specify only one of `in_processes` or `in_threads`.")
250
- 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])
251
262
  method = :in_threads
252
263
  size = options[method] || processor_count
253
264
  elsif options[:in_threads]
254
265
  method = :in_threads
255
266
  size = options[method]
267
+ elsif options[:in_ractors]
268
+ method = :in_ractors
269
+ size = options[method]
256
270
  else
257
271
  method = :in_processes
258
272
  if Process.respond_to?(:fork)
@@ -269,20 +283,24 @@ module Parallel
269
283
  options[:return_results] = (options[:preserve_results] != false || !!options[:finish])
270
284
  add_progress_bar!(job_factory, options)
271
285
 
272
- results = if size == 0
273
- work_direct(job_factory, options, &block)
274
- elsif method == :in_threads
275
- work_in_threads(job_factory, options.merge(:count => size), &block)
276
- else
277
- work_in_processes(job_factory, options.merge(:count => size), &block)
278
- end
279
- if results
280
- options[:return_results] ? results : source
281
- end
286
+ result =
287
+ if size == 0
288
+ work_direct(job_factory, options, &block)
289
+ elsif method == :in_threads
290
+ work_in_threads(job_factory, options.merge(count: size), &block)
291
+ elsif method == :in_ractors
292
+ work_in_ractors(job_factory, options.merge(count: size), &block)
293
+ else
294
+ work_in_processes(job_factory, options.merge(count: size), &block)
295
+ end
296
+
297
+ return result.value if result.is_a?(Break)
298
+ raise result if result.is_a?(Exception)
299
+ options[:return_results] ? result : source
282
300
  end
283
301
 
284
- def map_with_index(array, options={}, &block)
285
- map(array, options.merge(:with_index => true), &block)
302
+ def map_with_index(array, options = {}, &block)
303
+ map(array, options.merge(with_index: true), &block)
286
304
  end
287
305
 
288
306
  def flat_map(*args, &block)
@@ -336,10 +354,10 @@ module Parallel
336
354
  call_with_index(item, index, options, &block)
337
355
  end
338
356
  end
339
- rescue
357
+ rescue StandardError
340
358
  exception = $!
341
359
  end
342
- handle_exception(exception, results)
360
+ exception || results
343
361
  ensure
344
362
  self.worker_number = nil
345
363
  end
@@ -360,13 +378,79 @@ module Parallel
360
378
  call_with_index(item, index, options, &block)
361
379
  end
362
380
  results_mutex.synchronize { results[index] = result }
363
- rescue
381
+ rescue StandardError
364
382
  exception = $!
365
383
  end
366
384
  end
367
385
  end
368
386
 
369
- handle_exception(exception, results)
387
+ exception || results
388
+ end
389
+
390
+ def work_in_ractors(job_factory, options)
391
+ exception = nil
392
+ results = []
393
+ results_mutex = Mutex.new # arrays are not thread-safe on jRuby
394
+
395
+ callback = options[:ractor]
396
+ if block_given? || !callback
397
+ raise ArgumentError, "pass the code you want to execute as `ractor: [ClassName, :method_name]`"
398
+ end
399
+
400
+ # build
401
+ ractors = Array.new(options.fetch(:count)) do
402
+ Ractor.new do
403
+ loop do
404
+ got = receive
405
+ (klass, method_name), item, index = got
406
+ break if index == :break
407
+ begin
408
+ Ractor.yield [nil, klass.send(method_name, item), item, index]
409
+ rescue StandardError => e
410
+ Ractor.yield [e, nil, item, index]
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ # start
417
+ ractors.dup.each do |ractor|
418
+ if set = job_factory.next
419
+ item, index = set
420
+ instrument_start item, index, options
421
+ ractor.send [callback, item, index]
422
+ else
423
+ ractor.send([[nil, nil], nil, :break]) # stop the ractor
424
+ ractors.delete ractor
425
+ end
426
+ end
427
+
428
+ # replace with new items
429
+ while set = job_factory.next
430
+ item_next, index_next = set
431
+ done, (exception, result, item, index) = Ractor.select(*ractors)
432
+ if exception
433
+ ractors.delete done
434
+ break
435
+ end
436
+ instrument_finish item, index, result, options
437
+ results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
438
+
439
+ instrument_start item_next, index_next, options
440
+ done.send([callback, item_next, index_next])
441
+ end
442
+
443
+ # finish
444
+ ractors.each do |ractor|
445
+ (new_exception, result, item, index) = ractor.take
446
+ exception ||= new_exception
447
+ next if new_exception
448
+ instrument_finish item, index, result, options
449
+ results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
450
+ ractor.send([[nil, nil], nil, :break]) # stop the ractor
451
+ end
452
+
453
+ exception || results
370
454
  end
371
455
 
372
456
  def work_in_processes(job_factory, options, &blk)
@@ -400,9 +484,9 @@ module Parallel
400
484
  results_mutex.synchronize { results[index] = result } # arrays are not threads safe on jRuby
401
485
  rescue StandardError => e
402
486
  exception = e
403
- if Parallel::Kill === exception
487
+ if exception.is_a?(Kill)
404
488
  (workers - [worker]).each do |w|
405
- w.thread.kill if w.thread
489
+ w.thread&.kill
406
490
  UserInterruptHandler.kill(w.pid)
407
491
  end
408
492
  end
@@ -414,18 +498,18 @@ module Parallel
414
498
  end
415
499
  end
416
500
 
417
- handle_exception(exception, results)
501
+ exception || results
418
502
  end
419
503
 
420
- def replace_worker(job_factory, workers, i, options, blk)
504
+ def replace_worker(job_factory, workers, index, options, blk)
421
505
  options[:mutex].synchronize do
422
506
  # old worker is no longer used ... stop it
423
- worker = workers[i]
507
+ worker = workers[index]
424
508
  worker.stop
425
509
 
426
510
  # create a new replacement worker
427
511
  running = workers - [worker]
428
- workers[i] = worker(job_factory, options.merge(started_workers: running, worker_number: i), &blk)
512
+ workers[index] = worker(job_factory, options.merge(started_workers: running, worker_number: index), &blk)
429
513
  end
430
514
  end
431
515
 
@@ -467,14 +551,17 @@ module Parallel
467
551
  until read.eof?
468
552
  data = Marshal.load(read)
469
553
  item, index = job_factory.unpack(data)
470
- result = begin
471
- call_with_index(item, index, options, &block)
472
- # https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
473
- rescue NoMemoryError, SignalException, Interrupt, SystemExit
474
- raise $!
475
- rescue Exception
476
- ExceptionWrapper.new($!)
477
- end
554
+
555
+ result =
556
+ begin
557
+ call_with_index(item, index, options, &block)
558
+ # https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
559
+ rescue NoMemoryError, SignalException, Interrupt, SystemExit # rubocop:disable Lint/ShadowedException
560
+ raise $!
561
+ rescue Exception # # rubocop:disable Lint/RescueException
562
+ ExceptionWrapper.new($!)
563
+ end
564
+
478
565
  begin
479
566
  Marshal.dump(result, write)
480
567
  rescue Errno::EPIPE
@@ -483,12 +570,6 @@ module Parallel
483
570
  end
484
571
  end
485
572
 
486
- def handle_exception(exception, results)
487
- return nil if [Parallel::Break, Parallel::Kill].include? exception.class
488
- raise exception if exception
489
- results
490
- end
491
-
492
573
  # options is either a Integer or a Hash with :count
493
574
  def extract_count_from_options(options)
494
575
  if options.is_a?(Hash)
@@ -503,21 +584,29 @@ module Parallel
503
584
  def call_with_index(item, index, options, &block)
504
585
  args = [item]
505
586
  args << index if options[:with_index]
587
+ results = block.call(*args)
506
588
  if options[:return_results]
507
- block.call(*args)
589
+ results
508
590
  else
509
- block.call(*args)
510
591
  nil # avoid GC overhead of passing large results around
511
592
  end
512
593
  end
513
594
 
514
595
  def with_instrumentation(item, index, options)
515
- on_start = options[:start]
516
- on_finish = options[:finish]
517
- options[:mutex].synchronize { on_start.call(item, index) } if on_start
596
+ instrument_start(item, index, options)
518
597
  result = yield
519
- options[:mutex].synchronize { on_finish.call(item, index, result) } if on_finish
598
+ instrument_finish(item, index, result, options)
520
599
  result unless options[:preserve_results] == false
521
600
  end
601
+
602
+ def instrument_finish(item, index, result, options)
603
+ return unless on_finish = options[:finish]
604
+ options[:mutex].synchronize { on_finish.call(item, index, result) }
605
+ end
606
+
607
+ def instrument_start(item, index, options)
608
+ return unless on_start = options[:start]
609
+ options[:mutex].synchronize { on_start.call(item, index) }
610
+ end
522
611
  end
523
612
  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.19.1
4
+ version: 1.22.1
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-11-22 00:00:00.000000000 Z
11
+ date: 2022-03-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: michael@grosser.it
@@ -25,8 +25,8 @@ licenses:
25
25
  - MIT
26
26
  metadata:
27
27
  bug_tracker_uri: https://github.com/grosser/parallel/issues
28
- documentation_uri: https://github.com/grosser/parallel/blob/v1.19.1/Readme.md
29
- source_code_uri: https://github.com/grosser/parallel/tree/v1.19.1
28
+ documentation_uri: https://github.com/grosser/parallel/blob/v1.22.1/Readme.md
29
+ source_code_uri: https://github.com/grosser/parallel/tree/v1.22.1
30
30
  wiki_uri: https://github.com/grosser/parallel/wiki
31
31
  post_install_message:
32
32
  rdoc_options: []
@@ -36,14 +36,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '2.2'
39
+ version: '2.5'
40
40
  required_rubygems_version: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
44
  version: '0'
45
45
  requirements: []
46
- rubygems_version: 3.0.3
46
+ rubygems_version: 3.1.6
47
47
  signing_key:
48
48
  specification_version: 4
49
49
  summary: Run any kind of code in parallel processes