subprocess 1.3.0 → 1.5.5

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