childprocess 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -1
- data/README.md +15 -3
- data/lib/childprocess/abstract_process.rb +14 -0
- data/lib/childprocess/errors.rb +1 -0
- data/lib/childprocess/jruby.rb +1 -1
- data/lib/childprocess/jruby/process.rb +32 -8
- data/lib/childprocess/jruby/pump.rb +51 -0
- data/lib/childprocess/unix/process.rb +22 -13
- data/lib/childprocess/version.rb +1 -1
- data/lib/childprocess/windows/functions.rb +4 -1
- data/lib/childprocess/windows/process.rb +6 -3
- data/spec/childprocess_spec.rb +30 -13
- data/spec/pid_behavior.rb +1 -1
- data/spec/spec_helper.rb +17 -0
- metadata +5 -5
- data/lib/childprocess/jruby/redirector.rb +0 -29
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -14,18 +14,30 @@ Usage
|
|
14
14
|
```ruby
|
15
15
|
process = ChildProcess.build("ruby", "-e", "sleep")
|
16
16
|
|
17
|
-
# inherit stdout/stderr from parent
|
17
|
+
# inherit stdout/stderr from parent...
|
18
18
|
process.io.inherit!
|
19
19
|
|
20
|
-
# or pass an IO
|
20
|
+
# ...or pass an IO
|
21
21
|
process.io.stdout = Tempfile.new("child-output")
|
22
22
|
|
23
|
+
# start the process
|
24
|
+
|
23
25
|
process.start
|
24
26
|
|
27
|
+
# check process status
|
25
28
|
process.alive? #=> true
|
26
29
|
process.exited? #=> false
|
27
30
|
|
28
|
-
process
|
31
|
+
# wait indefinitely for process to exit...
|
32
|
+
process.wait
|
33
|
+
process.exited? #=> true
|
34
|
+
|
35
|
+
# ...or poll for exit + force quit
|
36
|
+
begin
|
37
|
+
process.poll_for_exit(10)
|
38
|
+
rescue ChildProcess::TimeoutError
|
39
|
+
process.stop # tries increasingly harsher methods to kill the process.
|
40
|
+
end
|
29
41
|
```
|
30
42
|
|
31
43
|
The object returned from ChildProcess.build will implement ChildProcess::AbstractProcess.
|
@@ -44,6 +44,10 @@ module ChildProcess
|
|
44
44
|
raise SubclassResponsibility, "io"
|
45
45
|
end
|
46
46
|
|
47
|
+
#
|
48
|
+
# @return [Fixnum] the pid of the process after it has started
|
49
|
+
#
|
50
|
+
|
47
51
|
def pid
|
48
52
|
raise SubclassResponsibility, "pid"
|
49
53
|
end
|
@@ -71,6 +75,16 @@ module ChildProcess
|
|
71
75
|
raise SubclassResponsibility, "stop"
|
72
76
|
end
|
73
77
|
|
78
|
+
#
|
79
|
+
# Block until the process has been terminated.
|
80
|
+
#
|
81
|
+
# @return [Fixnum] The exit status of the process
|
82
|
+
#
|
83
|
+
|
84
|
+
def wait
|
85
|
+
raise SubclassResponsibility, "wait"
|
86
|
+
end
|
87
|
+
|
74
88
|
#
|
75
89
|
# Did the process exit?
|
76
90
|
#
|
data/lib/childprocess/errors.rb
CHANGED
data/lib/childprocess/jruby.rb
CHANGED
@@ -3,6 +3,12 @@ require "java"
|
|
3
3
|
module ChildProcess
|
4
4
|
module JRuby
|
5
5
|
class Process < AbstractProcess
|
6
|
+
def initialize(args)
|
7
|
+
super(args)
|
8
|
+
|
9
|
+
@pumps = []
|
10
|
+
end
|
11
|
+
|
6
12
|
def io
|
7
13
|
@io ||= JRuby::IO.new
|
8
14
|
end
|
@@ -12,6 +18,8 @@ module ChildProcess
|
|
12
18
|
|
13
19
|
assert_started
|
14
20
|
@exit_code = @process.exitValue
|
21
|
+
stop_pumps
|
22
|
+
|
15
23
|
true
|
16
24
|
rescue java.lang.IllegalThreadStateException
|
17
25
|
false
|
@@ -23,8 +31,13 @@ module ChildProcess
|
|
23
31
|
assert_started
|
24
32
|
|
25
33
|
@process.destroy
|
26
|
-
|
34
|
+
wait # no way to actually use the timeout here..
|
35
|
+
end
|
36
|
+
|
37
|
+
def wait
|
38
|
+
@process.waitFor
|
27
39
|
|
40
|
+
stop_pumps
|
28
41
|
@exit_code = @process.exitValue
|
29
42
|
end
|
30
43
|
|
@@ -37,7 +50,7 @@ module ChildProcess
|
|
37
50
|
#
|
38
51
|
def pid
|
39
52
|
if @process.getClass.getName != "java.lang.UNIXProcess"
|
40
|
-
raise NotImplementedError
|
53
|
+
raise NotImplementedError, "pid is only supported by JRuby child processes on Unix"
|
41
54
|
end
|
42
55
|
|
43
56
|
# About the best way we can do this is with a nasty reflection-based impl
|
@@ -56,21 +69,29 @@ module ChildProcess
|
|
56
69
|
pb.directory java.io.File.new(Dir.pwd)
|
57
70
|
set_env pb.environment
|
58
71
|
|
59
|
-
|
72
|
+
begin
|
73
|
+
@process = pb.start
|
74
|
+
rescue java.io.IOException => ex
|
75
|
+
raise LaunchError, ex.message
|
76
|
+
end
|
77
|
+
|
60
78
|
setup_io
|
61
79
|
end
|
62
80
|
|
63
81
|
def setup_io
|
64
82
|
if @io
|
65
|
-
redirect
|
66
|
-
redirect
|
83
|
+
@pumps << redirect(@process.getErrorStream, @io.stderr)
|
84
|
+
@pumps << redirect(@process.getInputStream, @io.stdout)
|
67
85
|
else
|
68
86
|
@process.getErrorStream.close
|
69
87
|
@process.getInputStream.close
|
70
88
|
end
|
71
89
|
|
72
90
|
if duplex?
|
73
|
-
|
91
|
+
stdin = @process.getOutputStream.to_io
|
92
|
+
stdin.sync = true
|
93
|
+
|
94
|
+
io._stdin = stdin
|
74
95
|
else
|
75
96
|
@process.getOutputStream.close
|
76
97
|
end
|
@@ -82,8 +103,11 @@ module ChildProcess
|
|
82
103
|
return
|
83
104
|
end
|
84
105
|
|
85
|
-
|
86
|
-
|
106
|
+
Pump.new(input, output.to_outputstream).run
|
107
|
+
end
|
108
|
+
|
109
|
+
def stop_pumps
|
110
|
+
@pumps.each { |pump| pump.stop }
|
87
111
|
end
|
88
112
|
|
89
113
|
def set_env(env)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module JRuby
|
3
|
+
class Pump
|
4
|
+
BUFFER_SIZE = 2048
|
5
|
+
|
6
|
+
def initialize(input, output)
|
7
|
+
@input = input
|
8
|
+
@output = output
|
9
|
+
@stop = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def stop
|
13
|
+
@stop = true
|
14
|
+
@thread && @thread.join
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
@thread = Thread.new { pump }
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def pump
|
26
|
+
buffer = Java.byte[BUFFER_SIZE].new
|
27
|
+
|
28
|
+
until @stop
|
29
|
+
read, avail = 0, 0
|
30
|
+
|
31
|
+
while read != -1
|
32
|
+
avail = [@input.available, 1].max
|
33
|
+
read = @input.read(buffer, 0, avail)
|
34
|
+
|
35
|
+
if read > 0
|
36
|
+
@output.write(buffer, 0, read)
|
37
|
+
@output.flush
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
sleep 0.1
|
42
|
+
end
|
43
|
+
|
44
|
+
@output.flush
|
45
|
+
rescue java.io.IOException => ex
|
46
|
+
$stderr.puts ex.message, ex.backtrace if $DEBUG
|
47
|
+
end
|
48
|
+
|
49
|
+
end # Pump
|
50
|
+
end # JRuby
|
51
|
+
end # ChildProcess
|
@@ -1,9 +1,6 @@
|
|
1
1
|
module ChildProcess
|
2
2
|
module Unix
|
3
3
|
class Process < AbstractProcess
|
4
|
-
#
|
5
|
-
# @return [Fixnum] the pid of the process after it has started
|
6
|
-
#
|
7
4
|
attr_reader :pid
|
8
5
|
|
9
6
|
def io
|
@@ -28,12 +25,6 @@ module ChildProcess
|
|
28
25
|
true
|
29
26
|
end
|
30
27
|
|
31
|
-
#
|
32
|
-
# Did the process exit?
|
33
|
-
#
|
34
|
-
# @return [Boolean]
|
35
|
-
#
|
36
|
-
|
37
28
|
def exited?
|
38
29
|
return true if @exit_code
|
39
30
|
|
@@ -49,12 +40,14 @@ module ChildProcess
|
|
49
40
|
!!pid
|
50
41
|
end
|
51
42
|
|
52
|
-
private
|
53
|
-
|
54
43
|
def wait
|
55
|
-
|
44
|
+
pid, status = ::Process.waitpid2 @pid
|
45
|
+
|
46
|
+
@exit_code = status.exitstatus || status.termsig
|
56
47
|
end
|
57
48
|
|
49
|
+
private
|
50
|
+
|
58
51
|
def send_term
|
59
52
|
send_signal 'TERM'
|
60
53
|
end
|
@@ -76,11 +69,16 @@ module ChildProcess
|
|
76
69
|
stderr = @io.stderr
|
77
70
|
end
|
78
71
|
|
72
|
+
# pipe used to detect exec() failure
|
73
|
+
exec_r, exec_w = ::IO.pipe
|
74
|
+
ChildProcess.close_on_exec exec_w
|
75
|
+
|
79
76
|
if duplex?
|
80
77
|
reader, writer = ::IO.pipe
|
81
78
|
end
|
82
79
|
|
83
80
|
@pid = fork {
|
81
|
+
exec_r.close
|
84
82
|
set_env
|
85
83
|
|
86
84
|
STDOUT.reopen(stdout || "/dev/null")
|
@@ -91,14 +89,25 @@ module ChildProcess
|
|
91
89
|
writer.close
|
92
90
|
end
|
93
91
|
|
94
|
-
|
92
|
+
begin
|
93
|
+
exec(*@args)
|
94
|
+
rescue SystemCallError => ex
|
95
|
+
exec_w << ex.message
|
96
|
+
end
|
95
97
|
}
|
96
98
|
|
99
|
+
exec_w.close
|
100
|
+
|
97
101
|
if duplex?
|
98
102
|
io._stdin = writer
|
99
103
|
reader.close
|
100
104
|
end
|
101
105
|
|
106
|
+
# if we don't eventually get EOF, exec() failed
|
107
|
+
unless exec_r.eof?
|
108
|
+
raise LaunchError, exec_r.read || "executing command with #{@args.inspect} failed"
|
109
|
+
end
|
110
|
+
|
102
111
|
::Process.detach(@pid) if detach?
|
103
112
|
end
|
104
113
|
|
data/lib/childprocess/version.rb
CHANGED
@@ -34,6 +34,9 @@ module ChildProcess
|
|
34
34
|
read_pipe = read_pipe_ptr.read_pointer
|
35
35
|
write_pipe = write_pipe_ptr.read_pointer
|
36
36
|
|
37
|
+
ok = set_handle_information(write_pipe.address, HANDLE_FLAG_INHERIT, 0)
|
38
|
+
ok or raise Error, last_error_message
|
39
|
+
|
37
40
|
si[:hStdInput] = read_pipe
|
38
41
|
end
|
39
42
|
|
@@ -50,7 +53,7 @@ module ChildProcess
|
|
50
53
|
pi # process info
|
51
54
|
)
|
52
55
|
|
53
|
-
ok or raise
|
56
|
+
ok or raise LaunchError, last_error_message
|
54
57
|
|
55
58
|
close_handle pi[:hProcess]
|
56
59
|
close_handle pi[:hThread]
|
@@ -1,9 +1,7 @@
|
|
1
1
|
module ChildProcess
|
2
2
|
module Windows
|
3
3
|
class Process < AbstractProcess
|
4
|
-
|
5
|
-
# @return [Fixnum] the pid of the process after it has started
|
6
|
-
#
|
4
|
+
|
7
5
|
attr_reader :pid
|
8
6
|
|
9
7
|
def io
|
@@ -22,6 +20,11 @@ module ChildProcess
|
|
22
20
|
@handle.close
|
23
21
|
end
|
24
22
|
|
23
|
+
def wait
|
24
|
+
@handle.wait
|
25
|
+
@exit_code = @handle.exit_code
|
26
|
+
end
|
27
|
+
|
25
28
|
def exited?
|
26
29
|
return true if @exit_code
|
27
30
|
assert_started
|
data/spec/childprocess_spec.rb
CHANGED
@@ -13,20 +13,32 @@ describe ChildProcess do
|
|
13
13
|
process.should be_started
|
14
14
|
end
|
15
15
|
|
16
|
+
it "raises ChildProcess::LaunchError if the process can't be started" do
|
17
|
+
lambda { invalid_process.start }.should raise_error(ChildProcess::LaunchError)
|
18
|
+
end
|
19
|
+
|
16
20
|
it "knows if the process crashed" do
|
17
21
|
process = exit_with(1).start
|
18
|
-
process.
|
22
|
+
process.wait
|
19
23
|
|
20
24
|
process.should be_crashed
|
21
25
|
end
|
22
26
|
|
23
27
|
it "knows if the process didn't crash" do
|
24
28
|
process = exit_with(0).start
|
25
|
-
process.
|
29
|
+
process.wait
|
26
30
|
|
27
31
|
process.should_not be_crashed
|
28
32
|
end
|
29
33
|
|
34
|
+
it "can wait for a process to finish" do
|
35
|
+
process = exit_with(0).start
|
36
|
+
return_value = process.wait
|
37
|
+
|
38
|
+
process.should_not be_alive
|
39
|
+
return_value.should == 0
|
40
|
+
end
|
41
|
+
|
30
42
|
it "escalates if TERM is ignored" do
|
31
43
|
process = ignored('TERM').start
|
32
44
|
process.stop
|
@@ -42,7 +54,7 @@ describe ChildProcess do
|
|
42
54
|
Tempfile.open("env-spec") do |file|
|
43
55
|
with_env('INHERITED' => 'yes') do
|
44
56
|
process = write_env(file.path).start
|
45
|
-
process.
|
57
|
+
process.wait
|
46
58
|
end
|
47
59
|
|
48
60
|
file.rewind
|
@@ -59,7 +71,7 @@ describe ChildProcess do
|
|
59
71
|
|
60
72
|
ENV['CHILD_ONLY'].should be_nil
|
61
73
|
|
62
|
-
process.
|
74
|
+
process.wait
|
63
75
|
file.rewind
|
64
76
|
|
65
77
|
child_env = eval(file.read)
|
@@ -74,7 +86,8 @@ describe ChildProcess do
|
|
74
86
|
process.environment['CHILD_ONLY'] = 'yes'
|
75
87
|
|
76
88
|
process.start
|
77
|
-
process.
|
89
|
+
process.wait
|
90
|
+
|
78
91
|
file.rewind
|
79
92
|
child_env = eval(file.read)
|
80
93
|
|
@@ -89,7 +102,7 @@ describe ChildProcess do
|
|
89
102
|
|
90
103
|
Tempfile.open("argv-spec") do |file|
|
91
104
|
process = write_argv(file.path, *args).start
|
92
|
-
process.
|
105
|
+
process.wait
|
93
106
|
|
94
107
|
file.rewind
|
95
108
|
file.read.should == args.inspect
|
@@ -119,7 +132,7 @@ describe ChildProcess do
|
|
119
132
|
|
120
133
|
process.start
|
121
134
|
process.io.stdin.should be_nil
|
122
|
-
process.
|
135
|
+
process.wait
|
123
136
|
|
124
137
|
out.rewind
|
125
138
|
err.rewind
|
@@ -133,11 +146,10 @@ describe ChildProcess do
|
|
133
146
|
end
|
134
147
|
|
135
148
|
it "can write to stdin if duplex = true" do
|
136
|
-
process =
|
137
|
-
puts(STDIN.gets.chomp)
|
138
|
-
CODE
|
149
|
+
process = cat
|
139
150
|
|
140
151
|
out = Tempfile.new("duplex")
|
152
|
+
out.sync = true
|
141
153
|
|
142
154
|
begin
|
143
155
|
process.io.stdout = out
|
@@ -146,7 +158,7 @@ describe ChildProcess do
|
|
146
158
|
|
147
159
|
process.start
|
148
160
|
process.io.stdin.puts "hello world"
|
149
|
-
process.io.stdin.close
|
161
|
+
process.io.stdin.close
|
150
162
|
|
151
163
|
process.poll_for_exit(EXIT_TIMEOUT)
|
152
164
|
|
@@ -177,7 +189,7 @@ describe ChildProcess do
|
|
177
189
|
process.io.stdout = process.io.stderr = file
|
178
190
|
|
179
191
|
process.start
|
180
|
-
process.
|
192
|
+
process.wait
|
181
193
|
|
182
194
|
file.rewind
|
183
195
|
file.read.should == Dir.pwd
|
@@ -189,11 +201,16 @@ describe ChildProcess do
|
|
189
201
|
|
190
202
|
Tempfile.open("argv-spec") do |file|
|
191
203
|
process = write_argv(file.path, *args).start
|
192
|
-
process.
|
204
|
+
process.wait
|
193
205
|
|
194
206
|
file.rewind
|
195
207
|
file.read.should == args.inspect
|
196
208
|
end
|
197
209
|
end
|
198
210
|
|
211
|
+
it "times out when polling for exit" do
|
212
|
+
process = sleeping_ruby.start
|
213
|
+
lambda { process.poll_for_exit(0.1) }.should raise_error(ChildProcess::TimeoutError)
|
214
|
+
end
|
215
|
+
|
199
216
|
end
|
data/spec/pid_behavior.rb
CHANGED
@@ -4,7 +4,7 @@ shared_examples_for "a platform that provides the child's pid" do
|
|
4
4
|
it "knows the child's pid" do
|
5
5
|
Tempfile.open("pid-spec") do |file|
|
6
6
|
process = write_pid(file.path).start
|
7
|
-
process.
|
7
|
+
process.wait
|
8
8
|
file.rewind
|
9
9
|
|
10
10
|
process.pid.should == file.read.chomp.to_i
|
data/spec/spec_helper.rb
CHANGED
@@ -18,6 +18,10 @@ module ChildProcessSpecHelper
|
|
18
18
|
ruby_process("-e", "sleep")
|
19
19
|
end
|
20
20
|
|
21
|
+
def invalid_process
|
22
|
+
@process = ChildProcess.build("unlikely-to-exist")
|
23
|
+
end
|
24
|
+
|
21
25
|
def ignored(signal)
|
22
26
|
code = <<-RUBY
|
23
27
|
trap(#{signal.inspect}, "IGNORE")
|
@@ -90,6 +94,19 @@ module ChildProcessSpecHelper
|
|
90
94
|
raise last_error unless ok
|
91
95
|
end
|
92
96
|
|
97
|
+
def cat
|
98
|
+
if ChildProcess.os == :windows
|
99
|
+
ruby(<<-CODE)
|
100
|
+
STDIN.sync = true
|
101
|
+
STDOUT.sync = true
|
102
|
+
|
103
|
+
puts STDIN.read
|
104
|
+
CODE
|
105
|
+
else
|
106
|
+
ChildProcess.build("cat")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
93
110
|
def ruby(code)
|
94
111
|
ruby_process(tmp_script(code))
|
95
112
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: childprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 4
|
10
|
+
version: 0.2.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jari Bakken
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-12-27 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rspec
|
@@ -105,7 +105,7 @@ files:
|
|
105
105
|
- lib/childprocess/jruby.rb
|
106
106
|
- lib/childprocess/jruby/io.rb
|
107
107
|
- lib/childprocess/jruby/process.rb
|
108
|
-
- lib/childprocess/jruby/
|
108
|
+
- lib/childprocess/jruby/pump.rb
|
109
109
|
- lib/childprocess/unix.rb
|
110
110
|
- lib/childprocess/unix/io.rb
|
111
111
|
- lib/childprocess/unix/process.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module ChildProcess
|
2
|
-
module JRuby
|
3
|
-
class Redirector
|
4
|
-
BUFFER_SIZE = 2048
|
5
|
-
|
6
|
-
def initialize(input, output)
|
7
|
-
@input = input
|
8
|
-
@output = output
|
9
|
-
@buffer = Java.byte[BUFFER_SIZE].new
|
10
|
-
end
|
11
|
-
|
12
|
-
def run
|
13
|
-
read, avail = 0, 0
|
14
|
-
|
15
|
-
while(read != -1)
|
16
|
-
avail = [@input.available, 1].max
|
17
|
-
read = @input.read(@buffer, 0, avail)
|
18
|
-
|
19
|
-
if read > 0
|
20
|
-
@output.write(@buffer, 0, read)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
rescue java.io.IOException => ex
|
24
|
-
$stderr.puts ex.message, ex.backtrace if $DEBUG
|
25
|
-
end
|
26
|
-
|
27
|
-
end # Redirector
|
28
|
-
end # JRuby
|
29
|
-
end # ChildProcess
|