posix-spawn 0.3.0

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.
@@ -0,0 +1,5 @@
1
+ Gemfile.lock
2
+ ext/Makefile
3
+ lib/posix_spawn_ext.*
4
+ tmp
5
+ pkg
data/COPYING ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2011 by Ryan Tomayko <r@tomayko.com>
2
+ and Aman Gupta <aman@tmm1.net>
3
+
4
+ Permission is hereby granted, free of charge, to any person ob-
5
+ taining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without restric-
7
+ tion, including without limitation the rights to use, copy, modi-
8
+ fy, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is fur-
10
+ nished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
18
+ FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ A small portion of the environ dup'ing code in ext/posix-spawn.c
25
+ was taken from glibc <http://www.gnu.org/s/libc/> and is maybe
26
+ Copyright (c) 2011 by The Free Software Foundation or maybe
27
+ by others mentioned in the glibc LICENSES file. glibc is
28
+ distributed under the terms of the LGPL license.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/HACKING ADDED
@@ -0,0 +1,26 @@
1
+ Clone the project:
2
+
3
+ git clone http://github.com/rtomayko/posix-spawn.git
4
+ cd posix-spawn
5
+ bundle install
6
+
7
+ Rake tasks can be run without further setup:
8
+
9
+ rake build
10
+ rake test
11
+ rake benchmark
12
+
13
+ Just `rake' builds the extension and runs the tests.
14
+
15
+ If you want to run the benchmark scripts or tests directly out of a
16
+ working copy, first setup your PATH and RUBYLIB environment:
17
+
18
+ PATH="$(pwd)/bin:$PATH"
19
+ RUBYLIB="$(pwd)/lib:$(pwd)/ext:$RUBYLIB"
20
+ export RUBYLIB
21
+
22
+ Or, use the following rbdev script to quickly setup your PATH and
23
+ RUBYLIB environment for this and other projects adhering to the
24
+ Ruby Packaging Standard:
25
+
26
+ https://github.com/rtomayko/dotfiles/blob/rtomayko/bin/rbdev
@@ -0,0 +1,228 @@
1
+ # posix-spawn
2
+
3
+ `fork(2)` calls slow down as the parent process uses more memory due to the need
4
+ to copy page tables. In many common uses of fork(), where it is followed by one
5
+ of the exec family of functions to spawn child processes (`Kernel#system`,
6
+ `IO::popen`, `Process::spawn`, etc.), it's possible to remove this overhead by using
7
+ the use of special process spawning interfaces (`posix_spawn()`, `vfork()`, etc.)
8
+
9
+ The posix-spawn library aims to implement a subset of the Ruby 1.9 `Process::spawn`
10
+ interface in a way that takes advantage of fast process spawning interfaces when
11
+ available and provides sane fallbacks on systems that do not.
12
+
13
+ ### FEATURES
14
+
15
+ - Fast, constant-time spawn times across a variety of platforms.
16
+ - A largish compatible subset of Ruby 1.9's `Process::spawn` interface and
17
+ enhanced versions of `Kernel#system`, <code>Kernel#`</code>, etc. under
18
+ Ruby >= 1.8.7 (currently MRI only).
19
+ - High level `POSIX::Spawn::Child` class for quick (but correct!)
20
+ non-streaming IPC scenarios.
21
+
22
+ ## BENCHMARKS
23
+
24
+ The following benchmarks illustrate time needed to fork/exec a child process at
25
+ increasing resident memory sizes on Linux 2.6 and MacOS X. Tests were run using
26
+ the [`posix-spawn-benchmark`][pb] program included with the package.
27
+
28
+ [pb]: https://github.com/rtomayko/posix-spawn/tree/master/bin
29
+
30
+ ### Linux
31
+
32
+ ![](https://chart.googleapis.com/chart?chbh=a,5,25&chxr=1,0,36,7&chd=t:5.77,10.37,15.72,18.31,19.73,25.13,26.70,29.31,31.44,35.49|0.86,0.82,1.06,0.99,0.79,1.06,0.84,0.79,0.93,0.94&chxs=1N**%20secs&chs=900x200&chds=0,36&chxl=0:|50%20MB|100%20MB|150%20MB|200%20MB|250%20MB|300%20MB|350%20MB|400%20MB|450%20MB|500%20MB&cht=bvg&chdl=fspawn%20%28fork%2Bexec%29|pspawn%20%28posix_spawn%29&chtt=posix-spawn-benchmark%20--graph%20--count%20500%20--mem-size%20500%20%28x86_64-linux%29&chco=1f77b4,ff7f0e&chf=bg,s,f8f8f8&chxt=x,y#.png)
33
+
34
+ `posix_spawn` is faster than `fork+exec`, and executes in constant time when
35
+ used with `POSIX_SPAWN_USEVFORK`.
36
+
37
+ `fork+exec` is extremely slow for large parent processes.
38
+
39
+ ### OSX
40
+
41
+ ![](https://chart.googleapis.com/chart?chxl=0:|50%20MB|100%20MB|150%20MB|200%20MB|250%20MB|300%20MB|350%20MB|400%20MB|450%20MB|500%20MB&cht=bvg&chdl=fspawn%20%28fork%2Bexec%29|pspawn%20%28posix_spawn%29&chtt=posix-spawn-benchmark%20--graph%20--count%20500%20--mem-size%20500%20%28i686-darwin10.5.0%29&chco=1f77b4,ff7f0e&chf=bg,s,f8f8f8&chxt=x,y&chbh=a,5,25&chxr=1,0,3,0&chd=t:1.95,2.07,2.56,2.29,2.21,2.32,2.15,2.25,1.96,2.02|0.84,0.97,0.89,0.82,1.13,0.89,0.93,0.81,0.83,0.81&chxs=1N**%20secs&chs=900x200&chds=0,3#.png)
42
+
43
+ `posix_spawn` is faster than `fork+exec`, but neither is affected by the size of
44
+ the parent process.
45
+
46
+ ## USAGE
47
+
48
+ This library includes two distinct interfaces: `POSIX::Spawn::spawn`, a lower
49
+ level process spawning interface based on the new Ruby 1.9 `Process::spawn`
50
+ method, and `POSIX::Spawn::Child`, a higher level class geared toward easy
51
+ spawning of processes with simple string based standard input/output/error
52
+ stream handling. The former is much more versatile, the latter requires much
53
+ less code for certain common scenarios.
54
+
55
+ ### POSIX::Spawn::spawn
56
+
57
+ The `POSIX::Spawn` module (with help from the accompanying C extension)
58
+ implements a subset of the [Ruby 1.9 Process::spawn][ps] interface, largely
59
+ through the use of the [IEEE Std 1003.1 `posix_spawn(2)` systems interfaces][po].
60
+ These are widely supported by various UNIX operating systems.
61
+
62
+ [ps]: http://www.ruby-doc.org/core-1.9/classes/Process.html#M002230
63
+ [po]: http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_spawn.html
64
+
65
+ In its simplest form, the `POSIX::Spawn::spawn` method can be used to execute a
66
+ child process similar to `Kernel#system`:
67
+
68
+ require 'posix/spawn'
69
+ pid = POSIX::Spawn::spawn('echo', 'hello world')
70
+ stat = Process::waitpid(pid)
71
+
72
+ The first line executes `echo` with a single argument and immediately returns
73
+ the new process's `pid`. The second line waits for the process to complete and
74
+ returns a `Process::Status` object. Note that `spawn` *does not* wait for the
75
+ process to finish execution like `system` and does not reap the child's exit
76
+ status -- you must call `Process::waitpid` (or equivalent) or the process will
77
+ become a zombie.
78
+
79
+ The `spawn` method is capable of performing a large number of additional
80
+ operations, from setting up the new process's environment, to changing the
81
+ child's working directory, to redirecting arbitrary file descriptors.
82
+
83
+ See the Ruby 1.9 [`Process::spawn` documentation][ps] for details and the
84
+ `STATUS` section below for a full account of the various `Process::spawn`
85
+ features supported by `POSIX::Spawn::spawn`.
86
+
87
+ ### `system`, `popen4`, and <code>`</code>
88
+
89
+ In addition to the `spawn` method, Ruby 1.9 compatible implementations of
90
+ `Kernel#system` and <code>Kernel#\`</code> are provided in the `POSIX::Spawn`
91
+ module. The `popen4` method can be used to spawn a process with redirected
92
+ stdin, stdout, and stderr objects.
93
+
94
+ ### POSIX::Spawn as a Mixin
95
+
96
+ The `POSIX::Spawn` module can also be mixed in to classes and modules to include
97
+ `spawn` and all utility methods in that namespace:
98
+
99
+ require 'posix/spawn'
100
+
101
+ class YourGreatClass
102
+ include POSIX::Spawn
103
+
104
+ def speak(message)
105
+ pid = spawn('echo', message)
106
+ Process::waitpid(pid)
107
+ end
108
+
109
+ def calculate(expression)
110
+ pid, in, out, err = popen4('bc')
111
+ in.write(expression)
112
+ in.close
113
+ out.read
114
+ ensure
115
+ [in, out, err].each { |io| io.close if !io.closed? }
116
+ Process::waitpid(pid)
117
+ end
118
+ end
119
+
120
+ ### POSIX::Spawn::Child
121
+
122
+ The `POSIX::Spawn::Child` class includes logic for executing child processes and
123
+ reading/writing from their standard input, output, and error streams. It's
124
+ designed to take all input in a single string and provides all output as single
125
+ strings and is therefore not well-suited to streaming large quantities of data
126
+ in and out of commands. That said, it has some benefits:
127
+
128
+ - **Simple** - requires little code for simple stream input and capture.
129
+ - **Internally non-blocking** (using `select(2)`) - handles all pipe hang cases
130
+ due to exceeding `PIPE_BUF` limits on one or more streams.
131
+ - **Potentially portable** - abstracts lower-level process and stream
132
+ management APIs so the class can be made to work on platforms like Java and
133
+ Windows where UNIX process spawning and stream APIs are not supported.
134
+
135
+ `POSIX::Spawn::Child` takes the standard `spawn` arguments when instantiated,
136
+ and runs the process to completion after writing all input and reading all
137
+ output:
138
+
139
+ >> require 'posix/spawn'
140
+ >> child = POSIX::Spawn::Child.new('git', '--help')
141
+
142
+ Retrieve process output written to stdout / stderr, or inspect the process's
143
+ exit status:
144
+
145
+ >> child.out
146
+ => "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]\n ..."
147
+ >> child.err
148
+ => ""
149
+ >> child.status
150
+ => #<Process::Status: pid=80718,exited(0)>
151
+
152
+ Use the `:input` option to write data on the new process's stdin immediately
153
+ after spawning:
154
+
155
+ >> child = POSIX::Spawn::Child.new('bc', :input => '40 + 2')
156
+ >> child.out
157
+ "42\n"
158
+
159
+ Additional options can be used to specify the maximum output size and time of
160
+ execution before the child process is aborted. See the `POSIX::Spawn::Child`
161
+ docs for more info.
162
+
163
+ ## STATUS
164
+
165
+ The `POSIX::Spawn::spawn` method is designed to be as compatible with Ruby 1.9's
166
+ `Process::spawn` as possible. Right now, it is a compatible subset.
167
+
168
+ These `Process::spawn` arguments are currently supported to any of
169
+ `Spawn::spawn`, `Spawn::system`, `Spawn::popen4`, and `Spawn::Child.new`:
170
+
171
+ env: hash
172
+ name => val : set the environment variable
173
+ name => nil : unset the environment variable
174
+ command...:
175
+ commandline : command line string which is passed to a shell
176
+ cmdname, arg1, ... : command name and one or more arguments (no shell)
177
+ [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
178
+ options: hash
179
+ clearing environment variables:
180
+ :unsetenv_others => true : clear environment variables except specified by env
181
+ :unsetenv_others => false : don't clear (default)
182
+ redirection:
183
+ key:
184
+ FD : single file descriptor in child process
185
+ [FD, FD, ...] : multiple file descriptor in child process
186
+ value:
187
+ FD : redirect to the file descriptor in parent process
188
+ :close : close the file descriptor in child process
189
+ string : redirect to file with open(string, "r" or "w")
190
+ [string] : redirect to file with open(string, File::RDONLY)
191
+ [string, open_mode] : redirect to file with open(string, open_mode, 0644)
192
+ [string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
193
+ FD is one of follows
194
+ :in : the file descriptor 0 which is the standard input
195
+ :out : the file descriptor 1 which is the standard output
196
+ :err : the file descriptor 2 which is the standard error
197
+ integer : the file descriptor of specified the integer
198
+ io : the file descriptor specified as io.fileno
199
+ current directory:
200
+ :chdir => str
201
+
202
+ These options are currently NOT supported:
203
+
204
+ options: hash
205
+ process group:
206
+ :pgroup => true or 0 : make a new process group
207
+ :pgroup => pgid : join to specified process group
208
+ :pgroup => nil : don't change the process group (default)
209
+ resource limit: resourcename is core, cpu, data, etc. See Process.setrlimit.
210
+ :rlimit_resourcename => limit
211
+ :rlimit_resourcename => [cur_limit, max_limit]
212
+ umask:
213
+ :umask => int
214
+ redirection:
215
+ value:
216
+ [:child, FD] : redirect to the redirected file descriptor
217
+ file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
218
+ :close_others => false : inherit fds (default for system and exec)
219
+ :close_others => true : don't inherit (default for spawn and IO.popen)
220
+
221
+ ## ACKNOWLEDGEMENTS
222
+
223
+ Copyright (c) by
224
+ [Ryan Tomayko](http://tomayko.com/about)
225
+ and
226
+ [Aman Gupta](https://github.com/tmm1).
227
+
228
+ See the `COPYING` file for more information on license and redistribution.
@@ -0,0 +1,36 @@
1
+ task :default => :test
2
+
3
+ # ==========================================================
4
+ # Packaging
5
+ # ==========================================================
6
+
7
+ GEMSPEC = eval(File.read('posix-spawn.gemspec'))
8
+
9
+ require 'rake/gempackagetask'
10
+ Rake::GemPackageTask.new(GEMSPEC) do |pkg|
11
+ end
12
+
13
+ # ==========================================================
14
+ # Ruby Extension
15
+ # ==========================================================
16
+
17
+ require 'rake/extensiontask'
18
+ Rake::ExtensionTask.new('posix_spawn_ext', GEMSPEC) do |ext|
19
+ ext.ext_dir = 'ext'
20
+ end
21
+ task :build => :compile
22
+
23
+ # ==========================================================
24
+ # Testing
25
+ # ==========================================================
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new 'test' do |t|
29
+ t.test_files = FileList['test/test_*.rb']
30
+ end
31
+ task :test => :build
32
+
33
+ desc 'Run some benchmarks'
34
+ task :benchmark => :build do
35
+ ruby '-Ilib', 'bin/posix-spawn-benchmark'
36
+ end
data/TODO ADDED
@@ -0,0 +1,23 @@
1
+ [x] license (LGPL)
2
+ [x] fucking name this thing
3
+ [x] fastspawn-bm should take iterations and memsize arguments
4
+ [x] high level Grit::Process like class (string based) (tmm1)
5
+ [x] add FD => '/path/to/file' (all variations) (rtomayko)
6
+ [x] raise exception on unhandled options
7
+ [x] benchmarks in README (tmm1)
8
+ [x] POSIX::Spawn::spawn usage examples in README
9
+ [x] POSIX::Spawn#pspawn should be just #spawn
10
+ [x] :err => :out case -- currently closing out after dup2'ing
11
+ [x] POSIX::Spawn::Process.new should have same method signature as Process::spawn
12
+ [x] POSIX::Spawn::Process renamed to POSIX::Spawn::Child
13
+ [x] Better POSIX::Spawn#spawn comment docs
14
+ [x] POSIX::Spawn::Child usage examples in README
15
+
16
+
17
+ [ ] popen* interfaces
18
+ [x] system interface
19
+ [x] ` interface
20
+ [ ] jruby Grit::Process stuff
21
+ [ ] make :vfork an option to Spawn#spawn
22
+ [ ] check all posix_spawn_* function call return values
23
+ [ ] POSIX::Spawn as ::Spawn? (maybe, we'll see)
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: posix-spawn-benchmark [-n <count>] [-m <mem-size>]
3
+ #/ Run posix-spawn (Ruby extension) benchmarks and report to standard output.
4
+ #/
5
+ #/ Options:
6
+ #/ -n, --count=NUM total number of processes to spawn.
7
+ #/ -m, --mem-size=MB RES size to bloat to before performing benchmarks.
8
+ #/ -g, --graph benchmark at 10MB itervals up to RES and graph results.
9
+ #/
10
+ #/ Benchmarks run with -n 1000 -m 100 by default.
11
+ require 'optparse'
12
+ require 'posix-spawn'
13
+ require 'benchmark'
14
+ include Benchmark
15
+
16
+ allocate = 100 * (1024 ** 2)
17
+ iterations = 1_000
18
+ graphmode = false
19
+ ARGV.options do |o|
20
+ o.set_summary_indent(' ')
21
+ o.on("-n", "--count=num") { |val| iterations = val.to_i }
22
+ o.on("-m", "--mem-size=MB") { |val| allocate = val.to_i * (1024 ** 2) }
23
+ o.on("-g", "--graph") { graphmode = true }
24
+ o.on_tail("-h", "--help") { exec "grep ^#/ <'#{__FILE__}' |cut -c4-" }
25
+ o.parse!
26
+ end
27
+
28
+ if graphmode
29
+ bloat = []
30
+ data = {}
31
+ chunk = allocate / 10
32
+ max = 0
33
+
34
+ 10.times do
35
+ puts "allocating #{chunk / (1024 ** 2)}MB (#{(bloat.size+1) * chunk / (1024 ** 2)}MB total)"
36
+ bloat << ('x' * chunk)
37
+ # size = bloat.size / (1024 ** 2)
38
+
39
+ %w[ fspawn pspawn ].each do |type|
40
+ print " - benchmarking #{type}... "
41
+ time = Benchmark.realtime do
42
+ iterations.times do
43
+ pid = POSIX::Spawn.send(type, 'true')
44
+ Process.wait(pid)
45
+ end
46
+ end
47
+ puts "done (#{time})"
48
+
49
+ data[type] ||= []
50
+ data[type] << time
51
+ max = time if time > max
52
+ end
53
+ end
54
+
55
+ max = max < 0.5 ? (max * 10).round / 10.0 : max.ceil
56
+ minmb, maxmb = chunk/(1024**2), allocate/(1024**2)
57
+ series = %w[ fspawn pspawn ].map{|name| data[name].map{|d| "%.2f" % d }.join(',') }
58
+
59
+ chart = {
60
+ :chs => '900x200',
61
+ :cht => 'bvg', # grouped vertical bar chart
62
+ :chtt => "posix-spawn-benchmark --graph --count #{iterations} --mem-size #{maxmb} (#{RUBY_PLATFORM})",
63
+
64
+ :chf => 'bg,s,f8f8f8', # background
65
+ :chbh => 'a,5,25', # 25px between bar groups
66
+
67
+ :chd => "t:#{series.join('|')}", # data
68
+ :chds => "0,#{max}", # scale
69
+ :chdl => 'fspawn (fork+exec)|pspawn (posix_spawn)', # legend
70
+ :chco => '1f77b4,ff7f0e', # colors
71
+
72
+ :chxt => 'x,y',
73
+ :chxr => "1,0,#{max},#{max/5}", # y labels up to max time
74
+ :chxs => '1N** secs', # y labels are +=' secs'
75
+ :chxl => "0:|#{minmb.step(maxmb, maxmb/10).map{ |mb| "#{mb} MB"}.join('|')}", # x bucket labels
76
+ }
77
+
78
+ url = "https://chart.googleapis.com/chart?"
79
+ url += chart.map do |key, val|
80
+ "#{key}=#{val.gsub(' ','%20').gsub('(','%28').gsub(')','%29').gsub('+','%2B')}"
81
+ end.join('&')
82
+ url += '#.png'
83
+
84
+ puts url
85
+
86
+ exit!
87
+ end
88
+
89
+ puts "benchmarking fork/exec vs. posix_spawn over #{iterations} runs" +
90
+ " at #{allocate / (1024 ** 2)}M res"
91
+
92
+ # bloat the process
93
+ bloat = 'x' * allocate
94
+
95
+ # run the benchmarks
96
+ bm 40 do |x|
97
+ x.report("fspawn (fork/exec):") do
98
+ iterations.times do
99
+ pid = POSIX::Spawn.fspawn('true')
100
+ Process.wait(pid)
101
+ end
102
+ end
103
+ x.report("pspawn (posix_spawn):") do
104
+ iterations.times do
105
+ pid = POSIX::Spawn.pspawn('true')
106
+ Process.wait(pid)
107
+ end
108
+ end
109
+ if Process.respond_to?(:spawn)
110
+ x.report("spawn (native):") do
111
+ iterations.times do
112
+ pid = Process.spawn('true')
113
+ Process.wait(pid)
114
+ end
115
+ end
116
+ end
117
+ end