multi_process 0.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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +674 -0
- data/README.md +67 -0
- data/Rakefile +5 -0
- data/lib/multi_process.rb +15 -0
- data/lib/multi_process/group.rb +136 -0
- data/lib/multi_process/logger.rb +71 -0
- data/lib/multi_process/nil_receiver.rb +13 -0
- data/lib/multi_process/process.rb +200 -0
- data/lib/multi_process/process/bundle_exec.rb +13 -0
- data/lib/multi_process/process/rails.rb +74 -0
- data/lib/multi_process/receiver.rb +90 -0
- data/lib/multi_process/version.rb +11 -0
- data/multi_process.gemspec +25 -0
- data/spec/files/sleep.rb +2 -0
- data/spec/files/test.rb +6 -0
- data/spec/multi_process_spec.rb +41 -0
- data/spec/spec_helper.rb +27 -0
- metadata +109 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class MultiProcess::Process
|
|
2
|
+
|
|
3
|
+
# Provides functionality to wrap command in with bundle
|
|
4
|
+
# execute.
|
|
5
|
+
#
|
|
6
|
+
module BundleExec
|
|
7
|
+
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
opts = Hash === args.last ? args.pop : Hash.new
|
|
10
|
+
super %w(bundle exec) + args, opts
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class MultiProcess::Process
|
|
2
|
+
|
|
3
|
+
# Provides functionality for a process that is a rails server
|
|
4
|
+
# process.
|
|
5
|
+
#
|
|
6
|
+
# Include this module if required.
|
|
7
|
+
#
|
|
8
|
+
# Functions include port generation, default server command and
|
|
9
|
+
# availability check based on if server socket is reachable.
|
|
10
|
+
#
|
|
11
|
+
module Rails
|
|
12
|
+
|
|
13
|
+
# Server wrapper given as argument to `server` action.
|
|
14
|
+
#
|
|
15
|
+
attr_reader :server
|
|
16
|
+
|
|
17
|
+
# Port server should be running on.
|
|
18
|
+
#
|
|
19
|
+
# Default will be a free port determined when process is created.
|
|
20
|
+
#
|
|
21
|
+
attr_reader :port
|
|
22
|
+
|
|
23
|
+
def initialize(opts = {})
|
|
24
|
+
self.server = opts[:server] if opts[:server]
|
|
25
|
+
self.port = opts[:port] if opts[:port]
|
|
26
|
+
|
|
27
|
+
super ['rails', 'server', server, '--port', port].reject(&:nil?).map(&:to_s), opts
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def server=(server)
|
|
31
|
+
@server = server.to_s.empty? ? nil : server.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def port=(port)
|
|
35
|
+
@port = port.to_i == 0 ? free_port : port.to_i
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def port
|
|
39
|
+
@port ||= free_port
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def available?
|
|
43
|
+
raise ArgumentError.new "Cannot check availability for port #{port}." if port == 0
|
|
44
|
+
|
|
45
|
+
TCPSocket.new('127.0.0.1', port).close
|
|
46
|
+
true
|
|
47
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
48
|
+
false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Load environment options from initialize options.
|
|
52
|
+
#
|
|
53
|
+
def configure(opts)
|
|
54
|
+
super
|
|
55
|
+
puts 'Configure RAILS'
|
|
56
|
+
self.dir = Dir.pwd
|
|
57
|
+
self.dir = opts[:dir].to_s if opts[:dir]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def start_childprocess(*args)
|
|
61
|
+
Dir.chdir(dir) { super }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def free_port
|
|
67
|
+
socket = Socket.new(:INET, :STREAM, 0)
|
|
68
|
+
socket.bind(Addrinfo.tcp("127.0.0.1", 0))
|
|
69
|
+
socket.local_address.ip_port
|
|
70
|
+
ensure
|
|
71
|
+
socket.close if socket
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module MultiProcess
|
|
2
|
+
|
|
3
|
+
# Can handle input from multiple processes and run custom
|
|
4
|
+
# actions on event and output.
|
|
5
|
+
#
|
|
6
|
+
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
|
+
if reader.eof?
|
|
22
|
+
op = @readers[reader]
|
|
23
|
+
@readers.delete_if { |key, value| key == reader }
|
|
24
|
+
removed op[:process], op[:name]
|
|
25
|
+
else
|
|
26
|
+
op = @readers[reader]
|
|
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
|
+
# Request a new pipe writer for given process and name.
|
|
39
|
+
#
|
|
40
|
+
# @param process [ Process ] Process requesting pipe.
|
|
41
|
+
# @param name [ Symbol ] Name associated to pipe e.g.
|
|
42
|
+
# `:out` or `:err`.
|
|
43
|
+
#
|
|
44
|
+
def pipe(process, name)
|
|
45
|
+
reader, writer = IO.pipe
|
|
46
|
+
@readers[reader] = {name: name, process: process}
|
|
47
|
+
connected process, name
|
|
48
|
+
writer
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Send a custom messages.
|
|
52
|
+
#
|
|
53
|
+
def message(process, name, message)
|
|
54
|
+
received process, name, message
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
# Will be called when content is received for given
|
|
60
|
+
# process and name.
|
|
61
|
+
#
|
|
62
|
+
# Must be overridden by subclass.
|
|
63
|
+
#
|
|
64
|
+
def received(process, name, message)
|
|
65
|
+
raise NotImplementedError.new 'Subclass responsibility.'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Read content from pipe. Can be used to provide custom reading
|
|
69
|
+
# like reading lines instead of byte ranges.
|
|
70
|
+
#
|
|
71
|
+
# Should be non blocking.
|
|
72
|
+
#
|
|
73
|
+
def read(reader)
|
|
74
|
+
reader.read_nonblock 4096
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Called after pipe for process and name was removed because it
|
|
78
|
+
# reached EOF.
|
|
79
|
+
#
|
|
80
|
+
def removed(process, name)
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Called after new pipe for process and name was created.
|
|
85
|
+
#
|
|
86
|
+
def connected(process, name)
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'multi_process/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "multi_process"
|
|
8
|
+
spec.version = MultiProcess::VERSION
|
|
9
|
+
spec.authors = ["Jan Graichen"]
|
|
10
|
+
spec.email = ["jg@altimos.de"]
|
|
11
|
+
spec.summary = %q{Handle multiple child processes.}
|
|
12
|
+
spec.description = %q{Handle multiple child processes.}
|
|
13
|
+
spec.homepage = "https://github.com/jgraichen/multi_process"
|
|
14
|
+
spec.license = "GPLv3"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_runtime_dependency 'activesupport', '>= 3.1'
|
|
22
|
+
spec.add_runtime_dependency 'childprocess'
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
|
25
|
+
end
|
data/spec/files/sleep.rb
ADDED
data/spec/files/test.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe MultiProcess do
|
|
4
|
+
|
|
5
|
+
it 'should run processes' do
|
|
6
|
+
reader, writer = IO.pipe
|
|
7
|
+
|
|
8
|
+
logger = MultiProcess::Logger.new writer
|
|
9
|
+
group = MultiProcess::Group.new #receiver: logger
|
|
10
|
+
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: 'rubyB')
|
|
12
|
+
group << MultiProcess::Process.new(%w(ruby spec/files/test.rb C), title: 'rubyC')
|
|
13
|
+
group.run
|
|
14
|
+
|
|
15
|
+
expect(reader.read_nonblock(4096).split("\n")).to match_array <<-EOF.gsub(/^\s+/, ' ').split("\n")
|
|
16
|
+
rubyB | Output from B
|
|
17
|
+
rubyA | Output from A
|
|
18
|
+
rubyA | Output from A
|
|
19
|
+
rubyC | Output from C
|
|
20
|
+
rubyC | Output from C
|
|
21
|
+
rubyB | Output from B
|
|
22
|
+
EOF
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should run processes' do
|
|
26
|
+
start = Time.now
|
|
27
|
+
|
|
28
|
+
group = MultiProcess::Group.new# logger: MultiProcess::NilReceiver.new
|
|
29
|
+
group << MultiProcess::Process.new(%w(ruby spec/files/sleep.rb 5000), title: 'rubyA')
|
|
30
|
+
group << MultiProcess::Process.new(%w(ruby spec/files/sleep.rb 5000), title: 'rubyB')
|
|
31
|
+
group << MultiProcess::Process.new(%w(ruby spec/files/sleep.rb 5000), title: 'rubyC')
|
|
32
|
+
group.start
|
|
33
|
+
sleep 1
|
|
34
|
+
group.stop
|
|
35
|
+
|
|
36
|
+
group.processes.each do |p|
|
|
37
|
+
expect(p).to_not be_alive
|
|
38
|
+
end
|
|
39
|
+
expect(Time.now - start).to be < 2
|
|
40
|
+
end
|
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
|
|
3
|
+
require 'bundler'
|
|
4
|
+
Bundler.require
|
|
5
|
+
|
|
6
|
+
require 'multi_process'
|
|
7
|
+
|
|
8
|
+
Dir[File.expand_path('spec/support/**/*.rb')].each {|f| require f}
|
|
9
|
+
|
|
10
|
+
RSpec.configure do |config|
|
|
11
|
+
# ## Mock Framework
|
|
12
|
+
#
|
|
13
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
|
14
|
+
#
|
|
15
|
+
# config.mock_with :mocha
|
|
16
|
+
# config.mock_with :flexmock
|
|
17
|
+
# config.mock_with :rr
|
|
18
|
+
|
|
19
|
+
# Run specs in random order to surface order dependencies. If you find an
|
|
20
|
+
# order dependency and want to debug it, you can fix the order by providing
|
|
21
|
+
# the seed, which is printed after each run.
|
|
22
|
+
# --seed 1234
|
|
23
|
+
config.order = 'random'
|
|
24
|
+
|
|
25
|
+
# Raise error when using old :should expectation syntax.
|
|
26
|
+
config.raise_errors_for_deprecations!
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: multi_process
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jan Graichen
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-01-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: childprocess
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: bundler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.5'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.5'
|
|
55
|
+
description: Handle multiple child processes.
|
|
56
|
+
email:
|
|
57
|
+
- jg@altimos.de
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".gitignore"
|
|
63
|
+
- Gemfile
|
|
64
|
+
- LICENSE.txt
|
|
65
|
+
- README.md
|
|
66
|
+
- Rakefile
|
|
67
|
+
- lib/multi_process.rb
|
|
68
|
+
- lib/multi_process/group.rb
|
|
69
|
+
- lib/multi_process/logger.rb
|
|
70
|
+
- lib/multi_process/nil_receiver.rb
|
|
71
|
+
- lib/multi_process/process.rb
|
|
72
|
+
- lib/multi_process/process/bundle_exec.rb
|
|
73
|
+
- lib/multi_process/process/rails.rb
|
|
74
|
+
- lib/multi_process/receiver.rb
|
|
75
|
+
- lib/multi_process/version.rb
|
|
76
|
+
- multi_process.gemspec
|
|
77
|
+
- spec/files/sleep.rb
|
|
78
|
+
- spec/files/test.rb
|
|
79
|
+
- spec/multi_process_spec.rb
|
|
80
|
+
- spec/spec_helper.rb
|
|
81
|
+
homepage: https://github.com/jgraichen/multi_process
|
|
82
|
+
licenses:
|
|
83
|
+
- GPLv3
|
|
84
|
+
metadata: {}
|
|
85
|
+
post_install_message:
|
|
86
|
+
rdoc_options: []
|
|
87
|
+
require_paths:
|
|
88
|
+
- lib
|
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: '0'
|
|
99
|
+
requirements: []
|
|
100
|
+
rubyforge_project:
|
|
101
|
+
rubygems_version: 2.2.1
|
|
102
|
+
signing_key:
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: Handle multiple child processes.
|
|
105
|
+
test_files:
|
|
106
|
+
- spec/files/sleep.rb
|
|
107
|
+
- spec/files/test.rb
|
|
108
|
+
- spec/multi_process_spec.rb
|
|
109
|
+
- spec/spec_helper.rb
|