subprocess 1.3.2 → 1.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 28480023adb6bb4e7bff7096e766e6be65ff9421
4
- data.tar.gz: 3ad723848b5a45e44fad49d967c0787d1d24f108
2
+ SHA256:
3
+ metadata.gz: b4d97562256aef790c8c0a24420d8b6509375cc023809aaa833310d2e3c46442
4
+ data.tar.gz: 9b91371b9e3e9a2af35431f69c4d3a795f02697063675378676bdb2f8cd7a262
5
5
  SHA512:
6
- metadata.gz: 566d18db86adb9950509f92d4efa608475c9c9d9ccc174e11c06ee2fa128eadc6c8fbc22ab49404abed394c930a0750a4de5eb489dcc8761a72c4fb3b6a043e0
7
- data.tar.gz: f9c2d1307de9dfcf067eeda605182456872dc16ef0948348db488fd0103d080ea296cba486b9b5448c712fb188cb23e1feacb4ce8f08c7647b1be766205a02c0
6
+ metadata.gz: 7c757567b28d5e36cffd5ea13b705012ab790b445d757a1744a76d3eef375bfa6bc48259e0f0e744a351ee8455a79264a74dd9b6bcd6275c7fdd864b5b2ec82f
7
+ data.tar.gz: fd3dd1fdbda8bcf00a2a96a7855610d2c9ff7fb2a6ec1d8f24751df80723f050444dbccd8bd93d3090210e4c7c6be033a008d46e7af550a0181fff0146a0329d
@@ -1,4 +1,3 @@
1
- require 'fileutils'
2
1
  require 'thread'
3
2
  require 'set'
4
3
 
@@ -31,7 +30,7 @@ module Subprocess
31
30
  #
32
31
  # @return [::Process::Status] The exit status of the process
33
32
  #
34
- # @see {Process#initialize}
33
+ # @see Process#initialize
35
34
  def self.call(cmd, opts={}, &blk)
36
35
  Process.new(cmd, opts, &blk).wait
37
36
  end
@@ -63,7 +62,7 @@ module Subprocess
63
62
  # was terminated with an error or was killed by a signal)
64
63
  # @return [::Process::Status] The exit status of the process
65
64
  #
66
- # @see {Process#initialize}
65
+ # @see Process#initialize
67
66
  def self.check_call(cmd, opts={}, &blk)
68
67
  status = Process.new(cmd, opts, &blk).wait
69
68
  raise NonZeroExit.new(cmd, status) unless status.success?
@@ -80,7 +79,7 @@ module Subprocess
80
79
  # was terminated with an error or was killed by a signal)
81
80
  # @return [String] The contents of `stdout`
82
81
  #
83
- # @see {Process#initialize}
82
+ # @see Process#initialize
84
83
  def self.check_output(cmd, opts={}, &blk)
85
84
  opts[:stdout] = PIPE
86
85
  child = Process.new(cmd, opts, &blk)
@@ -162,6 +161,22 @@ module Subprocess
162
161
  end
163
162
  end
164
163
 
164
+ # Error class representing a timeout during a call to `communicate`
165
+ class CommunicateTimeout < StandardError
166
+ # @!attribute [r] stdout
167
+ # @return [String] Content read from stdout before the timeout
168
+ # @!attribute [r] stderr
169
+ # @return [String] Content read from stderr before the timeout
170
+ attr_reader :stdout, :stderr
171
+
172
+ def initialize(cmd, stdout, stderr)
173
+ @stdout = stdout
174
+ @stderr = stderr
175
+
176
+ super("Timeout communicating with `#{cmd.join(' ')}`")
177
+ end
178
+ end
179
+
165
180
  # A child process. The preferred way of spawning a subprocess is through the
166
181
  # functions on {Subprocess} (especially {Subprocess::check_call} and
167
182
  # {Subprocess::check_output}).
@@ -187,7 +202,7 @@ module Subprocess
187
202
  #
188
203
  # @param [Array<String>] cmd The command to run and its arguments (in the
189
204
  # style of an `argv` array). Unlike Python's subprocess module, `cmd`
190
- # cannnot be a String.
205
+ # cannot be a String.
191
206
  #
192
207
  # @option opts [IO, Fixnum, String, Subprocess::PIPE, nil] :stdin The `IO`,
193
208
  # file descriptor number, or file name to use for the process's standard
@@ -224,7 +239,7 @@ module Subprocess
224
239
  # in conjunction with {Subprocess::check_call}.
225
240
  # @yieldparam process [Process] The process that was just spawned.
226
241
  def initialize(cmd, opts={}, &blk)
227
- raise ArgumentError, "cmd must be an Array" unless Array === cmd
242
+ raise ArgumentError, "cmd must be an Array of strings" unless Array === cmd
228
243
  raise ArgumentError, "cmd cannot be empty" if cmd.empty?
229
244
 
230
245
  @command = cmd
@@ -244,8 +259,6 @@ module Subprocess
244
259
 
245
260
  @pid = fork do
246
261
  begin
247
- FileUtils.cd(opts[:cwd]) if opts[:cwd]
248
-
249
262
  ::STDIN.reopen(@child_stdin) if @child_stdin
250
263
  ::STDOUT.reopen(@child_stdout) if @child_stdout
251
264
  if opts[:stderr] == STDOUT
@@ -257,24 +270,44 @@ module Subprocess
257
270
  # Set up a new environment if we're requested to do so.
258
271
  if opts[:env]
259
272
  ENV.clear
260
- ENV.update(opts[:env])
273
+ begin
274
+ ENV.update(opts[:env])
275
+ rescue TypeError => e
276
+ raise ArgumentError, "`env` option must be a hash where all keys and values are strings (#{e})"
277
+ end
261
278
  end
262
279
 
263
280
  # Call the user back, maybe?
264
- opts[:preexec_fn].call if opts[:preexec_fn]
281
+ if opts[:preexec_fn]
282
+ if opts[:cwd]
283
+ Dir.chdir(opts[:cwd], &opts[:preexec_fn])
284
+ else
285
+ opts[:preexec_fn].call
286
+ end
287
+ end
265
288
 
266
289
  options = {close_others: true}.merge(opts.fetch(:exec_opts, {}))
267
290
  if opts[:retain_fds]
268
291
  retained_fds.each { |fd| options[fd] = fd }
269
292
  end
293
+ if opts[:cwd]
294
+ # We use the chdir option to `exec` since wrapping the
295
+ # `exec` in a Dir.chdir block caused these sporadic errors on macOS:
296
+ # Too many open files - getcwd (Errno::EMFILE)
297
+ options[:chdir] = opts[:cwd]
298
+ end
270
299
 
271
- # Ruby's Kernel#exec will call an exec(3) variant if called with two
272
- # or more arguments, but when called with just a single argument will
273
- # spawn a subshell with that argument as the command. Since we always
274
- # want to call exec(3), we use the third exec form, which passes a
275
- # [cmdname, argv0] array as its first argument and never invokes a
276
- # subshell.
277
- exec([cmd[0], cmd[0]], *cmd[1..-1], options)
300
+ begin
301
+ # Ruby's Kernel#exec will call an exec(3) variant if called with two
302
+ # or more arguments, but when called with just a single argument will
303
+ # spawn a subshell with that argument as the command. Since we always
304
+ # want to call exec(3), we use the third exec form, which passes a
305
+ # [cmdname, argv0] array as its first argument and never invokes a
306
+ # subshell.
307
+ exec([cmd[0], cmd[0]], *cmd[1..-1], options)
308
+ rescue TypeError => e
309
+ raise ArgumentError, "cmd must be an Array of strings (#{e})"
310
+ end
278
311
 
279
312
  rescue Exception => e
280
313
  # Dump all errors up to the parent through the control socket
@@ -337,7 +370,7 @@ module Subprocess
337
370
  # condition (`EOFError` or `EPIPE`).
338
371
  def drain_fd(fd, buf=nil)
339
372
  loop do
340
- tmp = fd.read_nonblock(4096)
373
+ tmp = fd.read_nonblock(4096).force_encoding(fd.external_encoding)
341
374
  buf << tmp unless buf.nil?
342
375
  end
343
376
  rescue EOFError, Errno::EPIPE
@@ -348,43 +381,48 @@ module Subprocess
348
381
  false
349
382
  end
350
383
 
351
- # Write the (optional) input to the process's `stdin`. Also, read (and
352
- # buffer in memory) the contents of `stdout` and `stderr`. Do this all using
353
- # `IO::select`, so we don't deadlock due to full pipe buffers.
384
+ # Write the (optional) input to the process's `stdin` and read the contents of
385
+ # `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
386
+ # are read. Otherwise they are buffered in memory and returned when the process
387
+ # exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
388
+ # buffers.
354
389
  #
355
390
  # This is only really useful if you set some of `:stdin`, `:stdout`, and
356
391
  # `:stderr` to {Subprocess::PIPE}.
357
392
  #
358
393
  # @param [String] input A string to feed to the child's standard input.
359
- # @return [Array<String>] An array of two elements: the data read from the
394
+ # @param [Numeric] timeout_s Raise {Subprocess::CommunicateTimeout} if communicate
395
+ # does not finish after timeout_s seconds.
396
+ # @yieldparam [String] stdout Data read from stdout since the last yield
397
+ # @yieldparam [String] stderr Data read from stderr since the last yield
398
+ # @return [Array(String, String), nil] An array of two elements: the data read from the
360
399
  # child's standard output and standard error, respectively.
361
- def communicate(input=nil)
400
+ # Returns nil if a block is provided.
401
+ def communicate(input=nil, timeout_s=nil)
362
402
  raise ArgumentError if !input.nil? && @stdin.nil?
363
403
 
364
404
  stdout, stderr = "", ""
365
- stdout_encoding = @stdout.external_encoding if @stdout
366
- stderr_encoding = @stderr.external_encoding if @stderr
367
405
 
368
406
  input = input.dup unless input.nil?
369
407
 
370
408
  @stdin.close if (input.nil? || input.empty?) && !@stdin.nil?
371
409
 
372
- self_read, self_write = IO.pipe
373
- self.class.catching_sigchld(pid, self_write) do
374
- wait_r = [@stdout, @stderr, self_read].compact
410
+ timeout_at = Time.now + timeout_s if timeout_s
411
+
412
+ self.class.catching_sigchld(pid) do |global_read, self_read|
413
+ wait_r = [@stdout, @stderr, self_read, global_read].compact
375
414
  wait_w = [input && @stdin].compact
376
- loop do
377
- ready_r, ready_w = select(wait_r, wait_w)
378
-
379
- # If the child exits, we still have to be sure to read any data left
380
- # in the pipes. So we poll the child, drain all the pipes, and *then*
381
- # check @status.
382
- #
383
- # It's very important that we do not call poll between draining the
384
- # pipes and checking @status. If we did, we open a race condition
385
- # where the child writes to stdout and exits in that brief window,
386
- # causing us to lose that data.
387
- poll
415
+ done = false
416
+ while !done
417
+ # If the process has exited, we want to drain any remaining output before returning
418
+ if poll
419
+ ready_r = wait_r - [self_read, global_read]
420
+ ready_w = []
421
+ done = true
422
+ else
423
+ ready_r, ready_w = select_until(wait_r, wait_w, [], timeout_at)
424
+ raise CommunicateTimeout.new(@command, stdout, stderr) if ready_r.nil?
425
+ end
388
426
 
389
427
  if ready_r.include?(@stdout)
390
428
  if drain_fd(@stdout, stdout)
@@ -398,6 +436,13 @@ module Subprocess
398
436
  end
399
437
  end
400
438
 
439
+ if ready_r.include?(global_read)
440
+ if drain_fd(global_read)
441
+ raise "Unexpected internal error -- someone closed the global self-pipe!"
442
+ end
443
+ self.class.wakeup_sigchld
444
+ end
445
+
401
446
  if ready_r.include?(self_read)
402
447
  if drain_fd(self_read)
403
448
  raise "Unexpected internal error -- someone closed our self-pipe!"
@@ -405,10 +450,24 @@ module Subprocess
405
450
  end
406
451
 
407
452
  if ready_w.include?(@stdin)
453
+ written = 0
408
454
  begin
409
455
  written = @stdin.write_nonblock(input)
410
456
  rescue EOFError # Maybe I shouldn't catch this...
411
457
  rescue Errno::EINTR
458
+ rescue IO::WaitWritable
459
+ # On OS X, a pipe can raise EAGAIN even after select indicates
460
+ # that it is writable. Once the process consumes from the pipe,
461
+ # the next write should succeed and we should make forward progress.
462
+ # Until then, treat this as not writing any bytes and continue looping.
463
+ # For details see: https://github.com/stripe/subprocess/pull/22
464
+ nil
465
+ rescue Errno::EPIPE
466
+ # The other side of the pipe closed before we could
467
+ # write all of our input. This can happen if the
468
+ # process exits prematurely.
469
+ @stdin.close
470
+ wait_w.delete(@stdin)
412
471
  end
413
472
  input[0...written] = ''
414
473
  if input.empty?
@@ -417,19 +476,20 @@ module Subprocess
417
476
  end
418
477
  end
419
478
 
420
- break if @status
421
-
422
- # If there's nothing left to wait for, we're done!
423
- break if wait_r.length == 0 && wait_w.length == 0
479
+ if block_given? && !(stderr.empty? && stdout.empty?)
480
+ yield stdout, stderr
481
+ stdout, stderr = "", ""
482
+ end
424
483
  end
425
484
  end
426
485
 
427
486
  wait
428
487
 
429
- stdout.force_encoding(stdout_encoding) if stdout_encoding
430
- stderr.force_encoding(stderr_encoding) if stderr_encoding
431
-
432
- [stdout, stderr]
488
+ if block_given?
489
+ nil
490
+ else
491
+ [stdout, stderr]
492
+ end
433
493
  end
434
494
 
435
495
  # Does exactly what it says on the box.
@@ -482,19 +542,58 @@ module Subprocess
482
542
  end
483
543
  end
484
544
 
545
+ # Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
546
+ def select_until(read_array, write_array, err_array, timeout_at)
547
+ if !timeout_at
548
+ return IO.select(read_array, write_array, err_array)
549
+ end
550
+
551
+ remaining = (timeout_at - Time.now)
552
+ return nil if remaining <= 0
553
+
554
+ IO.select(read_array, write_array, err_array, remaining)
555
+ end
556
+
485
557
  @sigchld_mutex = Mutex.new
486
558
  @sigchld_fds = {}
487
559
  @sigchld_old_handler = nil
560
+ @sigchld_global_write = nil
561
+ @sigchld_global_read = nil
562
+ @sigchld_pipe_pid = nil
563
+
564
+ def self.handle_sigchld
565
+ # We'd like to just notify everything in `@sigchld_fds`, but
566
+ # ruby signal handlers are not executed atomically with respect
567
+ # to other Ruby threads, so reading it is racy. We can't grab
568
+ # `@sigchld_mutex`, because signal execution blocks the main
569
+ # thread, and so we'd deadlock if the main thread currently
570
+ # holds it.
571
+ #
572
+ # Instead, we keep a long-lived notify self-pipe that we select
573
+ # on inside `communicate`, and we task `communicate` with
574
+ # grabbing the lock and fanning out the wakeups.
575
+ begin
576
+ @sigchld_global_write.write_nonblock("\x00")
577
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
578
+ nil # ignore
579
+ end
580
+ end
488
581
 
489
582
  # Wake up everyone. We can't tell who we should wake up without `wait`ing,
490
583
  # and we want to let the process itself do that. In practice, we're not
491
584
  # likely to have that many in-flight subprocesses, so this is probably not a
492
585
  # big deal.
493
- def self.handle_sigchld
494
- @sigchld_fds.values.each do |fd|
495
- begin
496
- fd.write_nonblock("\x00")
497
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
586
+ def self.wakeup_sigchld
587
+ @sigchld_mutex.synchronize do
588
+ @sigchld_fds.values.each do |fd|
589
+ begin
590
+ fd.write_nonblock("\x00")
591
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
592
+ # If the pipe is full, the other end will be woken up
593
+ # regardless when it next reads, so it's fine to skip the
594
+ # write (the pipe is a wakeup channel, and doesn't contain
595
+ # meaningful data).
596
+ end
498
597
  end
499
598
  end
500
599
  end
@@ -503,6 +602,14 @@ module Subprocess
503
602
  @sigchld_mutex.synchronize do
504
603
  @sigchld_fds[pid] = fd
505
604
  if @sigchld_fds.length == 1
605
+ if @sigchld_global_write.nil? || @sigchld_pipe_pid != ::Process.pid
606
+ # Check the PID so that if we fork we will re-open the
607
+ # pipe. It's important that a fork parent and child don't
608
+ # share this pipe, because if they do they risk stealing
609
+ # each others' wakeups.
610
+ @sigchld_pipe_pid = ::Process.pid
611
+ @sigchld_global_read, @sigchld_global_write = IO.pipe
612
+ end
506
613
  @sigchld_old_handler = Signal.trap('SIGCHLD') {handle_sigchld}
507
614
  end
508
615
  end
@@ -517,11 +624,15 @@ module Subprocess
517
624
  end
518
625
  end
519
626
 
520
- def self.catching_sigchld(pid, fd)
521
- register_pid(pid, fd)
522
- yield
523
- ensure
524
- unregister_pid(pid)
627
+ def self.catching_sigchld(pid)
628
+ IO.pipe do |self_read, self_write|
629
+ begin
630
+ register_pid(pid, self_write)
631
+ yield @sigchld_global_read, self_read
632
+ ensure
633
+ unregister_pid(pid)
634
+ end
635
+ end
525
636
  end
526
637
  end
527
638
  end
@@ -1,3 +1,3 @@
1
1
  module Subprocess
2
- VERSION = '1.3.2'
2
+ VERSION = '1.5.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subprocess
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Jackson
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2016-08-17 00:00:00.000000000 Z
15
+ date: 2020-04-06 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: minitest
@@ -89,10 +89,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  - !ruby/object:Gem::Version
90
90
  version: '0'
91
91
  requirements: []
92
- rubyforge_project:
93
- rubygems_version: 2.2.2
92
+ rubygems_version: 3.1.2
94
93
  signing_key:
95
94
  specification_version: 4
96
95
  summary: A port of Python's subprocess module to Ruby
97
96
  test_files: []
98
- has_rdoc: