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.
@@ -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,11 @@
1
+ module MultiProcess
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ STAGE = nil
7
+ STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.')
8
+
9
+ def self.to_s; STRING end
10
+ end
11
+ 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
@@ -0,0 +1,2 @@
1
+
2
+ sleep ARGV[0].to_i
@@ -0,0 +1,6 @@
1
+
2
+ 2.times do
3
+ $stdout.print "Output from #{ARGV[0]}"
4
+ $stdout.flush
5
+ sleep rand
6
+ end
@@ -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
@@ -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