multi_process 0.4.0 → 1.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 +5 -5
- data/CHANGELOG.md +17 -0
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/lib/multi_process.rb +1 -0
- data/lib/multi_process/group.rb +32 -40
- data/lib/multi_process/logger.rb +28 -22
- data/lib/multi_process/loop.rb +39 -0
- data/lib/multi_process/nil_receiver.rb +1 -3
- data/lib/multi_process/process.rb +11 -11
- data/lib/multi_process/process/bundle_exec.rb +1 -3
- data/lib/multi_process/process/rails.rb +3 -5
- data/lib/multi_process/receiver.rb +16 -40
- data/lib/multi_process/string_receiver.rb +3 -5
- data/lib/multi_process/version.rb +5 -3
- data/multi_process.gemspec +10 -9
- data/spec/multi_process_spec.rb +10 -11
- data/spec/spec_helper.rb +1 -1
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 72585d4ab91c2cabcab27ece73e7759bc9ae7d9825564736583b551467dc480d
|
4
|
+
data.tar.gz: 27d863d2702517f16a7cb35de0f9e6823dbe32212b11465ba509fb91a3565bd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c1d02ed6a2ea5d5c08a7776ba4e9ad777f4d15454fc91d711e98cf52c430ef2faf51b2f3a83c40fe0ee3b688368c32e4e69102d65414dfcd12814d17b9b8f5e
|
7
|
+
data.tar.gz: ed7f3357d64c532b39271affe9156aaf58bc20ed59b73778d8bbdbfd73ac503aa44babd0b80c08e7d6f03a1cc1f33b848d57b1b237cdd408effbff7fe1f5d259
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## 1.1.0 - 2020-11-19
|
10
|
+
### Feature
|
11
|
+
- Add support for IPv6 by using the hostname instead of the loopback IPv4 address (#2)
|
12
|
+
|
13
|
+
## 1.0.0 - 2019-05-13
|
14
|
+
### Fixed
|
15
|
+
- Possible concurrent hash modification while iterating (#1)
|
16
|
+
|
17
|
+
[Unreleased]: https://github.com/jgraichen/multi_process/compare/v1.0.0...HEAD
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# MultiProcess
|
2
2
|
|
3
|
-
Handle multiple processes.
|
3
|
+
Handle multiple processes. Ruby >= 2.0.
|
4
4
|
|
5
5
|
TODO: Just experiment.
|
6
6
|
|
@@ -39,7 +39,7 @@ group.stop # Stop processes
|
|
39
39
|
(23314) rubyC | Output from C
|
40
40
|
(23311) rubyB | Output from B
|
41
41
|
(23308) rubyA | Output from A
|
42
|
-
|
42
|
+
```
|
43
43
|
|
44
44
|
## Contributing
|
45
45
|
|
@@ -51,7 +51,7 @@ group.stop # Stop processes
|
|
51
51
|
|
52
52
|
## License
|
53
53
|
|
54
|
-
Copyright (C)
|
54
|
+
Copyright (C) 2019 Jan Graichen
|
55
55
|
|
56
56
|
This program is free software: you can redistribute it and/or modify
|
57
57
|
it under the terms of the GNU General Public License as published by
|
data/Rakefile
CHANGED
data/lib/multi_process.rb
CHANGED
data/lib/multi_process/group.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module MultiProcess
|
2
|
-
|
2
|
+
#
|
3
3
|
# Store and run a group of processes.
|
4
4
|
#
|
5
5
|
class Group
|
6
|
-
|
6
|
+
#
|
7
7
|
# Return list of processes.
|
8
8
|
attr_reader :processes
|
9
9
|
|
@@ -22,10 +22,10 @@ module MultiProcess
|
|
22
22
|
# @option otps [ Receiver ] :receiver Receiver to use for new added
|
23
23
|
# processes. Defaults to `MultiProcess::Logger.global`.
|
24
24
|
#
|
25
|
-
def initialize(
|
25
|
+
def initialize(receiver: nil, partition: nil)
|
26
26
|
@processes = []
|
27
|
-
@receiver =
|
28
|
-
@partition =
|
27
|
+
@receiver = receiver ? receiver : MultiProcess::Logger.global
|
28
|
+
@partition = partition ? partition.to_i : 0
|
29
29
|
@mutex = Mutex.new
|
30
30
|
end
|
31
31
|
|
@@ -35,14 +35,12 @@ module MultiProcess
|
|
35
35
|
#
|
36
36
|
# @param process [Process, Array<Process>] New process or processes.
|
37
37
|
#
|
38
|
-
def <<(
|
39
|
-
Array(
|
38
|
+
def <<(procs)
|
39
|
+
Array(procs).flatten.each do |process|
|
40
40
|
processes << process
|
41
41
|
process.receiver = receiver
|
42
42
|
|
43
|
-
if started?
|
44
|
-
start process
|
45
|
-
end
|
43
|
+
start process if started?
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
@@ -50,15 +48,14 @@ module MultiProcess
|
|
50
48
|
#
|
51
49
|
# Call blocks until all processes are started.
|
52
50
|
#
|
53
|
-
# @
|
54
|
-
# @option opts [ Integer ] :delay Delay in seconds between starting processes.
|
51
|
+
# @option delay [Integer] Delay in seconds between starting processes.
|
55
52
|
#
|
56
|
-
def start(
|
53
|
+
def start(delay: nil)
|
57
54
|
processes.each do |process|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
55
|
+
next if process.started?
|
56
|
+
|
57
|
+
process.start
|
58
|
+
sleep delay if delay
|
62
59
|
end
|
63
60
|
end
|
64
61
|
|
@@ -67,15 +64,13 @@ module MultiProcess
|
|
67
64
|
# @return [ Boolean ] True if group was already started.
|
68
65
|
#
|
69
66
|
def started?
|
70
|
-
processes.any?
|
67
|
+
processes.any?(&:started?)
|
71
68
|
end
|
72
69
|
|
73
70
|
# Stop all processes.
|
74
71
|
#
|
75
72
|
def stop
|
76
|
-
processes.each
|
77
|
-
process.stop
|
78
|
-
end
|
73
|
+
processes.each(&:stop)
|
79
74
|
end
|
80
75
|
|
81
76
|
# Wait until all process terminated.
|
@@ -84,11 +79,11 @@ module MultiProcess
|
|
84
79
|
# @option opts [ Integer ] :timeout Timeout in seconds to wait before
|
85
80
|
# raising {Timeout::Error}.
|
86
81
|
#
|
87
|
-
def wait(
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
processes.each
|
82
|
+
def wait(timeout: nil)
|
83
|
+
if timeout
|
84
|
+
::Timeout.timeout(timeout) { wait }
|
85
|
+
else
|
86
|
+
processes.each(&:wait)
|
92
87
|
end
|
93
88
|
end
|
94
89
|
|
@@ -100,20 +95,18 @@ module MultiProcess
|
|
100
95
|
# If timeout is given process will be terminated using {#stop}
|
101
96
|
# when timeout error is raised.
|
102
97
|
#
|
103
|
-
def run(
|
98
|
+
def run(delay: nil, timeout: nil)
|
104
99
|
if partition > 0
|
105
|
-
|
106
|
-
|
107
|
-
threads << Thread.new do
|
100
|
+
partition.times.map do
|
101
|
+
Thread.new do
|
108
102
|
while (process = next_process)
|
109
103
|
process.run
|
110
104
|
end
|
111
105
|
end
|
112
|
-
end
|
113
|
-
threads.each &:join
|
106
|
+
end.each(&:join)
|
114
107
|
else
|
115
|
-
start
|
116
|
-
wait
|
108
|
+
start delay: delay
|
109
|
+
wait timeout: timeout
|
117
110
|
end
|
118
111
|
ensure
|
119
112
|
stop
|
@@ -124,14 +117,14 @@ module MultiProcess
|
|
124
117
|
# @return [ Boolean ] True if group is alive.
|
125
118
|
#
|
126
119
|
def alive?
|
127
|
-
processes.any?
|
120
|
+
processes.any?(&:alive?)
|
128
121
|
end
|
129
122
|
|
130
123
|
# Check if group is available. The group is available if all
|
131
124
|
# processes are available.
|
132
125
|
#
|
133
126
|
def available?
|
134
|
-
|
127
|
+
processes.all?(:available?)
|
135
128
|
end
|
136
129
|
|
137
130
|
# Wait until group is available. This implies waiting until
|
@@ -143,15 +136,14 @@ module MultiProcess
|
|
143
136
|
# @option opts [ Integer ] :timeout Timeout in seconds to wait for processes
|
144
137
|
# to become available. Defaults to {MultiProcess::DEFAULT_TIMEOUT}.
|
145
138
|
#
|
146
|
-
def available!(
|
147
|
-
timeout = opts[:timeout] ? opts[:timeout].to_i : MultiProcess::DEFAULT_TIMEOUT
|
148
|
-
|
139
|
+
def available!(timeout: MultiProcess::DEFAULT_TIMEOUT)
|
149
140
|
Timeout.timeout timeout do
|
150
|
-
processes.each
|
141
|
+
processes.each(&:available!)
|
151
142
|
end
|
152
143
|
end
|
153
144
|
|
154
145
|
private
|
146
|
+
|
155
147
|
def next_process
|
156
148
|
@mutex.synchronize do
|
157
149
|
@index ||= 0
|
data/lib/multi_process/logger.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
module MultiProcess
|
2
|
-
|
3
2
|
# Can create pipes and multiplex pipe content to put into
|
4
3
|
# given IO objects e.g. multiple output from multiple
|
5
4
|
# processes to current stdout.
|
6
5
|
#
|
7
6
|
class Logger < Receiver
|
8
|
-
|
9
7
|
# Create new logger.
|
10
8
|
#
|
11
9
|
# @param out [IO] IO to push formatted output from
|
@@ -14,10 +12,12 @@ module MultiProcess
|
|
14
12
|
# error sources.
|
15
13
|
#
|
16
14
|
def initialize(*args)
|
17
|
-
@opts = Hash === args.last ? args.pop :
|
15
|
+
@opts = Hash === args.last ? args.pop : {}
|
18
16
|
@out = args[0] || $stdout
|
19
17
|
@err = args[1] || $stderr
|
20
18
|
|
19
|
+
@colwidth = 0
|
20
|
+
|
21
21
|
super()
|
22
22
|
end
|
23
23
|
|
@@ -34,6 +34,10 @@ module MultiProcess
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def connected(process, _)
|
38
|
+
@colwidth = [process.title.to_s.length, @colwidth].max
|
39
|
+
end
|
40
|
+
|
37
41
|
def read(pipe)
|
38
42
|
pipe.gets
|
39
43
|
end
|
@@ -43,32 +47,34 @@ module MultiProcess
|
|
43
47
|
end
|
44
48
|
|
45
49
|
private
|
46
|
-
def output(process, line, opts = {})
|
47
|
-
@mutex.synchronize do
|
48
|
-
opts[:delimiter] ||= ' |'
|
49
|
-
name = if opts[:name]
|
50
|
-
opts[:name].to_s.dup
|
51
|
-
else
|
52
|
-
max = @readers.values.map{|h| h[:process] ? h[:process].title.length : 0 }.max
|
53
|
-
process ? process.title.to_s.rjust(max, ' ') : (' ' * max)
|
54
|
-
end
|
55
50
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
51
|
+
def output(process, line, opts = {})
|
52
|
+
opts[:delimiter] ||= ' |'
|
53
|
+
name = if opts[:name]
|
54
|
+
opts[:name].to_s.dup
|
55
|
+
else
|
56
|
+
if process
|
57
|
+
process.title.to_s.rjust(@colwidth, ' ')
|
58
|
+
else
|
59
|
+
(' ' * @colwidth)
|
60
|
+
end
|
61
|
+
end
|
64
62
|
|
65
|
-
|
63
|
+
io = opts[:io] || @out
|
64
|
+
if @last_name == name && collapse?
|
65
|
+
io.print " #{' ' * name.length} #{opts[:delimiter]} "
|
66
|
+
else
|
67
|
+
io.print " #{name} #{opts[:delimiter]} "
|
66
68
|
end
|
69
|
+
io.puts line
|
70
|
+
io.flush
|
71
|
+
|
72
|
+
@last_name = name
|
67
73
|
end
|
68
74
|
|
69
75
|
class << self
|
70
76
|
def global
|
71
|
-
@global ||=
|
77
|
+
@global ||= new $stdout, $stderr
|
72
78
|
end
|
73
79
|
end
|
74
80
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'nio'
|
2
|
+
|
3
|
+
module MultiProcess
|
4
|
+
class Loop
|
5
|
+
def initialize
|
6
|
+
@selector = ::NIO::Selector.new
|
7
|
+
|
8
|
+
Thread.new do
|
9
|
+
loop do
|
10
|
+
@selector.select(30.0) do |monitor|
|
11
|
+
if monitor.io.eof?
|
12
|
+
@selector.deregister(monitor.io)
|
13
|
+
monitor.value.call(:eof, monitor)
|
14
|
+
else
|
15
|
+
monitor.value.call(:ready, monitor)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Wait very short time to allow scheduling another thread
|
20
|
+
sleep(0.001)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def watch(io, &block)
|
26
|
+
@selector.wakeup
|
27
|
+
@selector.register(io, :r).tap do |monitor|
|
28
|
+
monitor.value = block
|
29
|
+
monitor.value.call(:registered, monitor)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def instance
|
35
|
+
@instance ||= new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'active_support/core_ext/module/delegation'
|
2
2
|
|
3
3
|
module MultiProcess
|
4
|
-
|
4
|
+
#
|
5
5
|
# Describes a single process that can be configured and run.
|
6
6
|
#
|
7
7
|
# {Process} basically is just a thin wrapper around {ChildProcess}.
|
8
8
|
#
|
9
9
|
class Process
|
10
|
-
|
10
|
+
# @!group Process
|
11
11
|
|
12
12
|
# Process title used in e.g. logger
|
13
13
|
attr_reader :title
|
@@ -23,7 +23,7 @@ module MultiProcess
|
|
23
23
|
opts = (Hash === args.last ? args.pop : {})
|
24
24
|
|
25
25
|
@title = opts[:title].to_s || args.first.to_s.strip.split(/\s+/, 2)[0]
|
26
|
-
@command = args.map{ |arg| (arg =~ /\A[\s"']+\z/ ? arg.inspect : arg).gsub '"', '\"' }.join(' ')
|
26
|
+
@command = args.map { |arg| (arg =~ /\A[\s"']+\z/ ? arg.inspect : arg).gsub '"', '\"' }.join(' ')
|
27
27
|
@childprocess = create_childprocess *args
|
28
28
|
|
29
29
|
@env = opts[:env] if Hash === opts[:env]
|
@@ -63,7 +63,7 @@ module MultiProcess
|
|
63
63
|
return false if started?
|
64
64
|
|
65
65
|
at_exit { stop }
|
66
|
-
receiver.message(self, :sys,
|
66
|
+
receiver.message(self, :sys, command) if receiver
|
67
67
|
start_childprocess
|
68
68
|
@started = true
|
69
69
|
end
|
@@ -116,7 +116,7 @@ module MultiProcess
|
|
116
116
|
wait opts
|
117
117
|
end
|
118
118
|
|
119
|
-
|
119
|
+
# @!group Working Directory
|
120
120
|
|
121
121
|
# Working directory for child process.
|
122
122
|
attr_reader :dir
|
@@ -126,10 +126,10 @@ module MultiProcess
|
|
126
126
|
#
|
127
127
|
def dir=(dir)
|
128
128
|
@dir = ::File.expand_path(dir.to_s)
|
129
|
-
|
129
|
+
env['PWD'] = @dir
|
130
130
|
end
|
131
131
|
|
132
|
-
|
132
|
+
# @!group Environment
|
133
133
|
|
134
134
|
# Check if environment will be cleaned up for process.
|
135
135
|
#
|
@@ -144,17 +144,17 @@ module MultiProcess
|
|
144
144
|
# Return current environment.
|
145
145
|
#
|
146
146
|
def env
|
147
|
-
@env ||=
|
147
|
+
@env ||= {}
|
148
148
|
end
|
149
149
|
|
150
150
|
# Set environment.
|
151
151
|
#
|
152
152
|
def env=(env)
|
153
|
-
|
153
|
+
fail ArgumentError.new 'Environment must be a Hash.' unless hash === env
|
154
154
|
@env = env
|
155
155
|
end
|
156
156
|
|
157
|
-
|
157
|
+
# @!group Receiver
|
158
158
|
|
159
159
|
# Current receiver. Defaults to `MultiProcess::Logger.global`.
|
160
160
|
#
|
@@ -186,7 +186,7 @@ module MultiProcess
|
|
186
186
|
# Can be used to hook in subclasses and modules.
|
187
187
|
#
|
188
188
|
def start_childprocess
|
189
|
-
env.each{|k, v| childprocess.environment[k.to_s] = v.to_s }
|
189
|
+
env.each { |k, v| childprocess.environment[k.to_s] = v.to_s }
|
190
190
|
childprocess.cwd = dir
|
191
191
|
|
192
192
|
if clean_env?
|
@@ -1,12 +1,10 @@
|
|
1
1
|
class MultiProcess::Process
|
2
|
-
|
3
2
|
# Provides functionality to wrap command in with bundle
|
4
3
|
# execute.
|
5
4
|
#
|
6
5
|
module BundleExec
|
7
|
-
|
8
6
|
def initialize(*args)
|
9
|
-
opts = Hash === args.last ? args.pop :
|
7
|
+
opts = Hash === args.last ? args.pop : {}
|
10
8
|
super %w(bundle exec) + args, opts
|
11
9
|
end
|
12
10
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
class MultiProcess::Process
|
2
|
-
|
3
2
|
# Provides functionality for a process that is a rails server
|
4
3
|
# process.
|
5
4
|
#
|
@@ -9,7 +8,6 @@ class MultiProcess::Process
|
|
9
8
|
# availability check based on if server socket is reachable.
|
10
9
|
#
|
11
10
|
module Rails
|
12
|
-
|
13
11
|
# Server wrapper given as argument to `server` action.
|
14
12
|
#
|
15
13
|
attr_reader :server
|
@@ -44,9 +42,9 @@ class MultiProcess::Process
|
|
44
42
|
end
|
45
43
|
|
46
44
|
def available?
|
47
|
-
|
45
|
+
fail ArgumentError.new "Cannot check availability for port #{port}." if port == 0
|
48
46
|
|
49
|
-
TCPSocket.new('
|
47
|
+
TCPSocket.new('localhost', port).close
|
50
48
|
true
|
51
49
|
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
52
50
|
false
|
@@ -69,7 +67,7 @@ class MultiProcess::Process
|
|
69
67
|
|
70
68
|
def free_port
|
71
69
|
socket = Socket.new(:INET, :STREAM, 0)
|
72
|
-
socket.bind(Addrinfo.tcp(
|
70
|
+
socket.bind(Addrinfo.tcp('localhost', 0))
|
73
71
|
socket.local_address.ip_port
|
74
72
|
ensure
|
75
73
|
socket.close if socket
|
@@ -1,40 +1,8 @@
|
|
1
1
|
module MultiProcess
|
2
|
-
|
3
2
|
# Can handle input from multiple processes and run custom
|
4
3
|
# actions on event and output.
|
5
4
|
#
|
6
5
|
class Receiver
|
7
|
-
|
8
|
-
# Mutex to synchronize operations.
|
9
|
-
#
|
10
|
-
attr_reader :mutex
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@mutex = Mutex.new
|
14
|
-
@readers = {}
|
15
|
-
|
16
|
-
Thread.new do
|
17
|
-
begin
|
18
|
-
loop do
|
19
|
-
io = IO.select(@readers.keys, nil, nil, 0.1)
|
20
|
-
(io.nil? ? [] : io.first).each do |reader|
|
21
|
-
op = @readers[reader]
|
22
|
-
|
23
|
-
if reader.eof?
|
24
|
-
@readers.delete_if { |key, value| key == reader }
|
25
|
-
removed op[:process], op[:name]
|
26
|
-
else
|
27
|
-
received op[:process], op[:name], read(reader)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
rescue Exception => ex
|
32
|
-
puts ex.message
|
33
|
-
puts ex.backtrace
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
6
|
# Request a new pipe writer for given process and name.
|
39
7
|
#
|
40
8
|
# @param process [ Process ] Process requesting pipe.
|
@@ -43,8 +11,18 @@ module MultiProcess
|
|
43
11
|
#
|
44
12
|
def pipe(process, name)
|
45
13
|
reader, writer = IO.pipe
|
46
|
-
|
47
|
-
|
14
|
+
|
15
|
+
Loop.instance.watch(reader) do |action, monitor|
|
16
|
+
case action
|
17
|
+
when :registered
|
18
|
+
connected(process, name)
|
19
|
+
when :ready
|
20
|
+
received(process, name, read(monitor.io))
|
21
|
+
when :eof
|
22
|
+
removed(process, name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
48
26
|
writer
|
49
27
|
end
|
50
28
|
|
@@ -61,8 +39,8 @@ module MultiProcess
|
|
61
39
|
#
|
62
40
|
# Must be overridden by subclass.
|
63
41
|
#
|
64
|
-
def received(
|
65
|
-
|
42
|
+
def received(_process, _name, _message)
|
43
|
+
fail NotImplementedError.new 'Subclass responsibility.'
|
66
44
|
end
|
67
45
|
|
68
46
|
# Read content from pipe. Can be used to provide custom reading
|
@@ -77,14 +55,12 @@ module MultiProcess
|
|
77
55
|
# Called after pipe for process and name was removed because it
|
78
56
|
# reached EOF.
|
79
57
|
#
|
80
|
-
def removed(
|
81
|
-
|
58
|
+
def removed(_process, _name)
|
82
59
|
end
|
83
60
|
|
84
61
|
# Called after new pipe for process and name was created.
|
85
62
|
#
|
86
|
-
def connected(
|
87
|
-
|
63
|
+
def connected(_process, _name)
|
88
64
|
end
|
89
65
|
end
|
90
66
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
module MultiProcess
|
2
|
-
|
3
2
|
# Receiver implementation storing process output
|
4
3
|
# in string.
|
5
4
|
#
|
6
5
|
class StringReceiver < Receiver
|
7
|
-
|
8
|
-
def received(process, name, message)
|
6
|
+
def received(_process, name, message)
|
9
7
|
get(name) << message
|
10
8
|
end
|
11
9
|
|
12
10
|
def get(name)
|
13
|
-
@strings ||=
|
14
|
-
@strings[name.to_s] ||=
|
11
|
+
@strings ||= {}
|
12
|
+
@strings[name.to_s] ||= ''
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module MultiProcess
|
2
2
|
module VERSION
|
3
|
-
MAJOR =
|
4
|
-
MINOR =
|
3
|
+
MAJOR = 1
|
4
|
+
MINOR = 1
|
5
5
|
PATCH = 0
|
6
6
|
STAGE = nil
|
7
7
|
STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.')
|
8
8
|
|
9
|
-
def self.to_s
|
9
|
+
def self.to_s
|
10
|
+
STRING
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
data/multi_process.gemspec
CHANGED
@@ -4,22 +4,23 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'multi_process/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'multi_process'
|
8
8
|
spec.version = MultiProcess::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
9
|
+
spec.authors = ['Jan Graichen']
|
10
|
+
spec.email = ['jg@altimos.de']
|
11
|
+
spec.summary = 'Handle multiple child processes.'
|
12
|
+
spec.description = 'Handle multiple child processes.'
|
13
|
+
spec.homepage = 'https://github.com/jgraichen/multi_process'
|
14
|
+
spec.license = 'GPLv3'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'activesupport', '>= 3.1'
|
22
22
|
spec.add_runtime_dependency 'childprocess'
|
23
|
+
spec.add_runtime_dependency 'nio4r', '~> 2.0'
|
23
24
|
|
24
|
-
spec.add_development_dependency
|
25
|
+
spec.add_development_dependency 'bundler'
|
25
26
|
end
|
data/spec/multi_process_spec.rb
CHANGED
@@ -1,24 +1,23 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe MultiProcess do
|
4
|
-
|
5
4
|
it 'should run processes' do
|
6
5
|
reader, writer = IO.pipe
|
7
6
|
|
8
7
|
logger = MultiProcess::Logger.new writer, collapse: false
|
9
8
|
group = MultiProcess::Group.new receiver: logger
|
10
9
|
group << MultiProcess::Process.new(%w(ruby spec/files/test.rb A), title: 'rubyA')
|
11
|
-
group << MultiProcess::Process.new(%w(ruby spec/files/test.rb B), title: '
|
12
|
-
group << MultiProcess::Process.new(%w(ruby spec/files/test.rb C), title: '
|
10
|
+
group << MultiProcess::Process.new(%w(ruby spec/files/test.rb B), title: 'rubyBB')
|
11
|
+
group << MultiProcess::Process.new(%w(ruby spec/files/test.rb C), title: 'rubyCCC')
|
13
12
|
group.run
|
14
13
|
|
15
|
-
expect(reader.read_nonblock(4096).split("\n")).to match_array <<-EOF.gsub(/^\s
|
16
|
-
|
17
|
-
rubyA | Output from A
|
18
|
-
rubyA | Output from A
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
expect(reader.read_nonblock(4096).split("\n")).to match_array <<-EOF.gsub(/^\s+\./, '').split("\n")
|
15
|
+
. rubyBB | Output from B
|
16
|
+
. rubyA | Output from A
|
17
|
+
. rubyA | Output from A
|
18
|
+
. rubyCCC | Output from C
|
19
|
+
. rubyCCC | Output from C
|
20
|
+
. rubyBB | Output from B
|
22
21
|
EOF
|
23
22
|
end
|
24
23
|
|
@@ -57,7 +56,7 @@ describe MultiProcess do
|
|
57
56
|
|
58
57
|
it 'should env processes' do
|
59
58
|
receiver = MultiProcess::StringReceiver.new
|
60
|
-
process = MultiProcess::Process.new(%w(ruby spec/files/env.rb TEST), env: {'TEST' =>
|
59
|
+
process = MultiProcess::Process.new(%w(ruby spec/files/env.rb TEST), env: { 'TEST' => 'abc' }, receiver: receiver)
|
61
60
|
process.run
|
62
61
|
|
63
62
|
expect(receiver.get(:out)).to eq "ENV: abc\n"
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multi_process
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Graichen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -39,19 +39,33 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: nio4r
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
-
type: :
|
47
|
+
version: '2.0'
|
48
|
+
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: Handle multiple child processes.
|
56
70
|
email:
|
57
71
|
- jg@altimos.de
|
@@ -60,6 +74,7 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- ".gitignore"
|
77
|
+
- CHANGELOG.md
|
63
78
|
- Gemfile
|
64
79
|
- LICENSE.txt
|
65
80
|
- README.md
|
@@ -67,6 +82,7 @@ files:
|
|
67
82
|
- lib/multi_process.rb
|
68
83
|
- lib/multi_process/group.rb
|
69
84
|
- lib/multi_process/logger.rb
|
85
|
+
- lib/multi_process/loop.rb
|
70
86
|
- lib/multi_process/nil_receiver.rb
|
71
87
|
- lib/multi_process/process.rb
|
72
88
|
- lib/multi_process/process/bundle_exec.rb
|
@@ -99,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
115
|
- !ruby/object:Gem::Version
|
100
116
|
version: '0'
|
101
117
|
requirements: []
|
102
|
-
|
103
|
-
rubygems_version: 2.2.1
|
118
|
+
rubygems_version: 3.0.8
|
104
119
|
signing_key:
|
105
120
|
specification_version: 4
|
106
121
|
summary: Handle multiple child processes.
|