open4 1.0.1 → 1.3.4

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.
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