parallel 2.1.0 → 2.2.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: 3331bc6634cd376e4f3f8511049e49a0d4501110aaeac395e2f0c0b638f4a402
4
- data.tar.gz: 357e1424cb8297b6472c2c5b9486e26da3969edd928be7d6ca7f21670211a8e0
3
+ metadata.gz: bcd30f5bfe7076b4a0872b389f00057efaae80917298e3d227c160cc8c1be410
4
+ data.tar.gz: d6dd66ee7f75d2e14123c292ce13ac2b448134622c712650c07482986dbd48a3
5
5
  SHA512:
6
- metadata.gz: 1885d4f814023905f76105f0edce9155024478408be851a4f6869e537400e924e03d34196e03b428803c274b5fbf7e9bf2d93f4548782aa6d3015063c7ed7883
7
- data.tar.gz: 97d22f6b0320a089a3584e76c0baa245811861776e7337e913961cd64cfe0770131493d7de7e1355a62d6339d47d0b1f3ad32dd89084f31c69a61fcfab9f3148
6
+ metadata.gz: 11ed4f49a53e9e20d052725911593266b808dfaa7b2392f503d39494b318a16df15ad628b586f93b700f5e22627116c465afff818d61ac2f0ca3aed4dcd1b8c0
7
+ data.tar.gz: af37003c9dc58ae81dac2a80355f5b16db9f54734f06a9c0dc33e73730818f7e81306237f152cd2976399a6453300915b19593e2e6928b480330fa736c516bc6
@@ -24,6 +24,10 @@ module Parallel
24
24
  @secret = secret
25
25
  end
26
26
 
27
+ def inspect
28
+ "#<#{self.class} @inner=#{@inner.inspect}, @secret=[REDACTED]>"
29
+ end
30
+
27
31
  def dump(data, io)
28
32
  payload = @inner.dump(data)
29
33
  mac = OpenSSL::HMAC.digest('SHA256', @secret, payload)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Parallel
3
- VERSION = Version = '2.1.0' # rubocop:disable Naming/ConstantName
3
+ VERSION = Version = '2.2.0' # rubocop:disable Naming/ConstantName
4
4
  end
data/lib/parallel.rb CHANGED
@@ -124,7 +124,7 @@ module Parallel
124
124
  item, index = @mutex.synchronize do
125
125
  return if @stopped
126
126
  item = @lambda.call
127
- @stopped = (item == Stop)
127
+ @stopped = Stop.equal?(item)
128
128
  return if @stopped
129
129
  [item, @index += 1]
130
130
  end
@@ -178,7 +178,7 @@ module Parallel
178
178
 
179
179
  if @to_be_killed.empty?
180
180
  old_interrupt = trap_interrupt(signal) do
181
- warn 'Parallel execution interrupted, exiting ...'
181
+ warn 'parallel: execution interrupted, exiting ...'
182
182
  @to_be_killed.flatten.each { |pid| kill(pid) }
183
183
  end
184
184
  end
@@ -245,7 +245,7 @@ module Parallel
245
245
  end
246
246
 
247
247
  def each(array, options = {}, &block)
248
- map(array, options.merge(preserve_results: false), &block)
248
+ map(array, options.merge(discard_results: true), &block)
249
249
  end
250
250
 
251
251
  def any?(*args, &block)
@@ -266,9 +266,15 @@ module Parallel
266
266
  options = options.dup
267
267
  options[:mutex] = Mutex.new
268
268
 
269
- if options[:in_processes] && options[:in_threads]
270
- raise ArgumentError, "Please specify only one of `in_processes` or `in_threads`."
271
- elsif RUBY_PLATFORM.include?('java') && !options[:in_processes]
269
+ if options.slice(:in_processes, :in_threads, :in_ractors).size > 1
270
+ raise ArgumentError, "Use only one of `in_processes`, `in_threads`, or `in_ractors`."
271
+ end
272
+
273
+ if options[:in_ractors] ? block : options[:ractor]
274
+ raise ArgumentError, "use either in_ractors with :ractor or a not in_ractors and block"
275
+ end
276
+
277
+ if RUBY_PLATFORM.include?('java') && !options[:in_processes]
272
278
  method = :in_threads
273
279
  size = options[method] || processor_count
274
280
  elsif options[:in_threads]
@@ -282,31 +288,38 @@ module Parallel
282
288
  if Process.respond_to?(:fork)
283
289
  size = options[method] || processor_count
284
290
  else
285
- warn "Process.fork is not supported by this Ruby"
291
+ warn "parallel: Process.fork is not supported by this Ruby"
286
292
  size = 0
287
293
  end
288
294
  end
289
295
 
296
+ raise ArgumentError, "worker count must be a non-negative Integer" unless size.is_a?(Integer) && size >= 0
297
+
290
298
  job_factory = JobFactory.new(source, options[:mutex])
291
299
  size = [job_factory.size, size].min
292
300
 
293
- options[:return_results] = (options[:preserve_results] != false || !!options[:finish])
301
+ discard_results = options[:discard_results]
302
+
303
+ # finish callback needs the results, careful to do that before add_progress_bar which adds finish
304
+ options[:discard_results] = discard_results && !options[:finish]
305
+
294
306
  add_progress_bar!(job_factory, options)
295
307
 
296
308
  result =
297
309
  if size == 0
310
+ block = ractor_block(options) if method == :in_ractors
298
311
  work_direct(job_factory, options, &block)
299
312
  elsif method == :in_threads
300
313
  work_in_threads(job_factory, options.merge(count: size), &block)
301
314
  elsif method == :in_ractors
302
- work_in_ractors(job_factory, options.merge(count: size), &block)
315
+ work_in_ractors(job_factory, options.merge(count: size))
303
316
  else
304
317
  work_in_processes(job_factory, options.merge(count: size), &block)
305
318
  end
306
319
 
307
320
  return result.value if result.is_a?(Break)
308
321
  raise result if result.is_a?(Exception)
309
- options[:return_results] ? result : source
322
+ discard_results ? source : result
310
323
  end
311
324
 
312
325
  def map_with_index(array, options = {}, &block)
@@ -318,7 +331,7 @@ module Parallel
318
331
  end
319
332
 
320
333
  def filter_map(...)
321
- map(...).compact
334
+ map(...).select { |value| value }
322
335
  end
323
336
 
324
337
  # Number of physical processor cores on the current system.
@@ -379,7 +392,7 @@ module Parallel
379
392
  end
380
393
  if !result || $?.exitstatus != 0
381
394
  # Bail out if both commands returned something unexpected
382
- warn "guessing pyhsical processor count"
395
+ warn "parallel: guessing physical processor count"
383
396
  processor_count
384
397
  else
385
398
  # powershell: "\nNumberOfCores\n-------------\n 4\n\n\n"
@@ -420,22 +433,24 @@ module Parallel
420
433
  end
421
434
 
422
435
  def work_direct(job_factory, options, &block)
436
+ previous_worker_number = worker_number
423
437
  self.worker_number = 0
424
438
  results = []
425
439
  exception = nil
426
440
  begin
427
441
  while (set = job_factory.next)
428
442
  item, index = set
429
- results << with_instrumentation(item, index, options) do
443
+ result = with_instrumentation(item, index, options) do
430
444
  call_with_index(item, index, options, &block)
431
445
  end
446
+ results << result unless options[:discard_results]
432
447
  end
433
448
  rescue StandardError
434
449
  exception = $!
435
450
  end
436
451
  exception || results
437
452
  ensure
438
- self.worker_number = nil
453
+ self.worker_number = previous_worker_number
439
454
  end
440
455
 
441
456
  def work_in_threads(job_factory, options, &block)
@@ -453,7 +468,7 @@ module Parallel
453
468
  result = with_instrumentation item, index, options do
454
469
  call_with_index(item, index, options, &block)
455
470
  end
456
- results_mutex.synchronize { results[index] = result }
471
+ results_mutex.synchronize { results[index] = result } unless options[:discard_results]
457
472
  rescue StandardError
458
473
  exception = $!
459
474
  end
@@ -482,55 +497,70 @@ module Parallel
482
497
  ports[port] = ractor
483
498
  end
484
499
 
485
- # start
486
- ports.dup.each do |port, ractor|
487
- if (job = job_factory.next)
488
- item, index = job
489
- instrument_start item, index, options
490
- ractor.send [callback, item, index]
491
- else # not enough work, `receive` would hang
492
- ractor_stop ractor
493
- ports.delete port
500
+ begin
501
+ # start by sending 1 item each
502
+ ports.dup.each do |port, ractor|
503
+ if (job = job_factory.next)
504
+ ractor_send ractor, callback, job, options
505
+ else # not enough work, `receive` would hang
506
+ ractor_stop ractor
507
+ ports.delete port
508
+ end
494
509
  end
495
- end
496
510
 
497
- # receive result and send new items to done ractors
498
- while (job = job_factory.next)
499
- # receive result
500
- done_port, (exception, result, item_prev, index_prev) = Ractor.select(*ports.keys)
501
- done_ractor = ports[done_port]
502
- if exception
503
- ports.delete done_port
504
- break
505
- end
506
- ractor_result item_prev, index_prev, result, results, results_mutex, options
511
+ # receive result and send new items
512
+ while (job = job_factory.next)
513
+ # receive result
514
+ done_port, (exception, result, item_prev, index_prev) = Ractor.select(*ports.keys)
515
+ done_ractor = ports[done_port]
516
+ if exception
517
+ ractor_stop done_ractor
518
+ ports.delete done_port
519
+ break
520
+ end
521
+ ractor_result item_prev, index_prev, result, results, results_mutex, options
507
522
 
508
- # send new
509
- item_next, index_next = job
510
- instrument_start item_next, index_next, options
511
- done_ractor.send([callback, item_next, index_next])
512
- end
523
+ # send new
524
+ ractor_send done_ractor, callback, job, options
525
+ end
513
526
 
514
- # finish
515
- ports.each do |port, ractor|
516
- (new_exception, result, item, index) = use_port ? port.receive : ractor.take
517
- exception ||= new_exception
518
- next if new_exception
519
- ractor_result item, index, result, results, results_mutex, options
520
- ractor_stop ractor
527
+ # finish by receiving the last results
528
+ ports.dup.each do |port, ractor|
529
+ (new_exception, result, item, index) = use_port ? port.receive : ractor.take
530
+ exception ||= new_exception
531
+ ractor_result item, index, result, results, results_mutex, options unless new_exception
532
+ ractor_stop ractor
533
+ ports.delete port
534
+ end
535
+ ensure # close any ports that remained open in case something blew up
536
+ ports.each_value do |ractor|
537
+ ractor_stop(ractor)
538
+ ractor.take unless use_port
539
+ rescue Ractor::ClosedError, Ractor::RemoteError
540
+ nil
541
+ end
521
542
  end
522
543
 
523
544
  exception || results
524
545
  end
525
546
 
547
+ # ractors cannot execute blocks, so run the callback directly when not using ractors
548
+ def ractor_block(options)
549
+ klass, method_name = options[:ractor] ||
550
+ raise(ArgumentError, "pass the code you want to execute as `ractor: [ClassName, :method_name]`")
551
+ ->(*args) { klass.send(method_name, *args) }
552
+ end
553
+
526
554
  def ractor_build(use_port)
527
555
  args = use_port ? [Ractor::Port.new] : []
528
556
  ractor = Ractor.new(*args) do |port|
529
557
  loop do
530
- (klass, method_name), item, index = receive
558
+ (klass, method_name), item, index, with_index, discard_results = receive
531
559
  break if index == :break
532
560
  begin
533
- result = [nil, klass.send(method_name, item), item, index]
561
+ value = with_index ? klass.send(method_name, item, index) : klass.send(method_name, item)
562
+ value = nil if discard_results
563
+ result = [nil, value, item, index]
534
564
  rescue StandardError => e
535
565
  result = [e, nil, item, index]
536
566
  end
@@ -546,11 +576,19 @@ module Parallel
546
576
 
547
577
  def ractor_result(item, index, result, results, results_mutex, options)
548
578
  instrument_finish item, index, result, options
549
- results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
579
+ results_mutex.synchronize { results[index] = result } unless options[:discard_results]
580
+ end
581
+
582
+ def ractor_send(ractor, callback, job, options)
583
+ item, index = job
584
+ instrument_start item, index, options
585
+ ractor.send [callback, item, index, options[:with_index], options[:discard_results]]
550
586
  end
551
587
 
552
588
  def ractor_stop(ractor)
553
589
  ractor.send([[nil, nil], nil, :break])
590
+ rescue Ractor::ClosedError
591
+ nil
554
592
  end
555
593
 
556
594
  def work_in_processes(job_factory, options, &blk)
@@ -581,7 +619,8 @@ module Parallel
581
619
  result = with_instrumentation item, index, options do
582
620
  worker.work(job_factory.pack(item, index))
583
621
  end
584
- results_mutex.synchronize { results[index] = result } # arrays are not threads safe on jRuby
622
+ # arrays are not threads safe on jRuby
623
+ results_mutex.synchronize { results[index] = result } unless options[:discard_results]
585
624
  rescue StandardError => e
586
625
  exception = e
587
626
  if exception.is_a?(Kill)
@@ -619,6 +658,13 @@ module Parallel
619
658
  workers << worker(job_factory, options.merge(started_workers: workers, worker_number: i), &block)
620
659
  end
621
660
  workers
661
+ rescue Exception # rubocop:disable Lint/RescueException
662
+ workers.each do |worker|
663
+ worker.stop
664
+ rescue StandardError
665
+ nil
666
+ end
667
+ raise
622
668
  end
623
669
 
624
670
  def worker(job_factory, options, &block)
@@ -646,6 +692,19 @@ module Parallel
646
692
  child_write.close
647
693
 
648
694
  Worker.new(parent_read, parent_write, pid, options[:serializer])
695
+ rescue Exception # rubocop:disable Lint/RescueException
696
+ [child_read, parent_write, parent_read, child_write].compact.each do |io|
697
+ io.close unless io.closed?
698
+ end
699
+ if pid
700
+ UserInterruptHandler.kill(pid)
701
+ begin
702
+ Process.wait(pid)
703
+ rescue Errno::ECHILD
704
+ nil
705
+ end
706
+ end
707
+ raise
649
708
  end
650
709
 
651
710
  def process_incoming_jobs(read, write, job_factory, options, &block)
@@ -687,10 +746,10 @@ module Parallel
687
746
  args = [item]
688
747
  args << index if options[:with_index]
689
748
  results = block.call(*args)
690
- if options[:return_results]
691
- results
692
- else
749
+ if options[:discard_results]
693
750
  nil # avoid GC overhead of passing large results around
751
+ else
752
+ results
694
753
  end
695
754
  end
696
755
 
@@ -698,7 +757,7 @@ module Parallel
698
757
  instrument_start(item, index, options)
699
758
  result = yield
700
759
  instrument_finish(item, index, result, options)
701
- result unless options[:preserve_results] == false
760
+ result unless options[:discard_results]
702
761
  end
703
762
 
704
763
  def instrument_finish(item, index, result, options)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
@@ -23,10 +23,10 @@ licenses:
23
23
  - MIT
24
24
  metadata:
25
25
  bug_tracker_uri: https://github.com/grosser/parallel/issues
26
- documentation_uri: https://github.com/grosser/parallel/blob/v2.1.0/Readme.md
27
- source_code_uri: https://github.com/grosser/parallel/tree/v2.1.0
26
+ documentation_uri: https://github.com/grosser/parallel/blob/v2.2.0/Readme.md
27
+ source_code_uri: https://github.com/grosser/parallel/tree/v2.2.0
28
28
  wiki_uri: https://github.com/grosser/parallel/wiki
29
- changelog_uri: https://github.com/grosser/parallel/blob/v2.1.0/CHANGELOG.md
29
+ changelog_uri: https://github.com/grosser/parallel/blob/v2.2.0/CHANGELOG.md
30
30
  rubygems_mfa_required: 'true'
31
31
  rdoc_options: []
32
32
  require_paths: