multi_process 0.4.0 → 0.5.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/README.md +1 -1
- data/Rakefile +1 -1
- data/lib/multi_process/group.rb +32 -40
- data/lib/multi_process/logger.rb +7 -8
- 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 +2 -4
- data/lib/multi_process/receiver.rb +6 -10
- data/lib/multi_process/string_receiver.rb +3 -5
- data/lib/multi_process/version.rb +4 -2
- data/multi_process.gemspec +9 -9
- data/spec/multi_process_spec.rb +1 -2
- data/spec/spec_helper.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b79dfed9809a87ffa684f477aa18f52ab0874998
|
4
|
+
data.tar.gz: a2499bf8685aaa502893af7848d7de805e104e5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12f8ae434f79b4168460722d3cff268ae2b230084e89ff000e9c758a3d8f625d47bf3a7d0a8169db4e2e6df36e61e1e0920f8192c89cdb526a814f4d3789d07d
|
7
|
+
data.tar.gz: 10e081ebeb820e7563fd9c91c50c962216283c7cee939da51b8adbcc74e49d72d983e6df9e4415c71f6b383e4ebaa5e8069b3968fdbff76b9e17e2a1fce19f0a
|
data/README.md
CHANGED
data/Rakefile
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 <<(processes)
|
39
|
+
Array(processes).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(**kwargs)
|
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(**kwargs)
|
109
|
+
wait(**kwargs)
|
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,7 +12,7 @@ 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
|
|
@@ -43,14 +41,15 @@ module MultiProcess
|
|
43
41
|
end
|
44
42
|
|
45
43
|
private
|
44
|
+
|
46
45
|
def output(process, line, opts = {})
|
47
46
|
@mutex.synchronize do
|
48
47
|
opts[:delimiter] ||= ' |'
|
49
48
|
name = if opts[:name]
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
opts[:name].to_s.dup
|
50
|
+
else
|
51
|
+
max = @readers.values.map { |h| h[:process] ? h[:process].title.length : 0 }.max
|
52
|
+
process ? process.title.to_s.rjust(max, ' ') : (' ' * max)
|
54
53
|
end
|
55
54
|
|
56
55
|
io = opts[:io] || @out
|
@@ -68,7 +67,7 @@ module MultiProcess
|
|
68
67
|
|
69
68
|
class << self
|
70
69
|
def global
|
71
|
-
@global ||=
|
70
|
+
@global ||= new $stdout, $stderr
|
72
71
|
end
|
73
72
|
end
|
74
73
|
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,7 +42,7 @@ 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
47
|
TCPSocket.new('127.0.0.1', port).close
|
50
48
|
true
|
@@ -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('127.0.0.1', 0))
|
73
71
|
socket.local_address.ip_port
|
74
72
|
ensure
|
75
73
|
socket.close if socket
|
@@ -1,10 +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
6
|
# Mutex to synchronize operations.
|
9
7
|
#
|
10
8
|
attr_reader :mutex
|
@@ -21,7 +19,7 @@ module MultiProcess
|
|
21
19
|
op = @readers[reader]
|
22
20
|
|
23
21
|
if reader.eof?
|
24
|
-
@readers.delete_if { |key,
|
22
|
+
@readers.delete_if { |key, _value| key == reader }
|
25
23
|
removed op[:process], op[:name]
|
26
24
|
else
|
27
25
|
received op[:process], op[:name], read(reader)
|
@@ -43,7 +41,7 @@ module MultiProcess
|
|
43
41
|
#
|
44
42
|
def pipe(process, name)
|
45
43
|
reader, writer = IO.pipe
|
46
|
-
@readers[reader] = {name: name, process: process}
|
44
|
+
@readers[reader] = { name: name, process: process }
|
47
45
|
connected process, name
|
48
46
|
writer
|
49
47
|
end
|
@@ -61,8 +59,8 @@ module MultiProcess
|
|
61
59
|
#
|
62
60
|
# Must be overridden by subclass.
|
63
61
|
#
|
64
|
-
def received(
|
65
|
-
|
62
|
+
def received(_process, _name, _message)
|
63
|
+
fail NotImplementedError.new 'Subclass responsibility.'
|
66
64
|
end
|
67
65
|
|
68
66
|
# Read content from pipe. Can be used to provide custom reading
|
@@ -77,14 +75,12 @@ module MultiProcess
|
|
77
75
|
# Called after pipe for process and name was removed because it
|
78
76
|
# reached EOF.
|
79
77
|
#
|
80
|
-
def removed(
|
81
|
-
|
78
|
+
def removed(_process, _name)
|
82
79
|
end
|
83
80
|
|
84
81
|
# Called after new pipe for process and name was created.
|
85
82
|
#
|
86
|
-
def connected(
|
87
|
-
|
83
|
+
def connected(_process, _name)
|
88
84
|
end
|
89
85
|
end
|
90
86
|
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
|
data/multi_process.gemspec
CHANGED
@@ -4,22 +4,22 @@ $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
23
|
|
24
|
-
spec.add_development_dependency
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
25
25
|
end
|
data/spec/multi_process_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
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
|
|
@@ -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: 0.
|
4
|
+
version: 0.5.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: 2015-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -100,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
100
|
version: '0'
|
101
101
|
requirements: []
|
102
102
|
rubyforge_project:
|
103
|
-
rubygems_version: 2.
|
103
|
+
rubygems_version: 2.4.6
|
104
104
|
signing_key:
|
105
105
|
specification_version: 4
|
106
106
|
summary: Handle multiple child processes.
|