multi_process 1.3.0 → 1.4.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: 5d62d4ef2d14471f23abcd7c7015d4370acffba45c77007df13505dd96e9fd16
4
- data.tar.gz: 479e3d12ed873435fcdebe1d8d1d37bab3fbd276f5db85bdb079fa0264bbae27
3
+ metadata.gz: ec194b3c4920ec50a3ff76ef6129c7950d9bf52f54d9fadbbd6e2be8820ad85a
4
+ data.tar.gz: ca53b1cd6dbe8b3522e6fa519a1c09843c52afdb30d0d4416e2814e801b94abe
5
5
  SHA512:
6
- metadata.gz: 5865c7397ca0cfcdc7b91c4d1e02f573b85dd63e1b1876074c49d1e5e3595926e6ef9d04d49b0cf0863be3e7c7bdbef814f7fb04a0f389308484847442b699e6
7
- data.tar.gz: c15f75913b1b3824386430e90202700ff2ac261f3ec09e4db46a52b9d2d639c3eda54102c00e3058bcdca01d1ffde87b4b79febe4d59474d99a86d889ba534e2
6
+ metadata.gz: '0709709c6a7b22570455a6c59f3581ad21ac4685bcdc4ef29b5f97dc260645e06a482aee960f30f7ff910850c78f11443004cbb8d6304ec59bddeafe1a2eb216'
7
+ data.tar.gz: 365c78a689b76dac6ea29400eed562488caf82ea83c945b691b2acb0a314d5e995197987f0d5dd014b3b6cdf6611f4a218e12ada743f783bed46d19ba604d7f3
data/.markdownlint.yml ADDED
@@ -0,0 +1,12 @@
1
+ # markdownlint config
2
+
3
+ # The CHANGELOG contains duplicated headers by design
4
+ MD024: false
5
+
6
+ # MD013/line-length: disable line length for all. We prefer lines as
7
+ # long as paragraph with in-editor line breaks.
8
+ MD013: false
9
+
10
+ # MD048/code-fence-style: code fence style
11
+ MD048:
12
+ style: backtick
data/.rubocop.yml CHANGED
@@ -4,6 +4,6 @@ inherit_gem:
4
4
  rubocop-config: default.yml
5
5
 
6
6
  AllCops:
7
- TargetRubyVersion: 2.7
7
+ TargetRubyVersion: '2.7'
8
8
  SuggestExtensions: False
9
- NewCops: enable
9
+ NewCops: enable
data/CHANGELOG.md CHANGED
@@ -6,9 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.4.0] - 2025-05-13
10
+
11
+ ### Changed
12
+
13
+ - Find free port in a cross-platform way by @tylerhunt (#13)
14
+
15
+ ### Fixed
16
+
17
+ - Fix Process#available! error handling by @tylerhunt (#12)
18
+ - Non-deterministic output causing intermittent spec failure by @tylerhunt (#14)
19
+
20
+ ## [1.3.0] - 2024-01-12
21
+
9
22
  ### Changed
10
23
 
11
- - Use unbundled environment for child processes to support nested bundler invokations by @tylerhunt (#10)
24
+ - Use unbundled environment for child processes to support nested bundler invocations by @tylerhunt (#10)
12
25
 
13
26
  ### Fixed
14
27
 
@@ -45,7 +58,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
45
58
 
46
59
  - Possible concurrent hash modification while iterating (#1)
47
60
 
48
- [Unreleased]: https://github.com/jgraichen/multi_process/compare/v1.2.1...HEAD
61
+ [Unreleased]: https://github.com/jgraichen/multi_process/compare/v1.4.0...HEAD
62
+ [1.4.0]: https://github.com/jgraichen/multi_process/compare/v1.3.0...v1.4.0
63
+ [1.3.0]: https://github.com/jgraichen/multi_process/compare/v1.2.1...v1.3.0
49
64
  [1.2.1]: https://github.com/jgraichen/multi_process/compare/v1.2.0...v1.2.1
50
65
  [1.2.0]: https://github.com/jgraichen/multi_process/compare/v1.1.1...v1.2.0
51
66
  [1.1.1]: https://github.com/jgraichen/multi_process/compare/v1.1.0...v1.1.1
data/README.md CHANGED
@@ -4,7 +4,7 @@ Run multiple processes.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ Add this line to your application's `Gemfile`:
8
8
 
9
9
  ```ruby
10
10
  gem 'multi_process'
@@ -47,7 +47,7 @@ group.stop # Stop processes
47
47
 
48
48
  ## Contributing
49
49
 
50
- 1. Fork it (http://github.com/jgraichen/multi_process/fork)
50
+ 1. [Fork it](http://github.com/jgraichen/multi_process/fork)
51
51
  2. Create your feature branch (`git checkout -b my-new-feature`)
52
52
  3. Commit your changes (`git commit -am 'Add some feature'`)
53
53
  4. Push to the branch (`git push origin my-new-feature`)
@@ -55,7 +55,7 @@ group.stop # Stop processes
55
55
 
56
56
  ## License
57
57
 
58
- Copyright © 2019-2023 Jan Graichen
58
+ Copyright © 2019-2025 Jan Graichen
59
59
 
60
60
  This program is free software: you can redistribute it and/or modify
61
61
  it under the terms of the GNU General Public License as published by
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timeout'
4
+
3
5
  module MultiProcess
4
6
  #
5
7
  # Store and run a group of processes.
@@ -27,7 +29,7 @@ module MultiProcess
27
29
  def initialize(receiver: nil, partition: nil)
28
30
  @processes = []
29
31
  @receiver = receiver || MultiProcess::Logger.global
30
- @partition = partition ? partition.to_i : 0
32
+ @partition = partition ? Integer(partition) : 0
31
33
  @mutex = Mutex.new
32
34
  end
33
35
 
@@ -7,7 +7,7 @@ class MultiProcess::Process
7
7
  module BundleExec
8
8
  def initialize(*args)
9
9
  opts = args.last.is_a?(Hash) ? args.pop : {}
10
- super %w[bundle exec] + args, opts
10
+ super(%w[bundle exec] + args, opts)
11
11
  end
12
12
  end
13
13
  end
@@ -30,7 +30,8 @@ class MultiProcess::Process
30
30
  end
31
31
 
32
32
  def port=(port)
33
- @port = port.to_i.zero? ? free_port : port.to_i
33
+ port = Integer(port)
34
+ @port = port.zero? ? free_port : port
34
35
  end
35
36
 
36
37
  def port
@@ -63,7 +64,7 @@ class MultiProcess::Process
63
64
 
64
65
  def free_port
65
66
  socket = Socket.new(:INET, :STREAM, 0)
66
- socket.bind(Addrinfo.tcp('localhost', 0))
67
+ socket.bind(Addrinfo.tcp('127.0.0.1', 0))
67
68
  socket.local_address.ip_port
68
69
  ensure
69
70
  socket&.close
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+ require 'timeout'
4
5
 
5
6
  module MultiProcess
6
7
  #
@@ -31,7 +32,7 @@ module MultiProcess
31
32
  @childprocess = create_childprocess(*args)
32
33
 
33
34
  @env = opts[:env] if opts[:env].is_a?(Hash)
34
- @env_clean = opts[:clean_env].nil? ? true : !opts[:clean_env].nil?
35
+ @env_clean = opts[:clean_env].nil? || !opts[:clean_env].nil?
35
36
 
36
37
  self.receiver = opts[:receiver] || MultiProcess::Logger.global
37
38
 
@@ -94,29 +95,29 @@ module MultiProcess
94
95
  childprocess.stop(*args) if started?
95
96
  end
96
97
 
97
- # Check if server is available. What available means can be defined
98
- # by subclasses e.g. a server process can check if server port is reachable.
98
+ # Check if the process is available. What available means can be defined
99
+ # by subclasses e.g. a server process can check if its port is reachable.
99
100
  #
100
- # By default is process if available if alive? returns true.
101
+ # By default a process is available if `#alive?` returns true.
101
102
  #
102
103
  def available?
103
104
  alive?
104
105
  end
105
106
 
106
- # Wait until process is available. See {#available?}.
107
+ # Wait until the process is available. See {#available?}.
107
108
  #
108
109
  # @param opts [Hash] Options.
109
110
  # @option opts [Integer] :timeout Timeout in seconds. Will raise
110
111
  # Timeout::Error if timeout is reached.
111
112
  #
112
113
  def available!(opts = {})
113
- timeout = opts[:timeout] ? opts[:timeout].to_i : MultiProcess::DEFAULT_TIMEOUT
114
+ timeout = opts[:timeout] ? Float(opts[:timeout]) : MultiProcess::DEFAULT_TIMEOUT
114
115
 
115
116
  Timeout.timeout timeout do
116
117
  sleep 0.2 until available?
117
118
  end
118
119
  rescue Timeout::Error
119
- raise Timeout::Error.new "Server #{id.inspect} on port #{port} didn't get up after #{timeout} seconds..."
120
+ raise Timeout::Error.new "Process #{pid} failed to start."
120
121
  end
121
122
 
122
123
  # Check if process was started.
@@ -3,7 +3,7 @@
3
3
  module MultiProcess
4
4
  module VERSION
5
5
  MAJOR = 1
6
- MINOR = 3
6
+ MINOR = 4
7
7
  PATCH = 0
8
8
  STAGE = nil
9
9
  STRING = [MAJOR, MINOR, PATCH, STAGE].compact.join('.')
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multi_process
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Graichen
8
- autorequire:
9
- bindir: bin
8
+ bindir: exe
10
9
  cert_chain: []
11
- date: 2024-01-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: childprocess
@@ -38,19 +37,15 @@ dependencies:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
39
  version: '2.0'
41
- description: Handle multiple child processes.
42
- email:
43
- - jg@altimos.de
40
+ email: jgraichen@altimos.de
44
41
  executables: []
45
42
  extensions: []
46
43
  extra_rdoc_files: []
47
44
  files:
48
45
  - ".editorconfig"
49
- - ".github/workflows/test.yml"
50
- - ".gitignore"
46
+ - ".markdownlint.yml"
51
47
  - ".rubocop.yml"
52
48
  - CHANGELOG.md
53
- - Gemfile
54
49
  - LICENSE.txt
55
50
  - README.md
56
51
  - Rakefile
@@ -66,22 +61,15 @@ files:
66
61
  - lib/multi_process/receiver.rb
67
62
  - lib/multi_process/string_receiver.rb
68
63
  - lib/multi_process/version.rb
69
- - multi_process.gemspec
70
64
  - renovate.json
71
- - spec/files/env.rb
72
- - spec/files/fail.rb
73
- - spec/files/sleep.rb
74
- - spec/files/test.rb
75
- - spec/multi_process/group_spec.rb
76
- - spec/multi_process/process_spec.rb
77
- - spec/multi_process_spec.rb
78
- - spec/spec_helper.rb
79
65
  homepage: https://github.com/jgraichen/multi_process
80
66
  licenses:
81
67
  - GPLv3
82
68
  metadata:
69
+ changelog_uri: https://github.com/jgraichen/multi_process/blob/main/CHANGELOG.md
70
+ homepage_uri: https://github.com/jgraichen/multi_process
71
+ source_code_uri: https://github.com/jgraichen/multi_process.git
83
72
  rubygems_mfa_required: 'true'
84
- post_install_message:
85
73
  rdoc_options: []
86
74
  require_paths:
87
75
  - lib
@@ -96,8 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
84
  - !ruby/object:Gem::Version
97
85
  version: '0'
98
86
  requirements: []
99
- rubygems_version: 3.4.22
100
- signing_key:
87
+ rubygems_version: 3.6.7
101
88
  specification_version: 4
102
- summary: Handle multiple child processes.
89
+ summary: Run multiple processes
103
90
  test_files: []
@@ -1,38 +0,0 @@
1
- # vim: ft=yaml
2
-
3
- name: test
4
- on: push
5
- jobs:
6
- rspec:
7
- name: ruby-${{ matrix.ruby }}
8
- runs-on: ubuntu-22.04
9
-
10
- strategy:
11
- fail-fast: false
12
- matrix:
13
- ruby: ["3.2", "3.1", "3.0", "2.7"]
14
-
15
- steps:
16
- - uses: actions/checkout@v4
17
- - uses: ruby/setup-ruby@v1
18
- with:
19
- ruby-version: ${{ matrix.ruby }}
20
- bundler-cache: True
21
-
22
- - run: bundle exec rspec --color
23
-
24
- rubocop:
25
- name: rubocop
26
- runs-on: ubuntu-22.04
27
-
28
- steps:
29
- - uses: actions/checkout@v4
30
- - uses: ruby/setup-ruby@v1
31
- with:
32
- ruby-version: "3.2"
33
- bundler-cache: True
34
- env:
35
- BUNDLE_JOBS: 4
36
- BUNDLE_RETRY: 3
37
-
38
- - run: bundle exec rubocop --parallel --color
data/.gitignore DELETED
@@ -1,18 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- .rspec
data/Gemfile DELETED
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in multi_process.gemspec
6
- gemspec
7
-
8
- gem 'rake'
9
- gem 'rake-release', '~> 1.3'
10
- gem 'rspec', '~> 3.11'
11
- gem 'rubocop-config', github: 'jgraichen/rubocop-config', ref: 'v11', require: false
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'multi_process/version'
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = 'multi_process'
9
- spec.version = MultiProcess::VERSION
10
- spec.authors = ['Jan Graichen']
11
- spec.email = ['jg@altimos.de']
12
- spec.summary = 'Handle multiple child processes.'
13
- spec.description = 'Handle multiple child processes.'
14
- spec.homepage = 'https://github.com/jgraichen/multi_process'
15
- spec.license = 'GPLv3'
16
-
17
- spec.metadata['rubygems_mfa_required'] = 'true'
18
- spec.required_ruby_version = '>= 2.7'
19
-
20
- spec.files = `git ls-files -z`.split("\x0")
21
- spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f) }
22
- spec.require_paths = ['lib']
23
-
24
- spec.add_runtime_dependency 'childprocess'
25
- spec.add_runtime_dependency 'nio4r', '~> 2.0'
26
- end
data/spec/files/env.rb DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $stdout.puts "ENV: #{ENV.fetch(ARGV[0], nil)}"
4
- $stdout.sync
5
-
6
- sleep 1
data/spec/files/fail.rb DELETED
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- exit Integer(ARGV.fetch(0, '1'))
data/spec/files/sleep.rb DELETED
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- sleep ARGV[0].to_i
data/spec/files/test.rb DELETED
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- 2.times do
4
- $stdout.puts "Output from #{ARGV[0]}"
5
- $stdout.flush
6
- sleep rand
7
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe MultiProcess::Group do
6
- subject(:group) { MultiProcess::Group.new }
7
-
8
- before do
9
- group << MultiProcess::Process.new(command)
10
- end
11
-
12
- describe '#run!' do
13
- context 'with failing command' do
14
- let(:command) { %w[ruby spec/files/fail.rb] }
15
-
16
- it 'does raise an error' do
17
- expect { group.run! }.to raise_error(MultiProcess::ProcessError, /Process \d+ exited with code 1/)
18
- end
19
- end
20
-
21
- context 'with partition and failing command' do
22
- subject(:group) { MultiProcess::Group.new(partition: 1) }
23
-
24
- let(:command) { %w[ruby spec/files/fail.rb] }
25
-
26
- it 'does raise an error' do
27
- expect { group.run! }.to raise_error(MultiProcess::ProcessError, /Process \d+ exited with code 1/)
28
- end
29
- end
30
- end
31
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe MultiProcess::Process do
6
- subject(:process) { MultiProcess::Process.new(command) }
7
-
8
- describe '#run!' do
9
- context 'with failing command' do
10
- let(:command) { %w[ruby spec/files/fail.rb] }
11
-
12
- it 'does raise an error' do
13
- expect { process.run! }.to raise_error(MultiProcess::ProcessError, /Process \d+ exited with code 1/)
14
- end
15
- end
16
- end
17
- end
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe MultiProcess do
6
- it 'runs processes (I)' do
7
- reader, writer = IO.pipe
8
-
9
- logger = MultiProcess::Logger.new writer, collapse: false
10
- group = MultiProcess::Group.new receiver: logger
11
- group << MultiProcess::Process.new(%w[ruby spec/files/test.rb A], title: 'rubyA')
12
- group << MultiProcess::Process.new(%w[ruby spec/files/test.rb B], title: 'rubyBB')
13
- group << MultiProcess::Process.new(%w[ruby spec/files/test.rb C], title: 'rubyCCC')
14
- group.run
15
-
16
- expect(reader.read_nonblock(4096).split("\n")).to match_array <<-OUTPUT.gsub(/^\s+\./, '').split("\n")
17
- . rubyBB | Output from B
18
- . rubyA | Output from A
19
- . rubyA | Output from A
20
- . rubyCCC | Output from C
21
- . rubyCCC | Output from C
22
- . rubyBB | Output from B
23
- OUTPUT
24
- end
25
-
26
- it 'runs processes (II)' do
27
- start = Time.now
28
-
29
- group = MultiProcess::Group.new receiver: MultiProcess::NilReceiver.new
30
- group << MultiProcess::Process.new(%w[ruby spec/files/sleep.rb 5000], title: 'rubyA')
31
- group << MultiProcess::Process.new(%w[ruby spec/files/sleep.rb 5000], title: 'rubyB')
32
- group << MultiProcess::Process.new(%w[ruby spec/files/sleep.rb 5000], title: 'rubyC')
33
- group.start
34
- sleep 1
35
- group.stop
36
-
37
- group.processes.each do |p|
38
- expect(p).not_to be_alive
39
- end
40
- expect(Time.now - start).to be < 2
41
- end
42
-
43
- it 'starts a process added after the group has been started' do
44
- reader, writer = IO.pipe
45
-
46
- logger = MultiProcess::Logger.new writer, collapse: false
47
- group = MultiProcess::Group.new receiver: logger
48
- group << MultiProcess::Process.new(%w[ruby spec/files/test.rb 1], title: 'ruby1')
49
- group.start
50
- group << MultiProcess::Process.new(%w[ruby spec/files/test.rb 2], title: 'ruby2')
51
- sleep 1
52
- group.stop
53
-
54
- expect(reader.read_nonblock(4096).split("\n")).to match_array <<-OUTPUT.gsub(/^\s+\./, '').split("\n")
55
- . ruby1 | Output from 1
56
- . ruby1 | Output from 1
57
- . ruby2 | Output from 2
58
- . ruby2 | Output from 2
59
- OUTPUT
60
- end
61
-
62
- it 'partitions processes' do
63
- group = MultiProcess::Group.new partition: 4, receiver: MultiProcess::NilReceiver.new
64
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyA')
65
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyB')
66
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyC')
67
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyD')
68
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyE')
69
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyF')
70
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyG')
71
- group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyH')
72
-
73
- start = Time.now
74
- group.run
75
- expect(Time.now - start).to be_within(0.5).of(2)
76
- end
77
-
78
- it 'envs processes' do
79
- receiver = MultiProcess::StringReceiver.new
80
- process = MultiProcess::Process.new(%w[ruby spec/files/env.rb TEST], env: {'TEST' => 'abc'}, receiver: receiver)
81
- process.run
82
-
83
- expect(receiver.get(:out)).to eq "ENV: abc\n"
84
- end
85
- end
data/spec/spec_helper.rb DELETED
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rspec'
4
-
5
- require 'bundler'
6
- Bundler.require
7
-
8
- require 'multi_process'
9
-
10
- Dir[File.expand_path('spec/support/**/*.rb')].sort.each {|f| require f }
11
-
12
- RSpec.configure do |config|
13
- # ## Mock Framework
14
- #
15
- # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
16
- #
17
- # config.mock_with :mocha
18
- # config.mock_with :flexmock
19
- # config.mock_with :rr
20
-
21
- # Run specs in random order to surface order dependencies. If you find an
22
- # order dependency and want to debug it, you can fix the order by providing
23
- # the seed, which is printed after each run.
24
- # --seed 1234
25
- config.order = 'random'
26
-
27
- # Raise error when using old :should expectation syntax.
28
- config.raise_errors_for_deprecations!
29
- end