open4 1.0.1 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTUyNzE3MzczYmM1N2M5ZTQ0MWY0ZTkxMGM2NDcwODQzZWU3YjBkZg==
5
+ data.tar.gz: !binary |-
6
+ NDU2NWRlNGIxZjg5OGZmYjA5OGZhNDhhMTBiZDI2NTEyYzgxMTc5ZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZDJjMzYwZjA5OGY5ODI2MDA3YzY5MmVkZjNjYjYzYTQyMjIzNmRjNzk3NDZi
10
+ NjRiMDExNjZjZDBkMWM5MWY0ZTlmNTYxNDRlODQ5NTNjMjk3YzcyNTYzYzU0
11
+ ZGIxYTNiZjc0MjdkY2JkZmNkYWIxYWY1MDlmY2RmYTdmZTUyZDk=
12
+ data.tar.gz: !binary |-
13
+ MzkxZDc2ODhjMjk4ZDEwMzM0YjIxYzEyYThiYTg3YmRlOTljYmRiOGUzMGY2
14
+ NTU0NmM4OTg2OTRlNDYwODMzMmU5ODA5OTkzNmYzMTgxYmIzY2ZlNGQ1NzE4
15
+ ZmY3ZDNlZmRmYzRiYmE2MGM0MDgxYjJhYWZkZTZhM2Q4ZmIyMzM=
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ same as Ruby's
2
+
3
+ http://www.ruby-lang.org/en/LICENSE.txt
data/README CHANGED
@@ -193,6 +193,44 @@ SAMPLES
193
193
  42
194
194
  /dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error)
195
195
 
196
+ ----------------------------------------------------------------------------
197
+ pfork4 is similar to popen4, but instead of executing a command, it runs
198
+ ruby code in a child process. if the child process raises an exception, it
199
+ propagates to the parent.
200
+ ----------------------------------------------------------------------------
201
+
202
+ harp: > cat sample/pfork4.rb
203
+ require 'open4'
204
+
205
+ echo = lambda do
206
+ $stdout.write $stdin.read
207
+ raise 'finish implementing me'
208
+ end
209
+
210
+ org_message = "hello, world!"
211
+ got_message = nil
212
+ exception = nil
213
+
214
+ begin
215
+ Open4.pfork4(echo) do |cid, stdin, stdout, stderr|
216
+ stdin.write org_message
217
+ stdin.close
218
+ got_message = stdout.read
219
+ end
220
+ rescue RuntimeError => e
221
+ exception = e.to_s
222
+ end
223
+
224
+ puts "org_message: #{org_message}"
225
+ puts "got_message: #{got_message}"
226
+ puts "exception : #{exception}"
227
+
228
+
229
+ harp: > ruby sample/pfork4.rb
230
+ org_message: hello, world!
231
+ got_message: hello, world!
232
+ exception : finish implementing me
233
+
196
234
  HISTORY
197
235
  1.0.0
198
236
  - added ability for spawn to take a proc (respond_to?(:call))
data/README.erb CHANGED
@@ -193,6 +193,44 @@ SAMPLES
193
193
  42
194
194
  /dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error)
195
195
 
196
+ ----------------------------------------------------------------------------
197
+ pfork4 is similar to popen4, but instead of executing a command, it runs
198
+ ruby code in a child process. if the child process raises an exception, it
199
+ propagates to the parent.
200
+ ----------------------------------------------------------------------------
201
+
202
+ harp: > cat sample/pfork4.rb
203
+ require 'open4'
204
+
205
+ echo = lambda do
206
+ $stdout.write $stdin.read
207
+ raise 'finish implementing me'
208
+ end
209
+
210
+ org_message = "hello, world!"
211
+ got_message = nil
212
+ exception = nil
213
+
214
+ begin
215
+ Open4.pfork4(echo) do |cid, stdin, stdout, stderr|
216
+ stdin.write org_message
217
+ stdin.close
218
+ got_message = stdout.read
219
+ end
220
+ rescue RuntimeError => e
221
+ exception = e.to_s
222
+ end
223
+
224
+ puts "org_message: #{org_message}"
225
+ puts "got_message: #{got_message}"
226
+ puts "exception : #{exception}"
227
+
228
+
229
+ harp: > ruby sample/pfork4.rb
230
+ org_message: hello, world!
231
+ got_message: hello, world!
232
+ exception : finish implementing me
233
+
196
234
  HISTORY
197
235
  1.0.0
198
236
  - added ability for spawn to take a proc (respond_to?(:call))
data/lib/open4.rb CHANGED
@@ -4,23 +4,69 @@ require 'timeout'
4
4
  require 'thread'
5
5
 
6
6
  module Open4
7
- #--{{{
8
- VERSION = '1.0.1'
9
- def self.version() VERSION end
7
+ VERSION = '1.3.4'
8
+ def Open4.version() VERSION end
9
+
10
+ def Open4.description
11
+ 'open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily.'
12
+ end
10
13
 
11
14
  class Error < ::StandardError; end
12
15
 
16
+ def pfork4(fun, &b)
17
+ Open4.do_popen(b, :block) do |ps_read, _|
18
+ ps_read.close
19
+ begin
20
+ fun.call
21
+ rescue SystemExit => e
22
+ # Make it seem to the caller that calling Kernel#exit in +fun+ kills
23
+ # the child process normally. Kernel#exit! bypasses this rescue
24
+ # block.
25
+ exit! e.status
26
+ else
27
+ exit! 0
28
+ end
29
+ end
30
+ end
31
+ module_function :pfork4
32
+
13
33
  def popen4(*cmd, &b)
14
- #--{{{
34
+ Open4.do_popen(b, :init) do |ps_read, ps_write|
35
+ ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
36
+ ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
37
+ exec(*cmd)
38
+ raise 'forty-two' # Is this really needed?
39
+ end
40
+ end
41
+ alias open4 popen4
42
+ module_function :popen4
43
+ module_function :open4
44
+
45
+ def popen4ext(closefds=false, *cmd, &b)
46
+ Open4.do_popen(b, :init, closefds) do |ps_read, ps_write|
47
+ ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
48
+ ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
49
+ exec(*cmd)
50
+ raise 'forty-two' # Is this really needed?
51
+ end
52
+ end
53
+ module_function :popen4ext
54
+
55
+ def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
15
56
  pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
16
57
 
17
58
  verbose = $VERBOSE
18
59
  begin
19
60
  $VERBOSE = nil
20
- ps.first.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
21
- ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
22
61
 
23
62
  cid = fork {
63
+ if closefds
64
+ exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten
65
+ ObjectSpace.each_object(IO){|io|
66
+ io.close if (not io.closed?) and (not exlist.include? io.fileno) rescue nil
67
+ }
68
+ end
69
+
24
70
  pw.last.close
25
71
  STDIN.reopen pw.first
26
72
  pw.first.close
@@ -36,52 +82,59 @@ module Open4
36
82
  STDOUT.sync = STDERR.sync = true
37
83
 
38
84
  begin
39
- exec(*cmd)
40
- raise 'forty-two'
85
+ cmd.call(ps)
41
86
  rescue Exception => e
42
87
  Marshal.dump(e, ps.last)
43
88
  ps.last.flush
89
+ ensure
90
+ ps.last.close unless ps.last.closed?
44
91
  end
45
- ps.last.close unless (ps.last.closed?)
92
+
46
93
  exit!
47
94
  }
48
95
  ensure
49
96
  $VERBOSE = verbose
50
97
  end
51
98
 
52
- [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
99
+ [ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close }
53
100
 
54
- begin
55
- e = Marshal.load ps.first
56
- raise(Exception === e ? e : "unknown failure!")
57
- rescue EOFError # If we get an EOF error, then the exec was successful
58
- 42
59
- ensure
60
- ps.first.close
61
- end
101
+ Open4.propagate_exception cid, ps.first if exception_propagation_at == :init
62
102
 
63
103
  pw.last.sync = true
64
104
 
65
- pi = [pw.last, pr.first, pe.first]
105
+ pi = [ pw.last, pr.first, pe.first ]
106
+
107
+ begin
108
+ return [cid, *pi] unless b
66
109
 
67
- if b
68
110
  begin
69
- b[cid, *pi]
70
- Process.waitpid2(cid).last
111
+ b.call(cid, *pi)
71
112
  ensure
72
- pi.each{|fd| fd.close unless fd.closed?}
113
+ pi.each { |fd| fd.close unless fd.closed? }
73
114
  end
74
- else
75
- [cid, pw.last, pr.first, pe.first]
115
+
116
+ Open4.propagate_exception cid, ps.first if exception_propagation_at == :block
117
+
118
+ Process.waitpid2(cid).last
119
+ ensure
120
+ ps.first.close unless ps.first.closed?
76
121
  end
77
- #--}}}
78
122
  end
79
- alias open4 popen4
80
- module_function :popen4
81
- module_function :open4
123
+
124
+ def self.propagate_exception(cid, ps_read)
125
+ e = Marshal.load ps_read
126
+ raise Exception === e ? e : "unknown failure!"
127
+ rescue EOFError
128
+ # Child process did not raise exception.
129
+ rescue
130
+ # Child process raised exception; wait it in order to avoid a zombie.
131
+ Process.waitpid2 cid
132
+ raise
133
+ ensure
134
+ ps_read.close
135
+ end
82
136
 
83
137
  class SpawnError < Error
84
- #--{{{
85
138
  attr 'cmd'
86
139
  attr 'status'
87
140
  attr 'signals'
@@ -98,11 +151,9 @@ module Open4
98
151
  sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
99
152
  super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
100
153
  end
101
- #--}}}
102
154
  end
103
155
 
104
156
  class ThreadEnsemble
105
- #--{{{
106
157
  attr 'threads'
107
158
 
108
159
  def initialize cid
@@ -132,9 +183,9 @@ module Open4
132
183
 
133
184
  begin
134
185
  @argv.each do |a, b|
135
- @threads << Thread.new(*a) do |*a|
186
+ @threads << Thread.new(*a) do |*_a|
136
187
  begin
137
- b[*a]
188
+ b[*_a]
138
189
  ensure
139
190
  killall rescue nil if $!
140
191
  @done.push Thread.current
@@ -154,54 +205,48 @@ module Open4
154
205
  def all_done
155
206
  @threads.size.times{ @done.pop }
156
207
  end
157
- #--}}}
158
208
  end
159
209
 
160
210
  def to timeout = nil
161
- #--{{{
162
211
  Timeout.timeout(timeout){ yield }
163
- #--}}}
164
212
  end
165
213
  module_function :to
166
214
 
167
215
  def new_thread *a, &b
168
- #--{{{
169
216
  cur = Thread.current
170
- Thread.new(*a) do |*a|
217
+ Thread.new(*a) do |*_a|
171
218
  begin
172
- b[*a]
219
+ b[*_a]
173
220
  rescue Exception => e
174
221
  cur.raise e
175
222
  end
176
223
  end
177
- #--}}}
178
224
  end
179
225
  module_function :new_thread
180
226
 
181
227
  def getopts opts = {}
182
- #--{{{
183
228
  lambda do |*args|
184
- keys, default, ignored = args
185
- catch('opt') do
229
+ keys, default, _ = args
230
+ catch(:opt) do
186
231
  [keys].flatten.each do |key|
187
- [key, key.to_s, key.to_s.intern].each do |key|
188
- throw 'opt', opts[key] if opts.has_key?(key)
232
+ [key, key.to_s, key.to_s.intern].each do |_key|
233
+ throw :opt, opts[_key] if opts.has_key?(_key)
189
234
  end
190
235
  end
191
236
  default
192
237
  end
193
238
  end
194
- #--}}}
195
239
  end
196
240
  module_function :getopts
197
241
 
198
242
  def relay src, dst = nil, t = nil
199
- #--{{{
200
243
  send_dst =
201
244
  if dst.respond_to?(:call)
202
245
  lambda{|buf| dst.call(buf)}
246
+ elsif dst.respond_to?(:<<)
247
+ lambda{|buf| dst << buf }
203
248
  else
204
- lambda{|buf| dst << buf}
249
+ lambda{|buf| buf }
205
250
  end
206
251
 
207
252
  unless src.nil?
@@ -214,19 +259,19 @@ module Open4
214
259
  q = Queue.new
215
260
  th = nil
216
261
 
217
- timer_set = lambda do |t|
218
- th = new_thread{ to(t){ q.pop } }
262
+ timer_set = lambda do |_t|
263
+ th = new_thread{ to(_t){ q.pop } }
219
264
  end
220
265
 
221
- timer_cancel = lambda do |t|
266
+ timer_cancel = lambda do |_t|
222
267
  th.kill if th rescue nil
223
268
  end
224
269
 
225
270
  timer_set[t]
226
271
  begin
227
- src.each do |buf|
272
+ src.each do |_buf|
228
273
  timer_cancel[t]
229
- send_dst[buf]
274
+ send_dst[_buf]
230
275
  timer_set[t]
231
276
  end
232
277
  ensure
@@ -242,12 +287,10 @@ module Open4
242
287
  send_dst[buf]
243
288
  end
244
289
  end
245
- #--}}}
246
290
  end
247
291
  module_function :relay
248
292
 
249
293
  def spawn arg, *argv
250
- #--{{{
251
294
  argv.unshift(arg)
252
295
  opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
253
296
  argv.flatten!
@@ -269,6 +312,7 @@ module Open4
269
312
  stderr_timeout = getopt[ %w( stderr_timeout ) ]
270
313
  status = getopt[ %w( status ) ]
271
314
  cwd = getopt[ %w( cwd dir ) ]
315
+ closefds = getopt[ %w( close_fds ) ]
272
316
 
273
317
  exitstatus =
274
318
  case exitstatus
@@ -289,7 +333,7 @@ module Open4
289
333
  begin
290
334
  chdir(cwd) do
291
335
  Timeout::timeout(timeout) do
292
- popen4(*argv) do |c, i, o, e|
336
+ popen4ext(closefds, *argv) do |c, i, o, e|
293
337
  started = true
294
338
 
295
339
  %w( replace pid= << push update ).each do |msg|
@@ -298,17 +342,17 @@ module Open4
298
342
 
299
343
  te = ThreadEnsemble.new c
300
344
 
301
- te.add_thread(i, stdin) do |i, stdin|
302
- relay stdin, i, stdin_timeout
303
- i.close rescue nil
345
+ te.add_thread(i, stdin) do |_i, _stdin|
346
+ relay _stdin, _i, stdin_timeout
347
+ _i.close rescue nil
304
348
  end
305
349
 
306
- te.add_thread(o, stdout) do |o, stdout|
307
- relay o, stdout, stdout_timeout
350
+ te.add_thread(o, stdout) do |_o, _stdout|
351
+ relay _o, _stdout, stdout_timeout
308
352
  end
309
353
 
310
- te.add_thread(e, stderr) do |o, stderr|
311
- relay e, stderr, stderr_timeout
354
+ te.add_thread(e, stderr) do |_o, _stderr| # HACK: I think this is a bug
355
+ relay e, _stderr, stderr_timeout
312
356
  end
313
357
 
314
358
  te.run
@@ -323,7 +367,6 @@ module Open4
323
367
  (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
324
368
 
325
369
  status
326
- #--}}}
327
370
  end
328
371
  module_function :spawn
329
372
 
@@ -334,7 +377,6 @@ module Open4
334
377
  module_function :chdir
335
378
 
336
379
  def background arg, *argv
337
- #--{{{
338
380
  require 'thread'
339
381
  q = Queue.new
340
382
  opts = { 'pid' => q, :pid => q }
@@ -344,7 +386,7 @@ module Open4
344
386
  else
345
387
  argv.push opts
346
388
  end
347
- thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
389
+ thread = Thread.new(arg, argv){|_arg, _argv| spawn _arg, *_argv}
348
390
  sc = class << thread; self; end
349
391
  sc.module_eval {
350
392
  define_method(:pid){ @pid ||= q.pop }
@@ -352,14 +394,12 @@ module Open4
352
394
  define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
353
395
  }
354
396
  thread
355
- #--}}}
356
397
  end
357
398
  alias bg background
358
399
  module_function :background
359
400
  module_function :bg
360
401
 
361
402
  def maim pid, opts = {}
362
- #--{{{
363
403
  getopt = getopts opts
364
404
  sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
365
405
  suspend = getopt[ 'suspend', 4 ]
@@ -377,12 +417,10 @@ module Open4
377
417
  return true unless alive? pid
378
418
  end
379
419
  return(not alive?(pid))
380
- #--}}}
381
420
  end
382
421
  module_function :maim
383
422
 
384
423
  def alive pid
385
- #--{{{
386
424
  pid = Integer pid
387
425
  begin
388
426
  Process.kill 0, pid
@@ -390,12 +428,10 @@ module Open4
390
428
  rescue Errno::ESRCH
391
429
  false
392
430
  end
393
- #--}}}
394
431
  end
395
432
  alias alive? alive
396
433
  module_function :alive
397
434
  module_function :'alive?'
398
- #--}}}
399
435
  end
400
436
 
401
437
  def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
data/open4.gemspec CHANGED
@@ -3,26 +3,51 @@
3
3
 
4
4
  Gem::Specification::new do |spec|
5
5
  spec.name = "open4"
6
- spec.version = "1.0.1"
6
+ spec.version = "1.3.4"
7
7
  spec.platform = Gem::Platform::RUBY
8
8
  spec.summary = "open4"
9
+ spec.description = "open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily."
10
+ spec.license = "Ruby"
9
11
 
10
- spec.description = "manage child processes and their io handles easily"
12
+ spec.files =
13
+ ["LICENSE",
14
+ "README",
15
+ "README.erb",
16
+ "lib",
17
+ "lib/open4.rb",
18
+ "open4.gemspec",
19
+ "rakefile",
20
+ "samples",
21
+ "samples/bg.rb",
22
+ "samples/block.rb",
23
+ "samples/exception.rb",
24
+ "samples/jesse-caldwell.rb",
25
+ "samples/pfork4.rb",
26
+ "samples/simple.rb",
27
+ "samples/spawn.rb",
28
+ "samples/stdin_timeout.rb",
29
+ "samples/timeout.rb",
30
+ "test",
31
+ "test/lib",
32
+ "test/lib/test_case.rb",
33
+ "test/pfork4_test.rb",
34
+ "test/popen4_test.rb",
35
+ "test/popen4ext_test.rb",
36
+ "white_box",
37
+ "white_box/leak.rb"]
11
38
 
12
- spec.files = ["lib", "lib/open4.rb", "open4.gemspec", "Rakefile", "README", "README.erb", "samples", "samples/bg.rb", "samples/block.rb", "samples/exception.rb", "samples/simple.rb", "samples/spawn.rb", "samples/stdin_timeout.rb", "samples/timeout.rb", "white_box", "white_box/leak.rb"]
13
39
  spec.executables = []
14
40
 
15
41
  spec.require_path = "lib"
16
42
 
17
- spec.has_rdoc = true
18
43
  spec.test_files = nil
19
- #spec.add_dependency 'lib', '>= version'
20
- #spec.add_dependency 'fattr'
44
+
45
+
21
46
 
22
47
  spec.extensions.push(*[])
23
48
 
24
49
  spec.rubyforge_project = "codeforpeople"
25
50
  spec.author = "Ara T. Howard"
26
51
  spec.email = "ara.t.howard@gmail.com"
27
- spec.homepage = "http://github.com/ahoward/open4/tree/master"
52
+ spec.homepage = "https://github.com/ahoward/open4"
28
53
  end