childprocess 0.2.3 → 0.2.4
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.
- 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
|