Skip to content

Commit 169d775

Browse files
committed
POSIX::Spawn::Child cleanup and docs
1 parent 1e59698 commit 169d775

File tree

4 files changed

+50
-44
lines changed

4 files changed

+50
-44
lines changed

TODO

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[x] POSIX::Spawn::Process.new should have same method signature as Process::spawn
1212
[x] POSIX::Spawn::Process renamed to POSIX::Spawn::Child
1313
[x] Better POSIX::Spawn#spawn comment docs
14-
[ ] POSIX::Spawn::Child usage examples in README
14+
[x] POSIX::Spawn::Child usage examples in README
1515

1616

1717
[ ] popen* interfaces

lib/posix/spawn.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module POSIX
3232
# unset in the child:
3333
#
3434
# # set FOO as BAR and unset BAZ.
35-
# pid = spawn({"FOO" => "BAR", "BAZ" => nil}, 'echo', 'hello world')
35+
# spawn({"FOO" => "BAR", "BAZ" => nil}, 'echo', 'hello world')
3636
#
3737
# == Command
3838
#
@@ -58,7 +58,7 @@ module POSIX
5858
#
5959
# The :chdir option specifies the current directory:
6060
#
61-
# pid = spawn(command, :chdir => "/var/tmp")
61+
# spawn(command, :chdir => "/var/tmp")
6262
#
6363
# The :in, :out, :err, a Fixnum, an IO object or an Array option specify
6464
# fd redirection. For example, stderr can be merged into stdout as follows:

lib/posix/spawn/child.rb

+44-38
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,80 @@
11
module POSIX
22
module Spawn
33
# POSIX::Spawn::Child includes logic for executing child processes and
4-
# reading/writing from their standard input, output, and error streams.
4+
# reading/writing from their standard input, output, and error streams. It's
5+
# designed to take all input in a single string and provides all output
6+
# (stderr and stdout) as single strings and is therefore not well-suited
7+
# to streaming large quantities of data in and out of commands.
58
#
6-
# Create an run a process to completion:
9+
# Create and run a process to completion:
710
#
8-
# >> process = POSIX::Spawn::Child.new(['git', '--help'])
11+
# >> child = POSIX::Spawn::Child.new('git', '--help')
912
#
1013
# Retrieve stdout or stderr output:
1114
#
12-
# >> process.out
15+
# >> child.out
1316
# => "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]\n ..."
14-
# >> process.err
17+
# >> child.err
1518
# => ""
1619
#
1720
# Check process exit status information:
1821
#
19-
# >> process.status
22+
# >> child.status
2023
# => #<Process::Status: pid=80718,exited(0)>
2124
#
22-
# POSIX::Spawn::Child is designed to take all input in a single string and
23-
# provides all output as single strings. It is therefore not well suited
24-
# to streaming large quantities of data in and out of commands.
25+
# To write data on the new process's stdin immediately after spawning:
26+
#
27+
# >> child = POSIX::Spawn::Child.new('bc', :input => '40 + 2')
28+
# >> child.out
29+
# "42\n"
2530
#
26-
# Q: Why not use popen3 or hand-roll fork/exec code?
31+
# Q: Why use POSIX::Spawn::Child instead of popen3, hand rolled fork/exec
32+
# code, or Process::spawn?
2733
#
2834
# - It's more efficient than popen3 and provides meaningful process
2935
# hierarchies because it performs a single fork/exec. (popen3 double forks
3036
# to avoid needing to collect the exit status and also calls
3137
# Process::detach which creates a Ruby Thread!!!!).
3238
#
33-
# - It's more portable than hand rolled pipe, fork, exec code because
34-
# fork(2) and exec(2) aren't available on all platforms. In those cases,
35-
# POSIX::Spawn::Child falls back to using whatever janky substitutes the platform
36-
# provides.
39+
# - It handles all max pipe buffer (PIPE_BUF) hang cases when reading and
40+
# writing semi-large amounts of data. This is non-trivial to implement
41+
# correctly and must be accounted for with popen3, spawn, or hand rolled
42+
# fork/exec code.
3743
#
38-
# - It handles all max pipe buffer hang cases, which is non trivial to
39-
# implement correctly and must be accounted for with either popen3 or
40-
# hand rolled fork/exec code.
44+
# - It's more portable than hand rolled pipe, fork, exec code because
45+
# fork(2) and exec aren't available on all platforms. In those cases,
46+
# POSIX::Spawn::Child falls back to using whatever janky substitutes
47+
# the platform provides.
4148
class Child
4249
include POSIX::Spawn
4350

44-
# Create and execute a new process.
51+
# Spawn a new process, write all input and read all output, and wait for
52+
# the program to exit. Supports the standard spawn interface as described
53+
# in the POSIX::Spawn module documentation:
4554
#
46-
# argv - Array of [command, arg1, ...] strings to use as the new
47-
# process's argv. When argv is a String, the shell is used
48-
# to interpret the command.
49-
# env - The new process's environment variables. This is merged with
50-
# the current environment as if by ENV.merge(env).
51-
# options - Additional options:
52-
# :input => str to write str to the process's stdin.
53-
# :timeout => int number of seconds before we given up.
54-
# :max => total number of output bytes
55-
# A subset of Process::spawn options are also supported on all
56-
# platforms:
57-
# :chdir => str to start the process in different working dir.
55+
# new([env], command, [argv1, ...], [options])
5856
#
59-
# Returns a new Child instance that has already executed to completion.
60-
# The out, err, and status attributes are immediately available.
61-
def initialize(*argv)
62-
env, argv, options = extract_process_spawn_arguments(*argv)
63-
@argv = argv
64-
@env = env
65-
57+
# The following options are supported in addition to the standard
58+
# POSIX::Spawn options:
59+
#
60+
# :input => str Write str to the new process's standard input.
61+
# :timeout => int Maximum number of seconds to allow the process
62+
# to execute before aborting with a TimeoutExceeded
63+
# exception.
64+
# :max => total Maximum number of bytes of output to allow the
65+
# process to generate before aborting with a
66+
# MaximumOutputExceeded exception.
67+
#
68+
# Returns a new Child instance whose underlying process has already
69+
# executed to completion. The out, err, and status attributes are
70+
# immediately available.
71+
def initialize(*args)
72+
@env, @argv, options = extract_process_spawn_arguments(*args)
6673
@options = options.dup
6774
@input = @options.delete(:input)
6875
@timeout = @options.delete(:timeout)
6976
@max = @options.delete(:max)
7077
@options.delete(:chdir) if @options[:chdir].nil?
71-
7278
exec!
7379
end
7480

test/test_child.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ def test_max_with_stubborn_child
7474

7575
def test_timeout
7676
assert_raise TimeoutExceeded do
77-
Child.new('sleep 1', :timeout => 0.05)
77+
Child.new('sleep', '1', :timeout => 0.05)
7878
end
7979
end
8080

8181
def test_timeout_with_child_hierarchy
8282
assert_raise TimeoutExceeded do
83-
Child.new('/bin/sh', '-c', 'yes', :timeout => 0.05)
83+
Child.new('/bin/sh', '-c', 'sleep 1', :timeout => 0.05)
8484
end
8585
end
8686

@@ -93,7 +93,7 @@ def test_lots_of_input_and_lots_of_output_at_the_same_time
9393
echo stuff on stderr 1>&2;
9494
done
9595
"
96-
p = Child.new('/bin/sh', '-c', command, :input => input)
96+
p = Child.new(command, :input => input)
9797
assert_equal input.size, p.out.size
9898
assert_equal input.size, p.err.size
9999
assert p.success?

0 commit comments

Comments
 (0)