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