parallel 1.19.1 → 1.22.1

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: 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