childprocess-server 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjhjOTBjNTljNzBkMjE4ZmJhODAxNTJiOWU5NzQzYmY3Zjc2YTQ5MA==
5
+ data.tar.gz: !binary |-
6
+ ZTQyZTBiNmNlMGNmZGFmY2U4ODgzOTFjYmMyZDk5ZmMxNDE0YjA2ZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDgzYWRjYTFkZGJiYjc0NDg1NzlhYjYwMmFkNGFiODEzNWM3OGM2ZmQxNTNh
10
+ ZGZjYTJhM2ZhYzVlOGI1Y2Q2N2E2MTJiMmE5YmM4YmE5YzI2YmZjNGYzNGQ2
11
+ MDhiZWIyNWU3N2VhZTIwNGYzY2UzNDY5NTQxYjFkMWIzMGI1Yjc=
12
+ data.tar.gz: !binary |-
13
+ YzA4Y2YzOTE2MTgxNjdmNDAxM2RmM2UyNGQ5OGI5NjE0ZjFiOTNlNzQzMjkw
14
+ YjEwZGM4MGI5MzdiNWRmZDI2MDc1ZjBmYTY3ZGVhN2Q2MzA3MWU2ZDIxOGJk
15
+ YmNjMTY5ZGZkYWYxYTMwMWJmYTJhOWRlMzBhMGVlMjk5YzM2M2Y=
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2013 WU Jun <quark@zju.edu.cn>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,72 @@
1
+ # childprocess-server
2
+
3
+ Manage and interact with processes, remotely (via dRuby).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install childprocess-server
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Standalone
14
+
15
+ ```ruby
16
+ require 'childprocess-server'
17
+ server = ChildProcess::Server.new
18
+
19
+ # Run a process
20
+ pid = server.launch('sleep', '1000')
21
+ server.alive?(pid) # => true
22
+ server.stop(pid)
23
+ server.alive?(pid) # => false
24
+
25
+ # Run 'echo', get output
26
+ pid = server.launch('echo', 'hello')
27
+ server.read_output(pid) # => "hello\n"
28
+ server.alive?(pid) # => false
29
+
30
+ # Run 'cat', write stdin interactively
31
+ pid = server.launch('cat')
32
+ server.write_input(pid, "foo\n")
33
+ server.read_output(pid) # => "foo\n"
34
+ server.write_input(pid, "bar\n")
35
+ server.read_output(pid) # => "foo\nbar\n"
36
+ server.stop(pid)
37
+
38
+ # List all process ids managed
39
+ # Note: exited processes are also listed.
40
+ server.list_pids # => [ ... ]
41
+
42
+ # Clean up
43
+ server.clean_up
44
+ server.list_pids # => []
45
+ ```
46
+
47
+ ### Client / Server
48
+
49
+ ```ruby
50
+ DRB_URI = 'drbunix://tmp/a.socket'
51
+
52
+ # Server side
53
+ ChildProcess::Server.new.start_service(DRB_URI) # will block by default
54
+
55
+ # Client side
56
+ server = ChildProcess::Server.connect(DRB_URI)
57
+ pid = server.launch('sort', '-n')
58
+ server.write_input(pid, "20\n10\n2\n1\n")
59
+ server.read_output(pid) # => "1\n2\n10\n20\n"
60
+ ```
61
+
62
+ ## Notes
63
+
64
+ * This library is thread-safe.
65
+ * This library does not have authentic feature.
66
+ Set file system permissions or firewall for security.
67
+ * After `stop`, output can still be read using `read_output`,
68
+ because outputs are stored in temporary files.
69
+ Use `clean_up` to delete them immediately.
70
+ Otherwise they are deleted when server exits.
71
+ * If `read_output` or `write_input` encounters errors,
72
+ they just return `nil` instead of raising an error.
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'yard'
3
+
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new
7
+ YARD::Rake::YardocTask.new
8
+
9
+ task :doc => [:yard]
10
+ task :test => [:spec]
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'childprocess-server'
3
+ s.version = '0.1.1'
4
+ s.date = Date.civil(2013,3,31)
5
+ s.summary = 'Manage and interact with processes, remotely.'
6
+ s.description = 'Manage and interact with CLI processes, remotely via dRuby.'
7
+ s.authors = ["Wu Jun"]
8
+ s.email = 'quark@zju.edu.cn'
9
+ s.homepage = 'https://github.com/quark-zju/childprocess-server'
10
+ s.require_paths = ['lib']
11
+ s.licenses = ['MIT']
12
+ s.has_rdoc = 'yard'
13
+ s.files = %w(LICENSE README.md Rakefile childprocess-server.gemspec)
14
+ s.files += Dir['{lib,spec}/**/*.rb']
15
+ s.add_dependency 'childprocess', '~> 0.3.9'
16
+ s.add_development_dependency 'rake', '~> 10.0'
17
+ s.add_development_dependency 'rspec', '~> 2.13'
18
+ s.add_development_dependency 'yard', '~> 0.8'
19
+ s.test_files = Dir['spec/**/*.rb']
20
+ end
21
+
@@ -0,0 +1 @@
1
+ require 'childprocess/server'
@@ -0,0 +1,123 @@
1
+ require 'drb/drb'
2
+ require 'childprocess'
3
+ require 'thread'
4
+ require 'tempfile'
5
+
6
+ class ChildProcess::Server
7
+
8
+ # Connect to existing DRb service.
9
+ #
10
+ # @param uri [String] drb path
11
+ # @return [DrbObject<Server>] remote server
12
+ def self.connect(uri)
13
+ DRb.start_service
14
+ DRbObject.new_with_uri(uri)
15
+ end
16
+
17
+ # Start DRb service.
18
+ #
19
+ # @param wait [Bool] whether to block and wait for drb service to end
20
+ # @param uri [String] drb path
21
+ # @return [DRb::DRbServer]
22
+ def start_service(uri, wait = true)
23
+ server = DRb.start_service(uri, self)
24
+ DRb.thread.join if wait
25
+ server
26
+ end
27
+
28
+ def initialize
29
+ @processes = {}
30
+ @mutex = Mutex.new
31
+ end
32
+
33
+ # Launch a process in background.
34
+ #
35
+ # @param commands [Array<String>] commands
36
+ # @return [Integer] pid
37
+ def launch(*commands)
38
+ output = Tempfile.new('cps-out')
39
+ output.sync = true
40
+
41
+ process = ChildProcess.build(*commands)
42
+ process.io.stdout = process.io.stderr = output
43
+ process.duplex = true
44
+ process.start
45
+
46
+ pid = process.pid
47
+ access_processes do |processes|
48
+ processes[pid] = process
49
+ end
50
+ pid
51
+ end
52
+
53
+ # Read output, will not block.
54
+ #
55
+ # @param pid [Integer] process id
56
+ # @return [String] output so far, <tt>nil</tt> on error
57
+ def read_output(pid)
58
+ access_processes do |processes|
59
+ File.read(processes[pid].io.stdout.path) rescue nil
60
+ end
61
+ end
62
+
63
+ # Write to input.
64
+ #
65
+ # @param pid [Integer] process id
66
+ def write_input(pid, content)
67
+ access_processes do |processes|
68
+ processes[pid] && processes[pid].io.stdin.write(content) rescue nil
69
+ end
70
+ end
71
+
72
+ # List process ids managed by this server.
73
+ #
74
+ # @return [Array<Integer>] process ids
75
+ def list_pids
76
+ access_processes do |processes|
77
+ processes.keys
78
+ end
79
+ end
80
+
81
+ # Check whether a process managed by this server is alive.
82
+ #
83
+ # @param pid [Integer] process id
84
+ # @return [Bool] whether that process is alive,
85
+ # <tt>nil</tt> if that process is not managed by this server
86
+ def alive?(pid)
87
+ access_processes do |processes|
88
+ processes[pid] && processes[pid].alive?
89
+ end
90
+ end
91
+
92
+ # Stop a process managed by this server.
93
+ #
94
+ # @param pid [Integer] process id
95
+ def stop(pid)
96
+ access_processes do |processes|
97
+ processes[pid] && processes[pid].stop
98
+ end
99
+ end
100
+
101
+ # Clean up exited processes.
102
+ def clean_up
103
+ access_processes do |processes|
104
+ processes.values.select(&:exited?).each do |process|
105
+ process.io.stdout.path.unlink rescue nil
106
+ end
107
+ processes.delete_if { |_, process| process.exited? }
108
+ # Do not leak @processes outside
109
+ # We are using dRuby, keep input/output objects simple
110
+ nil
111
+ end
112
+ end
113
+
114
+ protected
115
+
116
+ # Access processes, exclusively.
117
+ def access_processes
118
+ @mutex.synchronize do
119
+ yield @processes
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'childprocess/server'
3
+
4
+ shared_examples_for 'common-server-tests' do
5
+ describe 'for external processes' do
6
+ it 'runs "true"' do
7
+ pid = server.launch('true')
8
+ pid.should > 0
9
+ server.list_pids.should include(pid)
10
+ server.read_output(pid).should be_empty
11
+ server.stop(pid)
12
+ server.alive?(pid).should be_false
13
+ server.clean_up.should be_nil
14
+ server.alive?(pid).should be_nil
15
+ server.list_pids.should_not include(pid)
16
+ end
17
+
18
+ it 'runs "cat" interactively' do
19
+ pid = server.launch('stdbuf', '-o0', 'cat')
20
+ server.alive?(pid).should be_true
21
+ (server.read_output(pid) || '').should be_empty
22
+ server.write_input(pid, "a\n")
23
+ sleep 0.1
24
+ server.read_output(pid).should == "a\n"
25
+ server.write_input(pid, "bc\n")
26
+ sleep 0.1
27
+ server.read_output(pid).should == "a\nbc\n"
28
+ server.stop(pid)
29
+ server.alive?(pid).should be_false
30
+ server.clean_up.should be_nil
31
+ end
32
+ end
33
+
34
+ describe 'for processes not managed' do
35
+ it '#read_output' do
36
+ server.read_output($$).should be_nil
37
+ end
38
+
39
+ it '#alive?' do
40
+ server.alive?($$).should be_nil
41
+ end
42
+
43
+ it 'stop' do
44
+ expect do
45
+ server.stop($$)
46
+ end.to_not raise_error
47
+ end
48
+
49
+ it '#write_input' do
50
+ expect do
51
+ server.write_input($$, 'test')
52
+ end.to_not raise_error
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'standalone' do
58
+ let(:server) { ChildProcess::Server.new }
59
+
60
+ it_should_behave_like 'common-server-tests'
61
+ end
62
+
63
+ describe 'client-server' do
64
+ SOCKET_FILE = File.join(Dir.tmpdir, "csspec-#{$$}.socket")
65
+ DRB_URI = "drbunix://#{SOCKET_FILE}"
66
+ THREAD_COUNT = 10
67
+ CLIENT_COUNT = 4
68
+
69
+ before(:all) do
70
+ server = ChildProcess::Server.new
71
+ @service = server.start_service(DRB_URI, false)
72
+ end
73
+
74
+ after(:all) do
75
+ @service.stop_service
76
+ File.unlink(SOCKET_FILE) rescue nil
77
+ end
78
+
79
+ let(:server) { ChildProcess::Server.connect(DRB_URI) }
80
+
81
+ it_should_behave_like 'common-server-tests'
82
+
83
+ it 'accepts multiple connections' do
84
+ expect do
85
+ clients = CLIENT_COUNT.times.map { ChildProcess::Server.connect(DRB_URI) }
86
+ pids = clients.map { |client| client.launch('sleep', '3') }
87
+ server.list_pids.should include(*pids)
88
+ pids.each { |pid| server.stop(pid) }
89
+ server.clean_up
90
+ end.to_not change { server.list_pids }
91
+ end
92
+
93
+ it 'handle concurrent requests' do
94
+ expect do
95
+ threads = THREAD_COUNT.times.map do
96
+ Thread.new do
97
+ texts = CLIENT_COUNT.times.map { rand.to_s }
98
+ clients = CLIENT_COUNT.times.map { ChildProcess::Server.connect(DRB_URI) }
99
+ pids = clients.map.with_index do |client, i|
100
+ client.launch('echo', texts[i])
101
+ end
102
+ clients[0].list_pids.should include(*pids)
103
+ sleep 0.1
104
+ pids.map.with_index do |pid, i|
105
+ clients[rand(0...clients.size)].read_output(pid).should == "#{texts[i]}\n"
106
+ end
107
+ end
108
+ end
109
+ threads.each(&:join)
110
+ sleep 0.1
111
+ server.clean_up
112
+ end.to_not change { server.list_pids }
113
+ end
114
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ # config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: childprocess-server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Wu Jun
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: childprocess
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.9
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.13'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.13'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ description: Manage and interact with CLI processes, remotely via dRuby.
70
+ email: quark@zju.edu.cn
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - LICENSE
76
+ - README.md
77
+ - Rakefile
78
+ - childprocess-server.gemspec
79
+ - lib/childprocess/server.rb
80
+ - lib/childprocess-server.rb
81
+ - spec/childprocess/server_spec.rb
82
+ - spec/spec_helper.rb
83
+ homepage: https://github.com/quark-zju/childprocess-server
84
+ licenses:
85
+ - MIT
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 2.0.3
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Manage and interact with processes, remotely.
107
+ test_files:
108
+ - spec/childprocess/server_spec.rb
109
+ - spec/spec_helper.rb
110
+ has_rdoc: yard