multi_process 0.4.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|