subprocess 1.3.0 → 1.5.5

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: 82c898802f4160693d7836102254e37c303db198
4
- data.tar.gz: 71481f1fe51f83f84be323b801674c6ae34c8723
2
+ SHA256:
3
+ metadata.gz: ca7ff75142ac9cdc919053f61071f8ff54d21193754239da85b810d0dce7886d
4
+ data.tar.gz: ae5ec4dedac5fd3042e6006ae798bf286c5705b6aca0da37c398fbd52b16d100
5
5
  SHA512:
6
- metadata.gz: f3b32e75da714a7eb24ab94fa5486ed190c128b5e1e92fa8545a9797c93ff74cdef92f45a8d3e54b8d6fd653284d85d5640f6c89b926d23f9cfdd01786bbbdf3
7
- data.tar.gz: d7d449f41f1d7c2a2201af471916bdac29696cea27058ada2454d91de7ac6543cb56715ae56393dc61b9e9c2c1a31c1fce01b7b76934d1f929909e23307324d1
6
+ metadata.gz: d0e375e45a5ed1089690a90abe9bf3fcc83754a4bd490047465ff0b83bd82966a5cb73fcd6738cb215c6351784582b90a341ba7ad7315738ef426f532215ed9b
7
+ data.tar.gz: 87b16802e5534b86c4f2d0ab7b7f5bd0cd885b9375af520341a936c017e5c23a8a33a76bcd7a50566cb9a2631e77fc8c9797779e78e03c0e75afe916d680efb1
data/README.md CHANGED
@@ -1,14 +1,8 @@
1
- # Subprocess [![Build Status](https://travis-ci.org/stripe/subprocess.svg?branch=master)](https://travis-ci.org/stripe/subprocess)
1
+ # Subprocess ![Ruby](https://github.com/stripe/subprocess/workflows/Ruby/badge.svg) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/stripe/subprocess/Subprocess)
2
2
 
3
3
  ![Jacques Cousteau Submarine](http://i.imgur.com/lmej24F.jpg)
4
4
 
5
- A port of Python's excellent subprocess module to Ruby.
6
-
7
- Many thanks to [Bram Swenson][bram], the author of the old [subprocess][old]
8
- gem, for graciously letting us use the name.
9
-
10
- [bram]: https://github.com/bramswenson
11
- [old]: https://github.com/bramswenson/subprocess
5
+ A solid subprocess library for ruby, inspired by python's.
12
6
 
13
7
  Installation
14
8
  ------------
@@ -21,20 +15,10 @@ You can also build `subprocess` from source by running:
21
15
 
22
16
  $ gem build subprocess.gemspec
23
17
 
24
-
25
18
  Usage
26
19
  -----
27
20
 
28
- Most of the documentation for Python's [subprocess][python] module applies
29
- equally well to this gem as well. While there are a few places when our
30
- semantics differs from Python's, users of the Python module should largely feel
31
- at home using `subprocess`. We have attempted to [document][rubydoc] all of the
32
- differences, but if we have missed something, please file an issue.
33
-
34
- [python]: http://docs.python.org/library/subprocess.html
35
- [rubydoc]: http://rubydoc.info/github/stripe/subprocess
36
-
37
- A few examples:
21
+ Full documentation is on [RubyDoc][rubydoc]. A few examples:
38
22
 
39
23
  ```ruby
40
24
  require 'subprocess'
@@ -71,3 +55,46 @@ http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
71
55
  EMAIL
72
56
  end
73
57
  ```
58
+
59
+ Most of the documentation for Python's [subprocess][python] module applies
60
+ equally well to this gem as well. While there are a few places when our
61
+ semantics differs from Python's, users of the Python module should largely feel
62
+ at home using `subprocess`. We have attempted to [document][rubydoc] all of the
63
+ differences, but if we have missed something, please file an issue.
64
+
65
+ [python]: http://docs.python.org/library/subprocess.html
66
+ [rubydoc]: http://rubydoc.info/github/stripe/subprocess/Subprocess
67
+
68
+ Maintenance
69
+ -----------
70
+
71
+ Steps to release a new version:
72
+
73
+ ```bash
74
+ # Work directly on master
75
+ git checkout master
76
+
77
+ # -- edit version in lib/subprocess/version.rb --
78
+
79
+ # Update RBI files
80
+ bundle exec rake sord
81
+
82
+ # Subsequent commands reference the version
83
+ VERSION=1.5.5
84
+ git commit -am "Bump version to $VERSION"
85
+ git tag "v$VERSION"
86
+ git push origin master --tags
87
+ bundle exec rake publish
88
+ ```
89
+
90
+ If you get errors, ask someone to add you to `bindings/rubygems-api-key` in
91
+ Vault or ask someone who already has permissions. See <http://go/password-vault>
92
+
93
+ Acknowledgements
94
+ ----------------
95
+
96
+ Many thanks to [Bram Swenson][bram], the author of the old [subprocess][old]
97
+ gem, for graciously letting us use the name.
98
+
99
+ [bram]: https://github.com/bramswenson
100
+ [old]: https://github.com/bramswenson/subprocess
data/lib/subprocess.rb CHANGED
@@ -16,7 +16,13 @@ module Subprocess
16
16
 
17
17
  # An alias for `Process.new`. Mostly here to better emulate the Python API.
18
18
  #
19
+ # @param [Array<String>] cmd See {Process#initialize}
20
+ # @param [Hash] opts See {Process#initialize}
21
+ # @yield [process] See {Process#initialize}
22
+ # @yieldparam process [Process] See {Process#initialize}
19
23
  # @return [Process] A process with the given arguments
24
+ #
25
+ # @see Process#initialize
20
26
  def self.popen(cmd, opts={}, &blk)
21
27
  Process.new(cmd, opts, &blk)
22
28
  end
@@ -28,9 +34,14 @@ module Subprocess
28
34
  # fills up, as neither file descriptor will be read from. To avoid this, use
29
35
  # {Process#communicate} from a passed block.
30
36
  #
37
+ # @param [Array<String>] cmd See {Process#initialize}
38
+ # @param [Hash] opts See {Process#initialize}
39
+ # @yield [process] See {Process#initialize}
40
+ # @yieldparam process [Process] See {Process#initialize}
41
+ #
31
42
  # @return [::Process::Status] The exit status of the process
32
43
  #
33
- # @see {Process#initialize}
44
+ # @see Process#initialize
34
45
  def self.call(cmd, opts={}, &blk)
35
46
  Process.new(cmd, opts, &blk).wait
36
47
  end
@@ -58,11 +69,16 @@ module Subprocess
58
69
  # fills up, as neither file descriptor will be read from. To avoid this, use
59
70
  # {Process#communicate} from a passed block.
60
71
  #
72
+ # @param [Array<String>] cmd See {Process#initialize}
73
+ # @param [Hash] opts See {Process#initialize}
74
+ # @yield [process] See {Process#initialize}
75
+ # @yieldparam process [Process] See {Process#initialize}
76
+ #
61
77
  # @raise [NonZeroExit] if the process returned a non-zero exit status (i.e.,
62
78
  # was terminated with an error or was killed by a signal)
63
79
  # @return [::Process::Status] The exit status of the process
64
80
  #
65
- # @see {Process#initialize}
81
+ # @see Process#initialize
66
82
  def self.check_call(cmd, opts={}, &blk)
67
83
  status = Process.new(cmd, opts, &blk).wait
68
84
  raise NonZeroExit.new(cmd, status) unless status.success?
@@ -70,16 +86,21 @@ module Subprocess
70
86
  end
71
87
 
72
88
  # Like {Subprocess::check_call}, but return the contents of `stdout`, much
73
- # like `Kernel#system`.
89
+ # like {::Kernel#system}.
74
90
  #
75
91
  # @example Get the system load
76
92
  # system_load = Subprocess.check_output(['uptime']).split(' ').last(3)
77
93
  #
94
+ # @param [Array<String>] cmd See {Process#initialize}
95
+ # @param [Hash] opts See {Process#initialize}
96
+ # @yield [process] See {Process#initialize}
97
+ # @yieldparam process [Process] See {Process#initialize}
98
+ #
78
99
  # @raise [NonZeroExit] if the process returned a non-zero exit status (i.e.,
79
100
  # was terminated with an error or was killed by a signal)
80
101
  # @return [String] The contents of `stdout`
81
102
  #
82
- # @see {Process#initialize}
103
+ # @see Process#initialize
83
104
  def self.check_output(cmd, opts={}, &blk)
84
105
  opts[:stdout] = PIPE
85
106
  child = Process.new(cmd, opts, &blk)
@@ -107,14 +128,7 @@ module Subprocess
107
128
  # been according to the usual exit status convention
108
129
  sig_num = status.exitstatus - 128
109
130
 
110
- # sigh, why is ruby so silly
111
- if Signal.respond_to?(:signame)
112
- # ruby 2.0 way
113
- sig_name = Signal.signame(sig_num)
114
- else
115
- # ruby 1.9 way
116
- sig_name = Signal.list.key(sig_num)
117
- end
131
+ sig_name = Signal.signame(sig_num)
118
132
 
119
133
  if sig_name
120
134
  parts << "(maybe SIG#{sig_name})"
@@ -137,16 +151,17 @@ module Subprocess
137
151
 
138
152
  # Error class representing a process's abnormal exit.
139
153
  class NonZeroExit < StandardError
140
- # @!attribute [r] command
141
- # @note This is intended only for use in user-facing error messages. In
142
- # particular, no shell quoting of any sort is performed when
143
- # constructing this string, meaning that blindly running it in a shell
144
- # might have different semantics than the original command.
145
- # @return [String] The command and arguments for the process that exited
146
- # abnormally.
147
- # @!attribute [r] status
148
- # @return [::Process::Status] The Ruby status object returned by `waitpid`
149
- attr_reader :command, :status
154
+ # @note This is intended only for use in user-facing error messages. In
155
+ # particular, no shell quoting of any sort is performed when
156
+ # constructing this string, meaning that blindly running it in a shell
157
+ # might have different semantics than the original command.
158
+ #
159
+ # @return [String] The command and arguments for the process that exited
160
+ # abnormally.
161
+ attr_reader :command
162
+
163
+ # @return [::Process::Status] The Ruby status object returned by `waitpid`
164
+ attr_reader :status
150
165
 
151
166
  # Return an instance of {NonZeroExit}.
152
167
  #
@@ -168,42 +183,63 @@ module Subprocess
168
183
  end
169
184
  end
170
185
 
186
+ # Error class representing a timeout during a call to `communicate`
187
+ class CommunicateTimeout < StandardError
188
+ # @return [String] Content read from stdout before the timeout
189
+ attr_reader :stdout
190
+
191
+ # @return [String] Content read from stderr before the timeout
192
+ attr_reader :stderr
193
+
194
+ # @param [Array<String>] cmd
195
+ # @param [String] stdout
196
+ # @param [String] stderr
197
+ def initialize(cmd, stdout, stderr)
198
+ @stdout = stdout
199
+ @stderr = stderr
200
+
201
+ super("Timeout communicating with `#{cmd.join(' ')}`")
202
+ end
203
+ end
204
+
171
205
  # A child process. The preferred way of spawning a subprocess is through the
172
206
  # functions on {Subprocess} (especially {Subprocess::check_call} and
173
207
  # {Subprocess::check_output}).
174
208
  class Process
175
- # @!attribute [r] stdin
176
- # @return [IO] The `IO` that is connected to this process's `stdin`.
177
- # @!attribute [r] stdout
178
- # @return [IO] The `IO` that is connected to this process's `stdout`.
179
- # @!attribute [r] stderr
180
- # @return [IO] The `IO` that is connected to this process's `stderr`.
181
- attr_reader :stdin, :stdout, :stderr
182
-
183
- # @!attribute [r] command
184
- # @return [Array<String>] The command this process was invoked with.
185
- # @!attribute [r] pid
186
- # @return [Fixnum] The process ID of the spawned process.
187
- # @!attribute [r] status
188
- # @return [::Process::Status] The exit status code of the process. Only
189
- # set after the process has exited.
190
- attr_reader :command, :pid, :status
209
+ # @return [IO] The `IO` that is connected to this process's `stdin`.
210
+ attr_reader :stdin
211
+
212
+ # @return [IO] The `IO` that is connected to this process's `stdout`.
213
+ attr_reader :stdout
214
+
215
+ # @return [IO] The `IO` that is connected to this process's `stderr`.
216
+ attr_reader :stderr
217
+
218
+ # @return [Array<String>] The command this process was invoked with.
219
+ attr_reader :command
220
+
221
+ # @return [Integer] The process ID of the spawned process.
222
+ attr_reader :pid
223
+
224
+ # @return [::Process::Status, nil] The exit status code of the process.
225
+ # Only set after the process has exited.
226
+ attr_reader :status
191
227
 
192
228
  # Create a new process.
193
229
  #
194
230
  # @param [Array<String>] cmd The command to run and its arguments (in the
195
231
  # style of an `argv` array). Unlike Python's subprocess module, `cmd`
196
- # cannnot be a String.
232
+ # cannot be a String.
197
233
  #
198
- # @option opts [IO, Fixnum, String, Subprocess::PIPE, nil] :stdin The `IO`,
234
+ # @option opts [IO, Integer, String, Subprocess::PIPE, nil] :stdin The `IO`,
199
235
  # file descriptor number, or file name to use for the process's standard
200
236
  # input. If the magic value {Subprocess::PIPE} is passed, a new pipe will
201
237
  # be opened.
202
- # @option opts [IO, Fixnum, String, Subprocess::PIPE, nil] :stdout The `IO`,
238
+ # @option opts [IO, Integer, String, Subprocess::PIPE, nil] :stdout The `IO`,
203
239
  # file descriptor number, or file name to use for the process's standard
204
240
  # output. If the magic value {Subprocess::PIPE} is passed, a pipe will be
205
241
  # opened and attached to the process.
206
- # @option opts [IO, Fixnum, String, Subprocess::PIPE, Subprocess::STDOUT,
242
+ # @option opts [IO, Integer, String, Subprocess::PIPE, Subprocess::STDOUT,
207
243
  # nil] :stderr The `IO`, file descriptor number, or file name to use for
208
244
  # the process's standard error. If the special value {Subprocess::PIPE} is
209
245
  # passed, a pipe will be opened and attached to the process. If the
@@ -214,12 +250,12 @@ module Subprocess
214
250
  # child process.
215
251
  # @option opts [Hash<String, String>] :env The environment to use in the
216
252
  # child process.
217
- # @option opts [Array<Fixnum>] :retain_fds An array of file descriptor
253
+ # @option opts [Array<Integer>] :retain_fds An array of file descriptor
218
254
  # numbers that should not be closed before executing the child process.
219
255
  # Note that, unlike Python (which has :close_fds defaulting to false), all
220
256
  # file descriptors not specified here will be closed.
221
257
  # @option opts [Hash] :exec_opts A hash that will be merged into the options
222
- # hash of the call to {Kernel#exec}.
258
+ # hash of the call to {::Kernel#exec}.
223
259
  #
224
260
  # @option opts [Proc] :preexec_fn A function that will be called in the
225
261
  # child process immediately before executing `cmd`.
@@ -230,7 +266,7 @@ module Subprocess
230
266
  # in conjunction with {Subprocess::check_call}.
231
267
  # @yieldparam process [Process] The process that was just spawned.
232
268
  def initialize(cmd, opts={}, &blk)
233
- raise ArgumentError, "cmd must be an Array" unless Array === cmd
269
+ raise ArgumentError, "cmd must be an Array of strings" unless Array === cmd
234
270
  raise ArgumentError, "cmd cannot be empty" if cmd.empty?
235
271
 
236
272
  @command = cmd
@@ -250,8 +286,6 @@ module Subprocess
250
286
 
251
287
  @pid = fork do
252
288
  begin
253
- FileUtils.cd(opts[:cwd]) if opts[:cwd]
254
-
255
289
  ::STDIN.reopen(@child_stdin) if @child_stdin
256
290
  ::STDOUT.reopen(@child_stdout) if @child_stdout
257
291
  if opts[:stderr] == STDOUT
@@ -263,24 +297,44 @@ module Subprocess
263
297
  # Set up a new environment if we're requested to do so.
264
298
  if opts[:env]
265
299
  ENV.clear
266
- ENV.update(opts[:env])
300
+ begin
301
+ ENV.update(opts[:env])
302
+ rescue TypeError => e
303
+ raise ArgumentError, "`env` option must be a hash where all keys and values are strings (#{e})"
304
+ end
267
305
  end
268
306
 
269
307
  # Call the user back, maybe?
270
- opts[:preexec_fn].call if opts[:preexec_fn]
308
+ if opts[:preexec_fn]
309
+ if opts[:cwd]
310
+ Dir.chdir(opts[:cwd], &opts[:preexec_fn])
311
+ else
312
+ opts[:preexec_fn].call
313
+ end
314
+ end
271
315
 
272
316
  options = {close_others: true}.merge(opts.fetch(:exec_opts, {}))
273
317
  if opts[:retain_fds]
274
318
  retained_fds.each { |fd| options[fd] = fd }
275
319
  end
320
+ if opts[:cwd]
321
+ # We use the chdir option to `exec` since wrapping the
322
+ # `exec` in a Dir.chdir block caused these sporadic errors on macOS:
323
+ # Too many open files - getcwd (Errno::EMFILE)
324
+ options[:chdir] = opts[:cwd]
325
+ end
276
326
 
277
- # Ruby's Kernel#exec will call an exec(3) variant if called with two
278
- # or more arguments, but when called with just a single argument will
279
- # spawn a subshell with that argument as the command. Since we always
280
- # want to call exec(3), we use the third exec form, which passes a
281
- # [cmdname, argv0] array as its first argument and never invokes a
282
- # subshell.
283
- exec([cmd[0], cmd[0]], *cmd[1..-1], options)
327
+ begin
328
+ # Ruby's Kernel#exec will call an exec(3) variant if called with two
329
+ # or more arguments, but when called with just a single argument will
330
+ # spawn a subshell with that argument as the command. Since we always
331
+ # want to call exec(3), we use the third exec form, which passes a
332
+ # [cmdname, argv0] array as its first argument and never invokes a
333
+ # subshell.
334
+ exec([cmd[0], cmd[0]], *cmd[1..-1], options)
335
+ rescue TypeError => e
336
+ raise ArgumentError, "cmd must be an Array of strings (#{e})"
337
+ end
284
338
 
285
339
  rescue Exception => e
286
340
  # Dump all errors up to the parent through the control socket
@@ -343,7 +397,7 @@ module Subprocess
343
397
  # condition (`EOFError` or `EPIPE`).
344
398
  def drain_fd(fd, buf=nil)
345
399
  loop do
346
- tmp = fd.read_nonblock(4096)
400
+ tmp = fd.read_nonblock(4096).force_encoding(fd.external_encoding)
347
401
  buf << tmp unless buf.nil?
348
402
  end
349
403
  rescue EOFError, Errno::EPIPE
@@ -354,40 +408,51 @@ module Subprocess
354
408
  false
355
409
  end
356
410
 
357
- # Write the (optional) input to the process's `stdin`. Also, read (and
358
- # buffer in memory) the contents of `stdout` and `stderr`. Do this all using
359
- # `IO::select`, so we don't deadlock due to full pipe buffers.
411
+ # Write the (optional) input to the process's `stdin` and read the contents of
412
+ # `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
413
+ # are read. Otherwise they are buffered in memory and returned when the process
414
+ # exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
415
+ # buffers.
360
416
  #
361
417
  # This is only really useful if you set some of `:stdin`, `:stdout`, and
362
418
  # `:stderr` to {Subprocess::PIPE}.
363
419
  #
364
420
  # @param [String] input A string to feed to the child's standard input.
365
- # @return [Array<String>] An array of two elements: the data read from the
421
+ # @param [Numeric] timeout_s Raise {Subprocess::CommunicateTimeout} if communicate
422
+ # does not finish after timeout_s seconds.
423
+ # @yieldparam [String] stdout Data read from stdout since the last yield
424
+ # @yieldparam [String] stderr Data read from stderr since the last yield
425
+ # @return [Array(String, String), nil] An array of two elements: the data read from the
366
426
  # child's standard output and standard error, respectively.
367
- def communicate(input=nil)
427
+ # Returns nil if a block is provided.
428
+ def communicate(input=nil, timeout_s=nil)
368
429
  raise ArgumentError if !input.nil? && @stdin.nil?
369
430
 
370
431
  stdout, stderr = "", ""
371
- input = input.dup unless input.nil?
432
+
433
+ # NB: Always force encoding to binary so we handle unicode or binary input
434
+ # correctly across multiple write_nonblock calls, since we manually slice
435
+ # the input depending on how many bytes were written
436
+ input = input.dup.force_encoding('BINARY') unless input.nil?
372
437
 
373
438
  @stdin.close if (input.nil? || input.empty?) && !@stdin.nil?
374
439
 
375
- self_read, self_write = IO.pipe
376
- self.class.catching_sigchld(pid, self_write) do
377
- wait_r = [@stdout, @stderr, self_read].compact
440
+ timeout_at = Time.now + timeout_s if timeout_s
441
+
442
+ self.class.catching_sigchld(pid) do |global_read, self_read|
443
+ wait_r = [@stdout, @stderr, self_read, global_read].compact
378
444
  wait_w = [input && @stdin].compact
379
- loop do
380
- ready_r, ready_w = select(wait_r, wait_w)
381
-
382
- # If the child exits, we still have to be sure to read any data left
383
- # in the pipes. So we poll the child, drain all the pipes, and *then*
384
- # check @status.
385
- #
386
- # It's very important that we do not call poll between draining the
387
- # pipes and checking @status. If we did, we open a race condition
388
- # where the child writes to stdout and exits in that brief window,
389
- # causing us to lose that data.
390
- poll
445
+ done = false
446
+ while !done
447
+ # If the process has exited, we want to drain any remaining output before returning
448
+ if poll
449
+ ready_r = wait_r - [self_read, global_read]
450
+ ready_w = []
451
+ done = true
452
+ else
453
+ ready_r, ready_w = select_until(wait_r, wait_w, [], timeout_at)
454
+ raise CommunicateTimeout.new(@command, stdout, stderr) if ready_r.nil?
455
+ end
391
456
 
392
457
  if ready_r.include?(@stdout)
393
458
  if drain_fd(@stdout, stdout)
@@ -401,6 +466,13 @@ module Subprocess
401
466
  end
402
467
  end
403
468
 
469
+ if ready_r.include?(global_read)
470
+ if drain_fd(global_read)
471
+ raise "Unexpected internal error -- someone closed the global self-pipe!"
472
+ end
473
+ self.class.wakeup_sigchld
474
+ end
475
+
404
476
  if ready_r.include?(self_read)
405
477
  if drain_fd(self_read)
406
478
  raise "Unexpected internal error -- someone closed our self-pipe!"
@@ -408,10 +480,24 @@ module Subprocess
408
480
  end
409
481
 
410
482
  if ready_w.include?(@stdin)
483
+ written = 0
411
484
  begin
412
485
  written = @stdin.write_nonblock(input)
413
486
  rescue EOFError # Maybe I shouldn't catch this...
414
487
  rescue Errno::EINTR
488
+ rescue IO::WaitWritable
489
+ # On OS X, a pipe can raise EAGAIN even after select indicates
490
+ # that it is writable. Once the process consumes from the pipe,
491
+ # the next write should succeed and we should make forward progress.
492
+ # Until then, treat this as not writing any bytes and continue looping.
493
+ # For details see: https://github.com/stripe/subprocess/pull/22
494
+ nil
495
+ rescue Errno::EPIPE
496
+ # The other side of the pipe closed before we could
497
+ # write all of our input. This can happen if the
498
+ # process exits prematurely.
499
+ @stdin.close
500
+ wait_w.delete(@stdin)
415
501
  end
416
502
  input[0...written] = ''
417
503
  if input.empty?
@@ -420,29 +506,41 @@ module Subprocess
420
506
  end
421
507
  end
422
508
 
423
- break if @status
424
-
425
- # If there's nothing left to wait for, we're done!
426
- break if wait_r.length == 0 && wait_w.length == 0
509
+ if block_given? && !(stderr.empty? && stdout.empty?)
510
+ yield stdout, stderr
511
+ stdout, stderr = "", ""
512
+ end
427
513
  end
428
514
  end
429
515
 
430
516
  wait
431
517
 
432
- [stdout, stderr]
518
+ if block_given?
519
+ nil
520
+ else
521
+ [stdout, stderr]
522
+ end
433
523
  end
434
524
 
435
525
  # Does exactly what it says on the box.
436
526
  #
437
- # @param [String, Symbol, Fixnum] signal The signal to send to the child
527
+ # @param [String, Symbol, Integer] signal The signal to send to the child
438
528
  # process. Accepts all the same arguments as Ruby's built-in
439
529
  # {::Process::kill}, for instance a string like "INT" or "SIGINT", or a
440
530
  # signal number like 2.
531
+ #
532
+ # @return [Integer] See {::Process.kill}
533
+ #
534
+ # @see ::Process.kill
441
535
  def send_signal(signal)
442
536
  ::Process.kill(signal, pid)
443
537
  end
444
538
 
445
539
  # Sends `SIGTERM` to the process.
540
+ #
541
+ # @return [Integer] See {send_signal}
542
+ #
543
+ # @see send_signal
446
544
  def terminate
447
545
  send_signal("TERM")
448
546
  end
@@ -452,6 +550,10 @@ module Subprocess
452
550
  # descriptor should appear to the child and to this process, respectively.
453
551
  # "mine" is only non-nil in the case of a pipe (in fact, we just return a
454
552
  # list of length one, since ruby will unpack nils from missing list items).
553
+ #
554
+ # @param [IO, Integer, String, nil] fd
555
+ # @param [String] mode
556
+ # @return [Array<IO>]
455
557
  def parse_fd(fd, mode)
456
558
  fds = case fd
457
559
  when PIPE
@@ -473,6 +575,9 @@ module Subprocess
473
575
 
474
576
  # The pair to parse_fd, returns whether or not the file descriptor was
475
577
  # opened by us (and therefore should be closed by us).
578
+ #
579
+ # @param [IO, Integer, String, nil] fd
580
+ # @return [Boolean]
476
581
  def our_fd?(fd)
477
582
  case fd
478
583
  when PIPE, String
@@ -482,32 +587,92 @@ module Subprocess
482
587
  end
483
588
  end
484
589
 
590
+ # Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
591
+ #
592
+ # @param [Array<IO>, nil] read_array
593
+ # @param [Array<IO>, nil] write_array
594
+ # @param [Array<IO>, nil] err_array
595
+ # @param [Integer, Float, nil] timeout_at
596
+ # @return [Array<Array<IO>>, nil]
597
+ def select_until(read_array, write_array, err_array, timeout_at)
598
+ if !timeout_at
599
+ return IO.select(read_array, write_array, err_array)
600
+ end
601
+
602
+ remaining = (timeout_at - Time.now)
603
+ return nil if remaining <= 0
604
+
605
+ IO.select(read_array, write_array, err_array, remaining)
606
+ end
607
+
485
608
  @sigchld_mutex = Mutex.new
486
609
  @sigchld_fds = {}
487
610
  @sigchld_old_handler = nil
611
+ @sigchld_global_write = nil
612
+ @sigchld_global_read = nil
613
+ @sigchld_pipe_pid = nil
614
+
615
+ # @return [void]
616
+ def self.handle_sigchld
617
+ # We'd like to just notify everything in `@sigchld_fds`, but
618
+ # ruby signal handlers are not executed atomically with respect
619
+ # to other Ruby threads, so reading it is racy. We can't grab
620
+ # `@sigchld_mutex`, because signal execution blocks the main
621
+ # thread, and so we'd deadlock if the main thread currently
622
+ # holds it.
623
+ #
624
+ # Instead, we keep a long-lived notify self-pipe that we select
625
+ # on inside `communicate`, and we task `communicate` with
626
+ # grabbing the lock and fanning out the wakeups.
627
+ begin
628
+ @sigchld_global_write.write_nonblock("\x00")
629
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
630
+ nil # ignore
631
+ end
632
+ end
488
633
 
489
634
  # Wake up everyone. We can't tell who we should wake up without `wait`ing,
490
635
  # and we want to let the process itself do that. In practice, we're not
491
636
  # likely to have that many in-flight subprocesses, so this is probably not a
492
637
  # 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
638
+ # @return [void]
639
+ def self.wakeup_sigchld
640
+ @sigchld_mutex.synchronize do
641
+ @sigchld_fds.values.each do |fd|
642
+ begin
643
+ fd.write_nonblock("\x00")
644
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
645
+ # If the pipe is full, the other end will be woken up
646
+ # regardless when it next reads, so it's fine to skip the
647
+ # write (the pipe is a wakeup channel, and doesn't contain
648
+ # meaningful data).
649
+ end
498
650
  end
499
651
  end
500
652
  end
501
653
 
654
+ # @param [Integer] pid
655
+ # @param [IO] fd
656
+ # @return [void]
502
657
  def self.register_pid(pid, fd)
503
658
  @sigchld_mutex.synchronize do
504
659
  @sigchld_fds[pid] = fd
505
660
  if @sigchld_fds.length == 1
661
+ if @sigchld_global_write.nil? || @sigchld_pipe_pid != ::Process.pid
662
+ # Check the PID so that if we fork we will re-open the
663
+ # pipe. It's important that a fork parent and child don't
664
+ # share this pipe, because if they do they risk stealing
665
+ # each others' wakeups.
666
+ @sigchld_pipe_pid = ::Process.pid
667
+ @sigchld_global_read, @sigchld_global_write = IO.pipe
668
+ end
506
669
  @sigchld_old_handler = Signal.trap('SIGCHLD') {handle_sigchld}
507
670
  end
508
671
  end
509
672
  end
510
673
 
674
+ # @param [Integer] pid
675
+ # @return [void]
511
676
  def self.unregister_pid(pid)
512
677
  @sigchld_mutex.synchronize do
513
678
  if @sigchld_fds.length == 1
@@ -517,11 +682,17 @@ module Subprocess
517
682
  end
518
683
  end
519
684
 
520
- def self.catching_sigchld(pid, fd)
521
- register_pid(pid, fd)
522
- yield
523
- ensure
524
- unregister_pid(pid)
685
+ # @param [Integer] pid
686
+ # @return [void]
687
+ def self.catching_sigchld(pid)
688
+ IO.pipe do |self_read, self_write|
689
+ begin
690
+ register_pid(pid, self_write)
691
+ yield @sigchld_global_read, self_read
692
+ ensure
693
+ unregister_pid(pid)
694
+ end
695
+ end
525
696
  end
526
697
  end
527
698
  end
@@ -1,3 +1,3 @@
1
1
  module Subprocess
2
- VERSION = '1.3.0'
2
+ VERSION = '1.5.5'
3
3
  end
@@ -0,0 +1,312 @@
1
+ # typed: strong
2
+
3
+ # THIS FILE IS AUTOGENERATED. DO NOT EDIT.
4
+ # To regenerate from YARD comments:
5
+ #
6
+ # bundle exec rake sord
7
+ #
8
+ # A Ruby clone of Python's subprocess module.
9
+ #
10
+ # @see http://docs.python.org/2/library/subprocess.html
11
+ module ::Subprocess
12
+ PIPE = T.let(-1, T.untyped)
13
+ STDOUT = T.let(-2, T.untyped)
14
+ VERSION = T.let('1.5.5', T.untyped)
15
+
16
+ # An alias for `Process.new`. Mostly here to better emulate the Python API.
17
+ #
18
+ # _@param_ `cmd` — See {Process#initialize}
19
+ #
20
+ # _@param_ `opts` — See {Process#initialize}
21
+ #
22
+ # _@return_ — A process with the given arguments
23
+ #
24
+ # _@see_ `Process#initialize`
25
+ sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(Process) }
26
+ def self.popen(cmd, opts = {}, &blk); end
27
+
28
+ # Call and wait for the return of a given process.
29
+ #
30
+ # _@param_ `cmd` — See {Process#initialize}
31
+ #
32
+ # _@param_ `opts` — See {Process#initialize}
33
+ #
34
+ # _@return_ — The exit status of the process
35
+ #
36
+ # _@note_ — If you call this function with `:stdout => PIPE` or `:stderr => PIPE`,
37
+ # this function will block indefinitely as soon as the OS's pipe buffer
38
+ # fills up, as neither file descriptor will be read from. To avoid this, use
39
+ # {Process#communicate} from a passed block.
40
+ #
41
+ # _@see_ `Process#initialize`
42
+ sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(::Process::Status) }
43
+ def self.call(cmd, opts = {}, &blk); end
44
+
45
+ # Like {Subprocess::call}, except raise a {NonZeroExit} if the process did not
46
+ # terminate successfully.
47
+ #
48
+ # _@param_ `cmd` — See {Process#initialize}
49
+ #
50
+ # _@param_ `opts` — See {Process#initialize}
51
+ #
52
+ # _@return_ — The exit status of the process
53
+ #
54
+ # Grep a file for a string
55
+ # ```ruby
56
+ # Subprocess.check_call(%W{grep -q llama ~/favorite_animals})
57
+ # ```
58
+ #
59
+ # Communicate with a child process
60
+ # ```ruby
61
+ # Subprocess.check_call(%W{sendmail -t}, :stdin => Subprocess::PIPE) do |p|
62
+ # p.communicate <<-EMAIL
63
+ # From: alpaca@example.com
64
+ # To: llama@example.com
65
+ # Subject: I am so fluffy.
66
+ #
67
+ # SO FLUFFY!
68
+ # http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
69
+ # EMAIL
70
+ # end
71
+ # ```
72
+ #
73
+ # _@note_ — If you call this function with `:stdout => PIPE` or `:stderr => PIPE`,
74
+ # this function will block indefinitely as soon as the OS's pipe buffer
75
+ # fills up, as neither file descriptor will be read from. To avoid this, use
76
+ # {Process#communicate} from a passed block.
77
+ #
78
+ # _@see_ `Process#initialize`
79
+ sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(::Process::Status) }
80
+ def self.check_call(cmd, opts = {}, &blk); end
81
+
82
+ # Like {Subprocess::check_call}, but return the contents of `stdout`, much
83
+ # like {::Kernel#system}.
84
+ #
85
+ # _@param_ `cmd` — See {Process#initialize}
86
+ #
87
+ # _@param_ `opts` — See {Process#initialize}
88
+ #
89
+ # _@return_ — The contents of `stdout`
90
+ #
91
+ # Get the system load
92
+ # ```ruby
93
+ # system_load = Subprocess.check_output(['uptime']).split(' ').last(3)
94
+ # ```
95
+ #
96
+ # _@see_ `Process#initialize`
97
+ sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(String) }
98
+ def self.check_output(cmd, opts = {}, &blk); end
99
+
100
+ # Print a human readable interpretation of a process exit status.
101
+ #
102
+ # _@param_ `status` — The status returned by `waitpid2`.
103
+ #
104
+ # _@param_ `convert_high_exit` — Whether to convert exit statuses greater than 128 into the usual convention for exiting after trapping a signal. (e.g. many programs will exit with status 130 after receiving a SIGINT / signal 2.)
105
+ #
106
+ # _@return_ — Text interpretation
107
+ sig { params(status: ::Process::Status, convert_high_exit: T::Boolean).returns(String) }
108
+ def self.status_to_s(status, convert_high_exit = true); end
109
+
110
+ # Error class representing a process's abnormal exit.
111
+ class NonZeroExit < StandardError
112
+ # Return an instance of {NonZeroExit}.
113
+ #
114
+ # _@param_ `cmd` — The command that returned a non-zero status.
115
+ #
116
+ # _@param_ `status` — The status returned by `waitpid`.
117
+ sig { params(cmd: T::Array[String], status: ::Process::Status).void }
118
+ def initialize(cmd, status); end
119
+
120
+ # _@return_ — The command and arguments for the process that exited
121
+ # abnormally.
122
+ #
123
+ # _@note_ — This is intended only for use in user-facing error messages. In
124
+ # particular, no shell quoting of any sort is performed when
125
+ # constructing this string, meaning that blindly running it in a shell
126
+ # might have different semantics than the original command.
127
+ sig { returns(String) }
128
+ attr_reader :command
129
+
130
+ # _@return_ — The Ruby status object returned by `waitpid`
131
+ sig { returns(::Process::Status) }
132
+ attr_reader :status
133
+ end
134
+
135
+ # Error class representing a timeout during a call to `communicate`
136
+ class CommunicateTimeout < StandardError
137
+ # _@param_ `cmd`
138
+ #
139
+ # _@param_ `stdout`
140
+ #
141
+ # _@param_ `stderr`
142
+ sig { params(cmd: T::Array[String], stdout: String, stderr: String).void }
143
+ def initialize(cmd, stdout, stderr); end
144
+
145
+ # _@return_ — Content read from stdout before the timeout
146
+ sig { returns(String) }
147
+ attr_reader :stdout
148
+
149
+ # _@return_ — Content read from stderr before the timeout
150
+ sig { returns(String) }
151
+ attr_reader :stderr
152
+ end
153
+
154
+ # A child process. The preferred way of spawning a subprocess is through the
155
+ # functions on {Subprocess} (especially {Subprocess::check_call} and
156
+ # {Subprocess::check_output}).
157
+ class Process
158
+ # Create a new process.
159
+ #
160
+ # _@param_ `cmd` — The command to run and its arguments (in the style of an `argv` array). Unlike Python's subprocess module, `cmd` cannot be a String.
161
+ sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).void }
162
+ def initialize(cmd, opts = {}, &blk); end
163
+
164
+ # Poll the child, setting (and returning) its status. If the child has not
165
+ # terminated, return nil and exit immediately
166
+ #
167
+ # _@return_ — The exit status of the process
168
+ sig { returns(T.nilable(::Process::Status)) }
169
+ def poll; end
170
+
171
+ # Wait for the child to return, setting and returning the status of the
172
+ # child.
173
+ #
174
+ # _@return_ — The exit status of the process
175
+ sig { returns(::Process::Status) }
176
+ def wait; end
177
+
178
+ # Do nonblocking reads from `fd`, appending all data read into `buf`.
179
+ #
180
+ # _@param_ `fd` — The file to read from.
181
+ #
182
+ # _@param_ `buf` — A buffer to append the read data to.
183
+ #
184
+ # _@return_ — Whether `fd` was closed due to an exceptional
185
+ # condition (`EOFError` or `EPIPE`).
186
+ sig { params(fd: IO, buf: T.nilable(String)).returns(T::Boolean) }
187
+ def drain_fd(fd, buf = nil); end
188
+
189
+ # Write the (optional) input to the process's `stdin` and read the contents of
190
+ # `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
191
+ # are read. Otherwise they are buffered in memory and returned when the process
192
+ # exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
193
+ # buffers.
194
+ #
195
+ # This is only really useful if you set some of `:stdin`, `:stdout`, and
196
+ # `:stderr` to {Subprocess::PIPE}.
197
+ #
198
+ # _@param_ `input` — A string to feed to the child's standard input.
199
+ #
200
+ # _@param_ `timeout_s` — Raise {Subprocess::CommunicateTimeout} if communicate does not finish after timeout_s seconds.
201
+ #
202
+ # _@return_ — An array of two elements: the data read from the
203
+ # child's standard output and standard error, respectively.
204
+ # Returns nil if a block is provided.
205
+ sig { params(input: T.nilable(String), timeout_s: T.nilable(Numeric)).returns([String, String]) }
206
+ def communicate(input = nil, timeout_s = nil); end
207
+
208
+ # Does exactly what it says on the box.
209
+ #
210
+ # _@param_ `signal` — The signal to send to the child process. Accepts all the same arguments as Ruby's built-in {::Process::kill}, for instance a string like "INT" or "SIGINT", or a signal number like 2.
211
+ #
212
+ # _@return_ — See {::Process.kill}
213
+ #
214
+ # _@see_ `::Process.kill`
215
+ sig { params(signal: T.any(String, Symbol, Integer)).returns(Integer) }
216
+ def send_signal(signal); end
217
+
218
+ # Sends `SIGTERM` to the process.
219
+ #
220
+ # _@return_ — See {send_signal}
221
+ #
222
+ # _@see_ `send_signal`
223
+ sig { returns(Integer) }
224
+ def terminate; end
225
+
226
+ # Return a pair of values (child, mine), which are how the given file
227
+ # descriptor should appear to the child and to this process, respectively.
228
+ # "mine" is only non-nil in the case of a pipe (in fact, we just return a
229
+ # list of length one, since ruby will unpack nils from missing list items).
230
+ #
231
+ # _@param_ `fd`
232
+ #
233
+ # _@param_ `mode`
234
+ sig { params(fd: T.nilable(T.any(IO, Integer, String)), mode: String).returns(T::Array[IO]) }
235
+ def parse_fd(fd, mode); end
236
+
237
+ # The pair to parse_fd, returns whether or not the file descriptor was
238
+ # opened by us (and therefore should be closed by us).
239
+ #
240
+ # _@param_ `fd`
241
+ sig { params(fd: T.nilable(T.any(IO, Integer, String))).returns(T::Boolean) }
242
+ def our_fd?(fd); end
243
+
244
+ # Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
245
+ #
246
+ # _@param_ `read_array`
247
+ #
248
+ # _@param_ `write_array`
249
+ #
250
+ # _@param_ `err_array`
251
+ #
252
+ # _@param_ `timeout_at`
253
+ sig do
254
+ params(
255
+ read_array: T.nilable(T::Array[IO]),
256
+ write_array: T.nilable(T::Array[IO]),
257
+ err_array: T.nilable(T::Array[IO]),
258
+ timeout_at: T.nilable(T.any(Integer, Float))
259
+ ).returns(T.nilable(T::Array[T::Array[IO]]))
260
+ end
261
+ def select_until(read_array, write_array, err_array, timeout_at); end
262
+
263
+ sig { void }
264
+ def self.handle_sigchld; end
265
+
266
+ # Wake up everyone. We can't tell who we should wake up without `wait`ing,
267
+ # and we want to let the process itself do that. In practice, we're not
268
+ # likely to have that many in-flight subprocesses, so this is probably not a
269
+ # big deal.
270
+ sig { void }
271
+ def self.wakeup_sigchld; end
272
+
273
+ # _@param_ `pid`
274
+ #
275
+ # _@param_ `fd`
276
+ sig { params(pid: Integer, fd: IO).void }
277
+ def self.register_pid(pid, fd); end
278
+
279
+ # _@param_ `pid`
280
+ sig { params(pid: Integer).void }
281
+ def self.unregister_pid(pid); end
282
+
283
+ # _@param_ `pid`
284
+ sig { params(pid: Integer).void }
285
+ def self.catching_sigchld(pid); end
286
+
287
+ # _@return_ — The `IO` that is connected to this process's `stdin`.
288
+ sig { returns(IO) }
289
+ attr_reader :stdin
290
+
291
+ # _@return_ — The `IO` that is connected to this process's `stdout`.
292
+ sig { returns(IO) }
293
+ attr_reader :stdout
294
+
295
+ # _@return_ — The `IO` that is connected to this process's `stderr`.
296
+ sig { returns(IO) }
297
+ attr_reader :stderr
298
+
299
+ # _@return_ — The command this process was invoked with.
300
+ sig { returns(T::Array[String]) }
301
+ attr_reader :command
302
+
303
+ # _@return_ — The process ID of the spawned process.
304
+ sig { returns(Integer) }
305
+ attr_reader :pid
306
+
307
+ # _@return_ — The exit status code of the process.
308
+ # Only set after the process has exited.
309
+ sig { returns(T.nilable(::Process::Status)) }
310
+ attr_reader :status
311
+ end
312
+ 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.0
4
+ version: 1.5.5
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-06-09 00:00:00.000000000 Z
15
+ date: 2021-07-15 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: minitest
@@ -56,6 +56,20 @@ dependencies:
56
56
  - - ">="
57
57
  - !ruby/object:Gem::Version
58
58
  version: '0'
59
+ - !ruby/object:Gem::Dependency
60
+ name: sord
61
+ requirement: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
59
73
  description: Control and communicate with spawned processes
60
74
  email:
61
75
  - carl@stripe.com
@@ -70,6 +84,7 @@ files:
70
84
  - README.md
71
85
  - lib/subprocess.rb
72
86
  - lib/subprocess/version.rb
87
+ - rbi/subprocess.rbi
73
88
  homepage: https://github.com/stripe/subprocess
74
89
  licenses:
75
90
  - MIT
@@ -89,10 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
104
  - !ruby/object:Gem::Version
90
105
  version: '0'
91
106
  requirements: []
92
- rubyforge_project:
93
- rubygems_version: 2.2.2
107
+ rubygems_version: 3.1.2
94
108
  signing_key:
95
109
  specification_version: 4
96
110
  summary: A port of Python's subprocess module to Ruby
97
111
  test_files: []
98
- has_rdoc: