posix-spawn 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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