parallel 1.28.0 → 2.1.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 +4 -4
- data/lib/parallel/serializer.rb +52 -0
- data/lib/parallel/version.rb +1 -1
- data/lib/parallel.rb +68 -40
- metadata +9 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3331bc6634cd376e4f3f8511049e49a0d4501110aaeac395e2f0c0b638f4a402
|
|
4
|
+
data.tar.gz: 357e1424cb8297b6472c2c5b9486e26da3969edd928be7d6ca7f21670211a8e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1885d4f814023905f76105f0edce9155024478408be851a4f6869e537400e924e03d34196e03b428803c274b5fbf7e9bf2d93f4548782aa6d3015063c7ed7883
|
|
7
|
+
data.tar.gz: 97d22f6b0320a089a3584e76c0baa245811861776e7337e913961cd64cfe0770131493d7de7e1355a62d6339d47d0b1f3ad32dd89084f31c69a61fcfab9f3148
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Parallel
|
|
6
|
+
# Pluggable wire serializers. Each must respond to `dump(data, io)` /
|
|
7
|
+
# `load(io)` (used directly by Worker) and `dump(data)` / `load(string)`
|
|
8
|
+
# (used by wrappers like Hmac).
|
|
9
|
+
module Serializer
|
|
10
|
+
# Raw Marshal. Fast but trusts anything written to the pipe — a same-UID
|
|
11
|
+
# attacker that reopens /proc/<pid>/fd/<n> can inject Marshal gadgets (RCE).
|
|
12
|
+
Marshal = ::Marshal
|
|
13
|
+
|
|
14
|
+
# Wraps any inner serializer with a length-prefixed HMAC-SHA256 frame keyed
|
|
15
|
+
# on a per-worker secret generated before fork. Forged frames from a
|
|
16
|
+
# pipe-injector fail verification.
|
|
17
|
+
class Hmac
|
|
18
|
+
LENGTH_FORMAT = 'N' # 32-bit big-endian unsigned int
|
|
19
|
+
LENGTH_BYTES = 4
|
|
20
|
+
MAC_BYTES = 32 # SHA256
|
|
21
|
+
|
|
22
|
+
def initialize(inner: Marshal, secret: SecureRandom.bytes(32))
|
|
23
|
+
@inner = inner
|
|
24
|
+
@secret = secret
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dump(data, io)
|
|
28
|
+
payload = @inner.dump(data)
|
|
29
|
+
mac = OpenSSL::HMAC.digest('SHA256', @secret, payload)
|
|
30
|
+
io.write([payload.bytesize].pack(LENGTH_FORMAT), mac, payload)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def load(io)
|
|
34
|
+
# nil at frame boundary = clean EOF (worker died / pipe closed between messages)
|
|
35
|
+
header = io.read(LENGTH_BYTES) || raise(EOFError) # eof stops worker
|
|
36
|
+
raise SecurityError, "truncated frame header" if header.bytesize != LENGTH_BYTES
|
|
37
|
+
|
|
38
|
+
length = header.unpack1(LENGTH_FORMAT)
|
|
39
|
+
mac = io.read(MAC_BYTES)
|
|
40
|
+
raise SecurityError, "truncated frame mac" if mac.nil? || mac.bytesize != MAC_BYTES
|
|
41
|
+
|
|
42
|
+
payload = io.read(length)
|
|
43
|
+
raise SecurityError, "truncated frame payload" if payload.nil? || payload.bytesize != length
|
|
44
|
+
|
|
45
|
+
expected = OpenSSL::HMAC.digest('SHA256', @secret, payload)
|
|
46
|
+
raise SecurityError, "HMAC mismatch on worker pipe" unless OpenSSL.fixed_length_secure_compare(mac, expected)
|
|
47
|
+
|
|
48
|
+
@inner.load(payload)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/parallel/version.rb
CHANGED
data/lib/parallel.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'rbconfig'
|
|
3
3
|
require 'parallel/version'
|
|
4
|
+
require 'parallel/serializer'
|
|
4
5
|
|
|
5
6
|
module Parallel
|
|
6
7
|
Stop = Object.new.freeze
|
|
@@ -63,10 +64,11 @@ module Parallel
|
|
|
63
64
|
attr_reader :pid, :read, :write
|
|
64
65
|
attr_accessor :thread
|
|
65
66
|
|
|
66
|
-
def initialize(read, write, pid)
|
|
67
|
+
def initialize(read, write, pid, serializer)
|
|
67
68
|
@read = read
|
|
68
69
|
@write = write
|
|
69
70
|
@pid = pid
|
|
71
|
+
@serializer = serializer
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
def stop
|
|
@@ -83,13 +85,13 @@ module Parallel
|
|
|
83
85
|
|
|
84
86
|
def work(data)
|
|
85
87
|
begin
|
|
86
|
-
|
|
88
|
+
@serializer.dump(data, write)
|
|
87
89
|
rescue Errno::EPIPE
|
|
88
90
|
raise DeadWorker
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
result = begin
|
|
92
|
-
|
|
94
|
+
@serializer.load(read)
|
|
93
95
|
rescue EOFError
|
|
94
96
|
raise DeadWorker
|
|
95
97
|
end
|
|
@@ -266,7 +268,7 @@ module Parallel
|
|
|
266
268
|
|
|
267
269
|
if options[:in_processes] && options[:in_threads]
|
|
268
270
|
raise ArgumentError, "Please specify only one of `in_processes` or `in_threads`."
|
|
269
|
-
elsif RUBY_PLATFORM
|
|
271
|
+
elsif RUBY_PLATFORM.include?('java') && !options[:in_processes]
|
|
270
272
|
method = :in_threads
|
|
271
273
|
size = options[method] || processor_count
|
|
272
274
|
elsif options[:in_threads]
|
|
@@ -471,62 +473,86 @@ module Parallel
|
|
|
471
473
|
raise ArgumentError, "pass the code you want to execute as `ractor: [ClassName, :method_name]`"
|
|
472
474
|
end
|
|
473
475
|
|
|
476
|
+
use_port = defined?(Ractor::Port)
|
|
477
|
+
|
|
474
478
|
# build
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
(klass, method_name), item, index = got
|
|
480
|
-
break if index == :break
|
|
481
|
-
begin
|
|
482
|
-
Ractor.yield [nil, klass.send(method_name, item), item, index]
|
|
483
|
-
rescue StandardError => e
|
|
484
|
-
Ractor.yield [e, nil, item, index]
|
|
485
|
-
end
|
|
486
|
-
end
|
|
487
|
-
end
|
|
479
|
+
ports = {} # port (ruby 4+) or ractor (ruby 3) => ractor
|
|
480
|
+
options.fetch(:count).times do
|
|
481
|
+
port, ractor = ractor_build(use_port)
|
|
482
|
+
ports[port] = ractor
|
|
488
483
|
end
|
|
489
484
|
|
|
490
485
|
# start
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
item, index =
|
|
486
|
+
ports.dup.each do |port, ractor|
|
|
487
|
+
if (job = job_factory.next)
|
|
488
|
+
item, index = job
|
|
494
489
|
instrument_start item, index, options
|
|
495
490
|
ractor.send [callback, item, index]
|
|
496
|
-
else
|
|
497
|
-
|
|
498
|
-
|
|
491
|
+
else # not enough work, `receive` would hang
|
|
492
|
+
ractor_stop ractor
|
|
493
|
+
ports.delete port
|
|
499
494
|
end
|
|
500
495
|
end
|
|
501
496
|
|
|
502
|
-
#
|
|
503
|
-
while (
|
|
504
|
-
|
|
505
|
-
|
|
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]
|
|
506
502
|
if exception
|
|
507
|
-
|
|
503
|
+
ports.delete done_port
|
|
508
504
|
break
|
|
509
505
|
end
|
|
510
|
-
|
|
511
|
-
results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
|
|
506
|
+
ractor_result item_prev, index_prev, result, results, results_mutex, options
|
|
512
507
|
|
|
508
|
+
# send new
|
|
509
|
+
item_next, index_next = job
|
|
513
510
|
instrument_start item_next, index_next, options
|
|
514
|
-
|
|
511
|
+
done_ractor.send([callback, item_next, index_next])
|
|
515
512
|
end
|
|
516
513
|
|
|
517
514
|
# finish
|
|
518
|
-
|
|
519
|
-
(new_exception, result, item, index) = ractor.take
|
|
515
|
+
ports.each do |port, ractor|
|
|
516
|
+
(new_exception, result, item, index) = use_port ? port.receive : ractor.take
|
|
520
517
|
exception ||= new_exception
|
|
521
518
|
next if new_exception
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
ractor.send([[nil, nil], nil, :break]) # stop the ractor
|
|
519
|
+
ractor_result item, index, result, results, results_mutex, options
|
|
520
|
+
ractor_stop ractor
|
|
525
521
|
end
|
|
526
522
|
|
|
527
523
|
exception || results
|
|
528
524
|
end
|
|
529
525
|
|
|
526
|
+
def ractor_build(use_port)
|
|
527
|
+
args = use_port ? [Ractor::Port.new] : []
|
|
528
|
+
ractor = Ractor.new(*args) do |port|
|
|
529
|
+
loop do
|
|
530
|
+
(klass, method_name), item, index = receive
|
|
531
|
+
break if index == :break
|
|
532
|
+
begin
|
|
533
|
+
result = [nil, klass.send(method_name, item), item, index]
|
|
534
|
+
rescue StandardError => e
|
|
535
|
+
result = [e, nil, item, index]
|
|
536
|
+
end
|
|
537
|
+
if port
|
|
538
|
+
port.send result
|
|
539
|
+
else
|
|
540
|
+
Ractor.yield result
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
[use_port ? args.first : ractor, ractor]
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def ractor_result(item, index, result, results, results_mutex, options)
|
|
548
|
+
instrument_finish item, index, result, options
|
|
549
|
+
results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def ractor_stop(ractor)
|
|
553
|
+
ractor.send([[nil, nil], nil, :break])
|
|
554
|
+
end
|
|
555
|
+
|
|
530
556
|
def work_in_processes(job_factory, options, &blk)
|
|
531
557
|
workers = create_workers(job_factory, options, &blk)
|
|
532
558
|
results = []
|
|
@@ -598,6 +624,7 @@ module Parallel
|
|
|
598
624
|
def worker(job_factory, options, &block)
|
|
599
625
|
child_read, parent_write = IO.pipe
|
|
600
626
|
parent_read, child_write = IO.pipe
|
|
627
|
+
options[:serializer] ||= Serializer::Marshal
|
|
601
628
|
|
|
602
629
|
pid = Process.fork do
|
|
603
630
|
self.worker_number = options[:worker_number]
|
|
@@ -618,12 +645,13 @@ module Parallel
|
|
|
618
645
|
child_read.close
|
|
619
646
|
child_write.close
|
|
620
647
|
|
|
621
|
-
Worker.new(parent_read, parent_write, pid)
|
|
648
|
+
Worker.new(parent_read, parent_write, pid, options[:serializer])
|
|
622
649
|
end
|
|
623
650
|
|
|
624
651
|
def process_incoming_jobs(read, write, job_factory, options, &block)
|
|
652
|
+
serializer = options.fetch(:serializer)
|
|
625
653
|
until read.eof?
|
|
626
|
-
data =
|
|
654
|
+
data = serializer.load(read)
|
|
627
655
|
item, index = job_factory.unpack(data)
|
|
628
656
|
|
|
629
657
|
result =
|
|
@@ -632,12 +660,12 @@ module Parallel
|
|
|
632
660
|
# https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
|
|
633
661
|
rescue NoMemoryError, SignalException, Interrupt, SystemExit # rubocop:disable Lint/ShadowedException
|
|
634
662
|
raise $!
|
|
635
|
-
rescue Exception #
|
|
663
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
636
664
|
ExceptionWrapper.new($!)
|
|
637
665
|
end
|
|
638
666
|
|
|
639
667
|
begin
|
|
640
|
-
|
|
668
|
+
serializer.dump(result, write)
|
|
641
669
|
rescue Errno::EPIPE
|
|
642
670
|
return # parent thread already dead
|
|
643
671
|
end
|
metadata
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: parallel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Grosser
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
|
-
description:
|
|
14
12
|
email: michael@grosser.it
|
|
15
13
|
executables: []
|
|
16
14
|
extensions: []
|
|
@@ -18,16 +16,18 @@ extra_rdoc_files: []
|
|
|
18
16
|
files:
|
|
19
17
|
- MIT-LICENSE.txt
|
|
20
18
|
- lib/parallel.rb
|
|
19
|
+
- lib/parallel/serializer.rb
|
|
21
20
|
- lib/parallel/version.rb
|
|
22
21
|
homepage: https://github.com/grosser/parallel
|
|
23
22
|
licenses:
|
|
24
23
|
- MIT
|
|
25
24
|
metadata:
|
|
26
25
|
bug_tracker_uri: https://github.com/grosser/parallel/issues
|
|
27
|
-
documentation_uri: https://github.com/grosser/parallel/blob/
|
|
28
|
-
source_code_uri: https://github.com/grosser/parallel/tree/
|
|
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
|
|
29
28
|
wiki_uri: https://github.com/grosser/parallel/wiki
|
|
30
|
-
|
|
29
|
+
changelog_uri: https://github.com/grosser/parallel/blob/v2.1.0/CHANGELOG.md
|
|
30
|
+
rubygems_mfa_required: 'true'
|
|
31
31
|
rdoc_options: []
|
|
32
32
|
require_paths:
|
|
33
33
|
- lib
|
|
@@ -35,15 +35,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
35
35
|
requirements:
|
|
36
36
|
- - ">="
|
|
37
37
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: '
|
|
38
|
+
version: '3.3'
|
|
39
39
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
40
|
requirements:
|
|
41
41
|
- - ">="
|
|
42
42
|
- !ruby/object:Gem::Version
|
|
43
43
|
version: '0'
|
|
44
44
|
requirements: []
|
|
45
|
-
rubygems_version:
|
|
46
|
-
signing_key:
|
|
45
|
+
rubygems_version: 4.0.3
|
|
47
46
|
specification_version: 4
|
|
48
47
|
summary: Run any kind of code in parallel processes
|
|
49
48
|
test_files: []
|