Class: POSIX::Spawn::Child
- Inherits:
-
Object
- Object
- POSIX::Spawn::Child
- Includes:
- POSIX::Spawn
- Defined in:
- lib/posix/spawn/child.rb
Overview
POSIX::Spawn::Child includes logic for executing child processes and reading/writing from their standard input, output, and error streams. It’s designed to take all input in a single string and provides all output (stderr and stdout) as single strings and is therefore not well-suited to streaming large quantities of data in and out of commands.
Create and run a process to completion:
>> child = POSIX::Spawn::Child.new('git', '--help')
Retrieve stdout or stderr output:
>> child.out
=> "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]\n ..."
>> child.err
=> ""
Check process exit status information:
>> child.status
=> #<Process::Status: pid=80718,exited(0)>
To write data on the new process’s stdin immediately after spawning:
>> child = POSIX::Spawn::Child.new('bc', :input => '40 + 2')
>> child.out
"42\n"
To access output from the process even if an exception was raised:
>> child = POSIX::Spawn::Child.build('git', 'log', :max => 1000)
>> begin
?> child.exec!
?> rescue POSIX::Spawn::MaximumOutputExceeded
?> # just so you know
?> end
>> child.out
"... first 1000 characters of log output ..."
Q: Why use POSIX::Spawn::Child instead of popen3, hand rolled fork/exec code, or Process::spawn?
-
It’s more efficient than popen3 and provides meaningful process hierarchies because it performs a single fork/exec. (popen3 double forks to avoid needing to collect the exit status and also calls Process::detach which creates a Ruby Thread!!!!).
-
It handles all max pipe buffer (PIPE_BUF) hang cases when reading and writing semi-large amounts of data. This is non-trivial to implement correctly and must be accounted for with popen3, spawn, or hand rolled fork/exec code.
-
It’s more portable than hand rolled pipe, fork, exec code because fork(2) and exec aren’t available on all platforms. In those cases, POSIX::Spawn::Child falls back to using whatever janky substitutes the platform provides.
Constant Summary
Constants included from POSIX::Spawn
Instance Attribute Summary collapse
-
#err ⇒ Object
readonly
All data written to the child process’s stderr stream as a String.
-
#out ⇒ Object
readonly
All data written to the child process’s stdout stream as a String.
-
#pid ⇒ Object
readonly
The pid of the spawned child process.
-
#runtime ⇒ Object
readonly
Total command execution time (wall-clock time).
-
#status ⇒ Object
readonly
A Process::Status object with information on how the child exited.
Class Method Summary collapse
-
.build(*args) ⇒ Object
Set up a new process to spawn, but do not actually spawn it.
Instance Method Summary collapse
-
#exec! ⇒ Object
Execute command, write input, and read output.
-
#initialize(*args) ⇒ Child
constructor
Spawn a new process, write all input and read all output, and wait for the program to exit.
-
#success? ⇒ Boolean
Determine if the process did exit with a zero exit status.
Methods included from POSIX::Spawn
#_pspawn, #`, #fspawn, #popen4, #pspawn, #spawn, #system
Constructor Details
#initialize(*args) ⇒ Child
Spawn a new process, write all input and read all output, and wait for the program to exit. Supports the standard spawn interface as described in the POSIX::Spawn module documentation:
new([env], command, [argv1, ...], [options])
The following options are supported in addition to the standard POSIX::Spawn options:
:input => str Write str to the new process's standard input.
:timeout => int Maximum number of seconds to allow the process
to execute before aborting with a TimeoutExceeded
exception.
:max => total Maximum number of bytes of output to allow the
process to generate before aborting with a
MaximumOutputExceeded exception.
:pgroup_kill => bool Boolean specifying whether to kill the process
group (true) or individual process (false, default).
Setting this option true implies :pgroup => true.
Returns a new Child instance whose underlying process has already executed to completion. The out, err, and status attributes are immediately available.
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/posix/spawn/child.rb', line 87 def initialize(*args) @env, @argv, = extract_process_spawn_arguments(*args) @options = .dup @input = @options.delete(:input) @timeout = @options.delete(:timeout) @max = @options.delete(:max) if @options.delete(:pgroup_kill) @pgroup_kill = true @options[:pgroup] = true end @options.delete(:chdir) if @options[:chdir].nil? exec! if !@options.delete(:noexec) end |
Instance Attribute Details
#err ⇒ Object (readonly)
All data written to the child process’s stderr stream as a String.
130 131 132 |
# File 'lib/posix/spawn/child.rb', line 130 def err @err end |
#out ⇒ Object (readonly)
All data written to the child process’s stdout stream as a String.
127 128 129 |
# File 'lib/posix/spawn/child.rb', line 127 def out @out end |
#pid ⇒ Object (readonly)
The pid of the spawned child process. This is unlikely to be a valid current pid since Child#exec! doesn’t return until the process finishes and is reaped.
141 142 143 |
# File 'lib/posix/spawn/child.rb', line 141 def pid @pid end |
#runtime ⇒ Object (readonly)
Total command execution time (wall-clock time)
136 137 138 |
# File 'lib/posix/spawn/child.rb', line 136 def runtime @runtime end |
#status ⇒ Object (readonly)
A Process::Status object with information on how the child exited.
133 134 135 |
# File 'lib/posix/spawn/child.rb', line 133 def status @status end |
Class Method Details
.build(*args) ⇒ Object
Set up a new process to spawn, but do not actually spawn it.
Invoke this just like the normal constructor to set up a process to be run. Call ‘exec!` to actually run the child process, send the input, read the output, and wait for completion. Use this alternative way of constructing a POSIX::Spawn::Child if you want to read any partial output from the child process even after an exception.
child = POSIX::Spawn::Child.build(... arguments ...)
child.exec!
The arguments are the same as the regular constructor.
Returns a new Child instance but does not run the underlying process.
116 117 118 119 120 121 122 123 124 |
# File 'lib/posix/spawn/child.rb', line 116 def self.build(*args) = if args[-1].respond_to?(:to_hash) args.pop.to_hash else {} end new(*(args + [{ :noexec => true }.merge()])) end |
Instance Method Details
#exec! ⇒ Object
Execute command, write input, and read output. This is called immediately when a new instance of this object is created, or can be called explicitly when creating the Child via ‘build`.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/posix/spawn/child.rb', line 151 def exec! # spawn the process and hook up the pipes pid, stdin, stdout, stderr = popen4(@env, *(@argv + [@options])) @pid = pid # async read from all streams into buffers read_and_write(@input, stdin, stdout, stderr, @timeout, @max) # grab exit status @status = waitpid(pid) rescue Object [stdin, stdout, stderr].each { |fd| fd.close rescue nil } if @status.nil? if !@pgroup_kill ::Process.kill('TERM', pid) rescue nil else ::Process.kill('-TERM', pid) rescue nil end @status = waitpid(pid) rescue nil end raise ensure # let's be absolutely certain these are closed [stdin, stdout, stderr].each { |fd| fd.close rescue nil } end |
#success? ⇒ Boolean
Determine if the process did exit with a zero exit status.
144 145 146 |
# File 'lib/posix/spawn/child.rb', line 144 def success? @status && @status.success? end |